Skip to main content
This guide covers everything you need to run Timeseries in production on Kubernetes: a complete Helm chart, S3-backed storage with a local disk cache, health checks, monitoring, and security.

Deployment

Overview

Since we haven’t yet built partitioning into Timeseries, a production Timeseries deployment consists of a single replica only. The primary means of scaling would be scaling up, which can take you pretty far. Since all data is persisted on S3, data in a single node Timeseries is highly durable. So, a production deployment of Timeseries consists of:
  • A single-replica Deployment running the opendata-timeseries container
  • An S3 bucket for durable data storage
  • A PersistentVolumeClaim backed by a fast SSD for the SlateDB disk cache
  • A ConfigMap for the Prometheus-compatible scrape configuration, S3 storage settings, and SlateDB tuning
  • A ServiceAccount with an IAM role for S3 access (IRSA on EKS)
Timeseries uses SlateDB’s epoch-based fencing, which means only one writer can hold the epoch lock at a time. The Deployment uses the Recreate strategy so that the old pod is fully terminated before the new one starts — a RollingUpdate creates the possibility for the new pod to be fenced by the old one and never become ready.

Helm chart

Below is a complete Helm chart for deploying Timeseries to production. Create these files under charts/opendata-timeseries/.

values.yaml

values.yaml

templates/configmap.yaml

templates/configmap.yaml

templates/serviceaccount.yaml

templates/serviceaccount.yaml

templates/pvc.yaml

templates/pvc.yaml

templates/deployment.yaml

templates/deployment.yaml

templates/service.yaml

templates/service.yaml

Install the chart

Disk cache

SlateDB caches frequently accessed data on local disk to avoid repeated reads from S3. For production workloads, use an SSD-backed StorageClass:
  • EKS: Use gp3 (General Purpose SSD) or io2 for higher IOPS. For maximum performance, use instance-store NVMe volumes with a local-static-provisioner.
  • Size the cache based on your active working set. The default of 100 Gi is a good starting point; increase if you see frequent cache evictions in the slatedb_* metrics.
Avoid using HDD-backed volumes (e.g. st1, sc1) for the cache. SlateDB issues many small random reads, and spinning disks will bottleneck performance.

Block and metadata caches

On top of SlateDB’s disk cache, Timeseries keeps SST blocks in two foyer-backed caches: block_cache holds decoded data blocks, and meta_cache holds SST filters and indexes. Configure both under storage in prometheus.yaml:
prometheus.yaml
Do not configure block_cache without meta_cache. Every read consults SST filters and indexes; without a cache for them, each read fetches them from S3 again and query latency grows with SST count. In our testing, a query that resolves in ~300ms with both caches took over three minutes with block_cache alone.
Sizing guidance:
  • Set the block cache’s memory_capacity to roughly the recent working set (the last few hours of sample data).
  • Point disk_path at the same SSD-backed PVC used by the disk cache; the two workloads coexist.
  • Keep write_policy: WriteOnInsertion so every cached block is also on disk. Restarts then hit the disk tier instead of re-reading from S3.
  • Size meta_cache to hold the full live filter and index set so it never evicts. Metadata blocks are small relative to data: 4 GiB covers most deployments. An in-memory cache is enough because the cache warmer rebuilds it on restart.
  • Raise flushers if the foyer_* write-queue metrics show backpressure.
See <cache_config> for the full field reference.

Cache warmer

On startup the warmer discovers the time buckets in the warm window (default: the last 24 hours) and drives SlateDB’s cache manager over the SSTs backing them, loading filters and indexes into meta_cache and sample data blocks into block_cache. Queries after a restart run against warm caches instead of paying object-store round trips. That holds on a fresh node too: the warmer reads from S3, not from local state. The warmer is on by default and runs once at startup. To tune the window or disable it, see <cache_warmer_config>.

Durable OTLP ingest

For high-volume OTel metrics, run the stateless ingest path instead of (or alongside) direct OTLP/HTTP writes. Producers keep writing during TSDB restarts, writes stay inside the AZ, and a crashed consumer resumes from the last acked batch on its own.

Health checks

Timeseries exposes two health-check endpoints: Both probes are included in the Helm chart’s Deployment template above.

Graceful shutdown

Timeseries handles SIGTERM and SIGINT signals gracefully:
  1. Stops accepting new connections
  2. Drains in-flight requests
  3. Flushes TSDB data from memory to durable storage
  4. Exits cleanly
The Helm chart sets terminationGracePeriodSeconds: 60 to give the server enough time to complete the flush before Kubernetes force-kills the pod.

Monitoring

All metrics are exposed at /metrics in Prometheus text format. Since Timeseries is itself a Prometheus-compatible data source, you can configure it to scrape its own metrics endpoint (included in the default scrapeConfig above).

Key metrics

Timeseries also exposes slatedb_* metrics from the underlying SlateDB storage engine. These are useful for debugging storage-level performance and compaction behavior.

Example PromQL queries

Security

TLS and authentication

Timeseries does not include built-in TLS termination or authentication. Place a reverse proxy (nginx, Envoy, or a cloud load balancer) in front of Timeseries to handle TLS and access control.

Object storage security

The Helm chart uses IRSA (IAM Roles for Service Accounts) so that the pod receives temporary AWS credentials automatically — no static access keys required. Create an IAM role with the following policy and attach it to the ServiceAccount via the serviceAccount.roleArn value:
The IAM role’s trust policy should scope access to your EKS cluster’s OIDC provider and the specific service account:
Additional recommendations:
  • Enable encryption at rest on the S3 bucket (SSE-S3 or SSE-KMS).
  • Use a VPC endpoint for S3 to keep traffic off the public internet.
  • Block all public access on the bucket.
  • Add a lifecycle rule to transition old data to Intelligent-Tiering after 30 days and abort incomplete multipart uploads after 7 days.