Skip to main content
Back to Blog
Data Engineering

Avoid Cardinality Failures: Time Series Database Design for Engineers

February 202624 min read
Time series database design architecture

Architect a time series database on five pillars: a series-first data model, an append-only WAL-to-head-to-immutable-block write path, per-series columnar compression, time-based partitioning with tiered retention, and strict cardinality controls on the inverted index. The goal is to have predictable costs per active series, storage costs that are well-understood as data ages, sub-second queries on recent time ranges, and guardrails that prevent a single misbehaving exporter from bringing down the cluster. Techniques like Gorilla encoding, the WAL/LSM write pattern, and disciplined tag design, an approach PODTECH uses in all of its telemetry projects, distinguish a production-grade system from one that quietly breaks at scale.

TL;DR:

  • High-cardinality tags can lead to exponential series growth and database instability, especially when tag values are unbounded, such as IDs.
  • Batch flushing and compressed block sizing are a tradeoff between CPU spent on compression and the speed of recent-data queries.
  • Tiered retention with sum and count aggregation avoids spurious averages and optimizes storage for long-term trend analysis.
  • Monitoring index size, WAL lag, and memory consumption helps identify problems early before they become outages.
  • Operational controls like custom schema design, relabeling, and per-tenant series caps are critical for cardinality containment and scalability.

PODTECH Telemetry Systems Build More Reliable

PODTECH develops custom software to build telemetry, datacenter management and critical infrastructure integration at an enterprise scale.

LEARN ABOUT PODTECH

Table of Contents

When a purpose-built time series database is the right choice

Not all metrics require a dedicated store. The decision is based on workload shape, not data type alone.

You know you need a time series database when your ingest is high-frequency, most of your queries are time-range, and you have a strong recency bias. Metrics scraped every 10-15 seconds from thousands of hosts, building telemetry sensor readings, or a stream of financial tick data are all workloads that fit this profile. They all have a few things in common: the ratio of writes to reads is very high, the vast majority of queries are over the last few hours or days, and aggregation, averages, rates, and percentiles over a window, is far more important than random point lookups by primary key.

A general-purpose relational database or document store is capable of storing time-stamped data just fine when the volume is low and query patterns are diverse. The mistake engineers make is believing that, just because data is time-stamped, it must go in a TSDB. Order history, user session logs, or audit trails frequently require relational joins, transactional guarantees, or arbitrary filtering that a TSDB is not designed for. Shoving that data into a purpose-built time series engine generally means fighting the storage engine rather than working with it.

Run through this checklist during an architecture review:

  • Is the ingest rate consistently high, thousands of points per second per node, or does it arrive in bursts that still need smoothing?
  • Are most queries time-range plus aggregate, rather than per-row fetches?
  • Is 90% of read traffic from the last 24-72 hours, with archival data scanned rarely in large chunks?
  • Does the schema remain stable, with a bounded and predictable set of tag combinations?
  • Do you want automatic retention and downsampling instead of manually deleting old data?

If you answered yes to most of these, a purpose-built TSDB architecture will save you measurable engineering effort. If your workload is joins-intensive, transactionally-updated, or unpredictable ad hoc, a general-purpose database with effective time-range indexing will likely still be your best bet.

Series data model and schema: tags, identifiers and dimension design

A time series database identifies a series by its metric name and its full set of tags. cpu_usage{host="web-1", region="eu-west", env="prod"} is a different series from cpu_usage{host="web-2", region="eu-west", env="prod"}. Each unique combination of tag values produces a new series and each new series allocates memory in the index and a slot in the in-memory head. This is the single most important fact in time series data modeling, because it determines whether your system scales gracefully or falls over under its own index.

Tag choice impacts both index size and query surface. Generic tags such as region, service or status_code keep cardinality in check while allowing data to be sliced in a useful manner. Tags with high cardinality such as request_id or session_token increase the number of series without adding much value to the query surface, because no one in their right mind will ever ask for average latency for one request ID.

There are two general strategies for encoding tag data. Flattening the tags directly on each series record is easy and write-efficient, but duplicates the tag strings on every series that uses them. Normalising stable identifiers into a small series registry, and referring to them by a tiny series ID instead, reduces storage and index duplication dramatically, at least when the tag values are long strings duplicated across millions of series. Time series storage design covers this trade-off in more detail: registries reduce the data footprint but introduce a dereference hop you have to account for in the hot path.

Practical rules of thumb for dimension design:

  • Do not use user IDs, session tokens, or UUIDs for tag values; these become effectively unbounded cardinality.
  • Do not put raw URL query strings or entire request paths in a tag; use a template like /orders/{id} instead of a literal path.
  • Push truly high-cardinality event data into logs or tracing systems, and reserve the TSDB for aggregate counts and rates.
  • Use relabeling rules at ingest time to drop or rewrite tags before they ever reach the index.

Pro Tip: Run a weekly cardinality audit on your top 20 metrics ranked by series count. A single misconfigured exporter emitting a raw customer ID as a tag can quietly contribute hundreds of thousands of series before anyone notices the storage bill.

Write path and storage engine: WAL, in-memory head, flush to immutable blocks

Almost every modern TSDB implements the same canonical three-stage write pipeline, and it's important to understand it when tuning latency, durability, and throughput correctly.

  1. Write-ahead log (WAL) append. Each incoming sample is written to an append-only log on disk. This is the durability guarantee: if the process crashes before the data makes it to persistent storage, the WAL lets you replay the unflushed writes on restart.
  2. In-memory head buffer. Samples are buffered in memory in a data structure organised by series. This is usually a window of one or two hours, and is where incremental compression and indexing occurs, and where the newest and hottest data lives.
  3. Flush to immutable time-bounded blocks. When the head window closes, its contents are written out as a compressed, immutable block on disk or object storage. The block spans a fixed time range. Design a Time-Series Database explains how the WAL to head to block pipeline makes retention cheap: dropping old data means deleting whole blocks rather than scanning and deleting individual rows.

This is well suited to append-mostly workloads as it prevents random-write amplification. Writes arrive to the WAL in sequence, they are coalesced and compressed in memory, then written once as a dense, immutable chunk. There is no updating in place after the fact, exactly what an LSM-style engine is optimised for.

The knobs that really matter are head window length and block duration. A long head window has more data sitting in memory before it flushes. This increases memory pressure, but also means more compression opportunity and fresher query results served straight from RAM. A short window flushes more often, keeps memory bounded, but generates more, smaller blocks that must be compacted later. Most production systems choose a head window length somewhere between one and two hours, with block durations tuned to match typical query ranges.

Ingestsamples arriveappend-mostlyWALdurable appendcrash recoveryHeadin-memory windowindex + compressBlocksimmutabletime-boundedretention by whole-block deletion

WAL retention requires independent consideration from block retention. Only retain WAL segments that have not yet been durably flushed into a block; once a block is durable, all corresponding WAL segments may be truncated. Size your WAL to your worst-case recovery-time scenario: if a crash occurs immediately before a scheduled flush, how much data might you need to replay, and how long can that replay take before the node is considered available? WAL retention too small risks data loss on crash; too large wastes disk space and slows replay on startup.

Compression and block formats: delta-of-delta, XOR and columnar layout

Time series data compresses unusually well, and that is not coincidental. By storing data columnar, per series with timestamps in one stream and values in another, each stream is highly regular, and regular data compresses far better than rows of mixed type.

Timestamps in a series occur at approximately regular intervals, every 15 seconds, every minute, and so on. Instead of recording the timestamp in full, record the delta from the previous timestamp, then the delta of that delta. For a constant interval, the delta-of-delta is zero and encodes to almost nothing. Values compress in other ways: successive readings from the same sensor or counter are likely to be numerically close. XOR-ing a new float value against the previous one produces mostly zero bits, which run-length and bit-packing encoders then reduce further.

By the numbers:

Gorilla-style encodings, delta-of-delta for timestamps and XOR for float values, were able to achieve roughly 1.37 bytes per sample on Facebook’s production workload, compared to a naive fixed-width layout that would require about 16 bytes per sample. That is nearly a 12x reduction in raw storage before any general-purpose compression is layered on top.

The tradeoff is CPU, not disk. Decoding the delta-of-delta and XOR streams on the fly at query time is expensive in cycles that a dumb fixed-width format would avoid. Compaction, where many small blocks combine into larger blocks and are re-encoded, offloads even more work to the CPU. For teams thinking about long retention horizons they need to budget for that compaction and decompression cost in addition to disk space; the storage win is real, but it is not free.

Block size choice is part of all of this. Smaller blocks flush quickly and release memory pressure sooner, but cause more files, more index entries, and heavier compaction overhead when they merge. Larger blocks compact less frequently and are more efficient to query over wide time ranges, but tie up more data in a single unit which slows individual flushes and increases the blast radius if one block is corrupted.

  • Size block duration to the dominant query range on your system. If most dashboards query the last hour, hourly blocks are often a good fit.
  • Avoid all-at-once compaction; let it run in smaller background portions so CPU load stays steadier.
  • Watch decompression time on slow queries; if it dominates latency, your blocks may be too large or too old to remain in a fast tier.

Partitioning, sharding and retention: time blocks, custom partitions and tiering

Time-based partitioning is the default for a reason: it matches the write pattern, new data always goes to the current partition, and the most common query pattern, recent-range lookups only touch the newest one or two partitions. Most engines support partitioning by fixed time windows, hourly or daily, and this is a good approach when your queries are truly time-scoped.

Custom partitioning, split by tag value as well as time, is for when a significant fraction of queries filter tightly on some particular tag, such as region or tenant_id. Reducing the number of partitions scanned for a single-series or single-tenant query can meaningfully reduce query latency. The tradeoff is real: InfluxDB’s custom partitioning docs note that splitting by tag increases the number of partition files, which increases compaction load and file-system overhead. Don’t use custom partitioning unless a specific, high-frequency query pattern makes the additional file count worthwhile.

Sharding by hash(metric + tags) is the best way to evenly distribute the write load across storage nodes and at the same time keep all the data of a given series on the same shard. This is known as series locality. This matters because the vast majority of queries read or write one or at most a few series at a time; if a series' data were spread across multiple nodes then every such query would have to perform a scatter-gather round trip instead of a single targeted read.

Retention and downsampling design typically follows a tiered pattern:

  • Raw resolution kept for a short period, often 7 to 30 days, where engineers debug incidents at full fidelity.
  • 1-minute or 5-minute rollups kept for months, covering capacity planning and trend dashboards.
  • Hourly or daily rollups kept for a year or more to support long-term reporting and compliance retention.

One important fact that most teams do not realize is what is stored at each level of rollup. The trap to avoid is storing the pre-averaged value itself: simply averaging a value yields mathematically incorrect results when you roll up any further. The right pattern is to store the sum and the count separately at each level, so a true average can always be recomputed correctly regardless of how many levels of rollup a query traverses.

Indexing, tag lookup and the cardinality problem

Every TSDB query that uses a tag to filter data, region="eu-west", status="500", is served using an inverted index: a data structure that, for each tag key-value pair, lists the series IDs that have it. A query with several tag filters intersects the posting lists to identify the set of series to read, then only reads data blocks of those series.

Cardinality, the number of unique active series, is the primary limiting factor on how far a TSDB architecture can scale, and it scales far faster than most engineers anticipate. A metric with three tags of 10, 20 and 50 possible values respectively already produces 10,000 series. Add a fourth tag with unbounded values, such as a customer ID, and that number balloons into the millions. Operational experience across TSDB deployments bears this out in many ways, and shows that this is the single most common cause of production incidents: memory exhaustion in the head buffer, index bloat that slows every query, and ingestion nodes crashing under the weight of series they were never designed to hold.

Once you know what to look for the symptoms are unique: creeping memory consumption with no corresponding increase in real traffic, query latency that degrades over time instead of spiking, and ingestion queues backing up when traffic peaks. All three are symptoms of cardinality growth, not just raw data volume.

Operational defences that actually work in production:

  • Relabeling at the edge. Strip or rewrite offending tags before they reach the ingestion pipeline, not after.
  • Per-tenant series caps. Hard-limit the number of series a team or customer can have, with alerting well before they hit the limit.
  • Rate-limit new series creation. A sharp increase in the number of new series, not merely samples, is often the first indication of a cardinality problem.
  • Redirect naturally high-cardinality data to logs or traces. Request IDs, error messages, and stack traces belong in logging or tracing systems, not a metrics store.

Pro Tip: Watch your rate of new series creation, not just your total series count. A slow and steady increase might be normal growth; a sudden spike is, almost without exception, a bad deploy that introduced an unbounded tag.

Query engine: planner, time-bucketing and mergeable aggregations

A query begins with selector resolution: the planner accepts your tag filters, asks the inverted index for the concrete set of series IDs that match, then fans out to whichever storage nodes happen to have those series' blocks, and requests only the time range the query needs.

One piece of information the planner provides that engineers might otherwise miss is resolution selection. If a query is requesting a trend for the last six months, it should be served from an hourly rollup, not from the engine decompressing six months of raw 15-second samples. Selecting the coarsest resolution that still meets the accuracy requirements of the query is how long-range dashboards remain performant, and the core pattern that TSDB read paths are designed around.

Rollup operations are not composable in the naive way, this is where naive implementations fall over. If you have a set of hourly averages, you can't just take the average of those to get a correct daily average, unless each hour had exactly the same weight. The fix is to store both sum and count at each level of the rollup, so any aggregate at a higher level can recompute the true average from the raw totals. Percentiles are even more difficult: you cannot merge two p99s from different time buckets to get a correct combined p99. Production systems use mergeable sketch structures, t-digest and HDR histogram-style structures, specifically because they can be combined across buckets and still provide statistically valid percentiles.

Common TSDB functions carry real implementation weight:

  • rate() and increase() need to detect and account for counter resets, not just subtract sequential values.
  • Percentile functions need sketch-based storage if they are to support correct rollup recombination.
  • Exemplars, associating a spike in a metric to a particular trace, require keeping a small sample of raw trace references with the aggregated data.

Architecture patterns and trade-offs: single-node, distributed and cold tiering

A single-node or embedded TSDB is the correct starting point for most internal-use systems, and engineers often over-architect in this space. One node serving ingest, storage, and queries is easier to operate, has no cross-node consistency to reason about, and is significantly cheaper to run. It scales until your write throughput or query concurrency outpaces what a single machine can manage, or until your availability needs require redundancy that a single node can't provide.

Distributed designs do solve the throughput and availability problem, but at the cost of real complexity. Sharding the data across nodes entails query fan-out, cross-node result merging, and more brittle consistency guarantees during node failure or rebalancing. The operational cost is real: more moving parts means more failure modes, and a larger engineering investment in monitoring and automation to maintain a healthy cluster.

Hot-series problems occur when one series, or one tag value such as a single busy tenant, gets a disproportionately high share of write or query volume, causing overload on the single shard on which it resides. This may be ameliorated by splitting that series' key further, by aggressively caching recent data of the hot series at the query layer, or by placing known-hot tenants onto dedicated shards rather than having them compete with others.

Cold-tiering to object storage prolongs retention at a reduced cost: older blocks are moved from fast local disk to lower-cost object storage at the expense of higher query latency when that data is eventually needed. This works great for compliance retention and infrequent historical lookups, but shifts compaction and retrieval costs to the query path.

  • Retain frequently queried hot data on fast local storage even when it is aged, if query patterns demand it.
  • Bundle block files before sending them to cold storage to avoid object-storage request costs on many small objects.

Have clear expectations for latencies when dealing with cold-tier queries. If you're going to query two-year-old data, expect seconds not milliseconds.

Operational concerns: monitoring, backups and out-of-order data

Operating a TSDB reliably in production is about observing the right signals in time before they become incidents.

Monitor the memory used by the head regularly. This is the first sign of a cardinality problem you will see before it reaches the index. Monitor WAL lag, the amount of time between a write coming in and it being durably persisted. If the lag is growing it is a sign that the flush pipeline can't keep up with ingest. Monitor the size of the compaction backlog, as a backlog that continues to grow will eventually cause query latency to degrade as data ends up being stored on too many small blocks. And monitor total index size separately from disk usage, as index growth due to cardinality can substantially outpace raw data growth.

Backup and restore for a block-based TSDB is relatively easy because immutable blocks are easy to snapshot: copy completed blocks to a secondary location or object store and separately snapshot the WAL and current head, since these are still mutable. When doing upgrades, always double check block format compatibility first. Reading an unknown-but-compatible format is safe; writing a new format the old version can't read on rollback is how upgrades become outages.

Late or out-of-order writes must be explicitly handled, because the block-based write path is designed to receive data approximately in real time. Most systems have a bounded look-back window, writes for the last few hours are accepted, anything older is rejected, and route data arriving outside of that window through a separate catch-up path that eventually merges with already-flushed blocks. This directly interacts with retention: a very short look-back window is easier to implement but will silently drop legitimately delayed data from remote or intermittently connected sources.

Pro Tip: If your ingest sources are remote or intermittently connected, increase your out-of-order acceptance window before increasing your raw retention window. Late data that's rejected becomes silent data gaps in dashboards that no one notices until an incident review.

Practitioner perspective and PODTECH notes

Supplying telemetry systems to datacentre and critical-infrastructure customers has informed PODTECH's approach to time series database design. The lessons we learn are seldom about the storage engine. They're about the limitations that surround it.

PODTECH considers cardinality budgets a first-class citizen, not an afterthought. In the context of our work on enterprise telemetry deployments, that means instrumenting cardinality violations directly, surfacing the offending exporter or sensor feed when a budget is violated, and hardcoding relabeling rules into the deployment pipeline itself, instead of leaving cardinality control to manual firefighting in the aftermath of an incident.

Per-tenant isolation is more important in enterprise deployments than most general TSDB tutorials admit. When a single platform is serving many building sites, data centre halls, or customer organisations out of the same infrastructure, a badly configured sensor feed in one tenant's account should not be allowed to consume resources needed by other users. That leads to conclusions like per-tenant series caps instead of only global ones, and per-client retention tiers where regulatory compliance needs vary.

SLA-backed latency commitments and audit requirements also reweight default trade-offs. A generic engineering rule of thumb might advise aggressive cold-tiering to save on storage cost; a client with a stringent uptime SLA or a regulatory audit requirement may have to access older data more quickly than that default would have them do, shifting the position of the hot/cold boundary. Teams working on telemetry platforms with these kinds of constraints, especially in the fields of datacentre monitoring and construction site safety, often have to go for a bespoke integration approach rather than an out-of-the-box configuration.

Handling data consistency and durability under high write loads

Durability in a TSDB is built around the WAL: no sample is thought to be safely stored until it has been appended to the write-ahead log on durable storage. Under high write load the problem is how aggressively to flush that log to disk. Flushing on every single write provides maximum durability but limits throughput very hard; batching writes and flushing every few milliseconds gives up a small window of worst-case data loss in exchange for orders of magnitude greater ingest capacity. Almost all production systems do the batched approach and are happy to accept that a hard crash might lose a few milliseconds of the most recent writes.

Strong consistency is usually not the default in distributed TSDB deployments. A write acknowledged by one node might take a few milliseconds to become visible to a query hitting a replica. Most time series workloads can live with this: nobody wants sub-millisecond consistency on a dashboard showing server temperature. Tick data in financial services and other latency-sensitive series are an exception, where stronger consistency guarantees or single-writer designs may be worth the cost in throughput.

Backpressure handling is as important as the consistency model itself. If the ingest rate overwhelms the flush pipeline, the system must have a clearly defined behaviour: reject new writes with an obvious error, backpressure them with bounded memory queueing, or degrade gracefully by temporarily increasing the WAL flush interval. Silent, unbounded queueing is the worst option, because it turns a temporary load spike into an eventual out-of-memory crash. Defining and testing that backpressure behaviour before a real traffic spike occurs is far cheaper than debugging it live.

Storage engine architectures: LSM trees and their alternatives

Most modern time series databases are based on log-structured merge, LSM, trees. The reasons trace all the way back to the write path described above. An LSM tree writes new data sequentially to an in-memory structure, then flushes it as an immutable sorted file on disk, merging and compacting files together in the background. This fits the TSDB write pattern nearly perfectly: append-heavy, rarely-updated, time-ordered data.

The other alternative, B-tree based storage employed by most classic relational databases, optimises for random reads and in-place updates. That's a poor fit for time series workloads, where updates to old data are rare and writes are overwhelmingly new inserts at the current timestamp. Forcing a B-tree to absorb high-volume sequential inserts causes write amplification and page-splitting overhead that LSM-based engines simply avoid by design.

Some specialised TSDB engines take a hybrid approach: an LSM-style write path feeds into custom columnar block formats optimised specifically for per-series timestamp and value encoding, rather than generic key-value storage. This is where the Gorilla-style compression discussed earlier plugs in: the LSM tree handles write ordering and durability, while the columnar block format handles the actual byte-level efficiency once data is at rest.

The price paid by engineers for LSM-based engines is read amplification during compaction and a background CPU cost that increases with write volume. An overcommitted system with a constant high write load must have sufficient spare CPU headroom for compaction to keep up or query latency suffers as data piles up in too many uncompacted files.

Techniques for indexing and optimising time series queries

In addition to the main inverted index, a few tricks are actually used to help query performance in production TSDB deployments.

Block-level bloom filters allow the query planner to entirely bypass blocks that could not possibly contain a matching series, so no disk reads are wasted before even getting to the real index lookup. This has the biggest impact at scale, where a query would otherwise have to examine thousands of blocks to find the few that actually contain relevant data.

Pre-aggregated rollups, as mentioned above for retention purposes, are also a query optimisation technique in their own right. A dashboard querying a year of data should never touch raw samples; routing it automatically to the coarsest rollup that satisfies its accuracy needs is often the single biggest performance win available, bigger than any index tuning.

Caching query results at the API layer, not just blocks of data in memory, is massively useful for dashboards viewed by many users simultaneously. A single panel on a very popular dashboard making the same time range query from dozens of users in the same minute should access a result cache, not recompute the same aggregation dozens of times against storage.

Issuing parallel queries to storage nodes, fanning out a single query to all nodes with relevant series at the same time, rather than fanning in a sequence of results one at a time, dramatically reduces wall-clock latency for wide queries. This only applies if the series are uniformly distributed across nodes to begin with, of course, which is why the sharding strategy comes back into play here as well: a badly-chosen shard key bloats every downstream query optimisation.

Security considerations for time series databases

Access control must be provided at two levels within a TSDB deployment: which clients are allowed to write data, and which clients are allowed to query which series. Write access should be strictly scoped to prevent accidental or deliberate misuse of the TSDB. This may be as specific as per source or per exporter, such that a compromised or misconfigured client cannot inject data under the identity of another tenant, or pollute other tenants' unrelated series. Query access should support tag-based scoping as well, so a tenant or team should only be able to read series that match their own namespace, rather than relying on application-layer filtering alone as the only line of defense.

Encryption at rest and in transit are both important. Transit encryption will be over TLS between collection agents and the ingestion endpoint, obviously important for telemetry that has to cross public networks from remote sites or field devices. At-rest encryption pertains to the block storage layer itself, and will be relevant for compliance-driven deployments where telemetry data is considered sensitive operational data rather than innocuous metrics.

Audit logging in particular deserves a call out here. Who queried what, and when, is something that is a compliance requirement in a lot of financial and critical-infrastructure use cases, not just a feature add-on. This is the area where a purpose-built platform justifies its price over bare open-source deployment: industrial IoT and infrastructure security roadmaps are increasingly making telemetry access logging a baseline control, not an optional add-on.

Tenant isolation, which we discussed operationally earlier due to the cardinality requirements, also has a security aspect to it. Queries for one tenant's data in a multi-tenant TSDB deployment should not be able to leak information from, or infer information about, another tenant's series, even by indirect means such as timing side-channels on shared query infrastructure.

Author perspective: lessons learned and common engineering pitfalls

Three things are really clear after watching enough TSDBs deployed successfully and not. First, cardinality silently kills more deployments than data volume ever could; failures that look like the database can't keep up are almost always the index holding ten million series it was never designed to contain. Second, compression stats are seductive and CPU costs are easy to forget; 12x storage win at Gorilla-level encoding still needs decompression cycles at query time, and that bill will come due eventually. Third, retention designed on day one almost never survives contact with real compliance and debugging needs, so build tiering that can flex instead of hardcoding a single retention window.

A small but useful checklist to run through for any architecture review:

  • Are tag values bounded and enumerable?
  • Is there a relabeling step before ingestion?
  • Does retention store sum and count, not raw averages?
  • Is cardinality monitored as its own metric, separate from data volume?

Teams with a truly complex telemetry build, especially across regulated or multi-site infrastructure, are often well-served by obtaining a second opinion before finalizing the schema.

— Harry

How PODTECH supports enterprise time series projects

PODTECH designs and develops the telemetry and datacentre monitoring systems where these design decisions have real operational currency, not theoretical. Datacentre telemetry integrations, containment monitoring, and BMS, PMS, and NMS that work where a cardinality mistake or a badly tiered retention policy translates directly into downtime risk or compliance gap, underwritten by a 99.9% uptime SLA across delivery.

PODTECH engagement is often most compelling at a particular moment: when scale has overwhelmed a simple open-source deployment, when compliance or audit requirements are driving the need for access controls and retention tiering a default configuration cannot provide, or when a legacy monitoring infrastructure needs modernising without the distraction of a rip-and-replace. PODTECH’s legacy modernisation work and enterprise automation services both apply directly to this, as does dedicated machine learning development for teams that want to use predictive analytics layered on top of stored telemetry rather than simply dashboards.

When your team is considering a custom time series architecture versus a built-from-platform managed service, ask for a technical review through our services overview and have a second set of eyes on the schema before it solidifies.

Sources

Reliably ingesting data into a TSDB is a different design problem to storing it well. Most production pipelines work in push/pull mode at the collection layer, then normalise and buffer before writes hit the storage engine itself.

Pull-based collection, an agent scraping metrics endpoints on a schedule, is suitable for environments where you control the target and want centralised control over scrape frequency and failure handling. Push-based collection, sources sending data directly to a receiver, often via a message queue like Kafka, is suitable for high-volume or geographically distributed environments where buffering and decoupling are more important than central scrape orchestration.

References used throughout this article:

FAQ

What is cardinality in a time series database?

Cardinality is the number of unique active series in the database. Each unique combination of metric name and tag values creates a new series. High cardinality increases index size, memory usage, and query cost.

Why are IDs dangerous as tags?

IDs such as user IDs, request IDs, session tokens, and UUIDs are usually unbounded. That means they create a new series for nearly every event, which can explode series count and destabilize the TSDB.

Why do TSDBs use a WAL?

The write-ahead log provides durability. Incoming samples are appended to disk before they are flushed into immutable blocks, allowing recovery after crashes by replaying unflushed writes.

Why store sum and count instead of averages in rollups?

Averages are not safely composable across rollup levels. Storing sum and count allows the query engine to recompute a mathematically correct average at any larger time bucket.

When should you use a general-purpose database instead of a TSDB?

Use a general-purpose database when your workload is join-heavy, transactionally updated, or driven by unpredictable ad hoc filtering rather than time-range aggregation over append-mostly data.