Arhitectura datelor și a sistemelor, de la zero Lecția 21 / 80

Baze de date time-series: Influx, Timescale, Prometheus

Când timestamp-plus-valoare e 99% din datele tale. Optimizările care permit store-urilor time-series să bată bazele de date generale cu un factor de 10x sau mai mult.

Există o clasă de workload unde același rând se repetă la nesfârșit, doar timestamp-ul se mișcă, iar întrebările pe care le pui despre el au aproape mereu forma „care a fost media acestei măsurători din ultimele cincisprezece minute”. Metrici de aplicație. Metrici de infrastructură. Citiri de senzori IoT. Date de tick financiare. Dacă te-ai uitat vreodată fix la un dashboard Grafana, te-ai uitat la date time-series. Lecția asta e despre de ce această formă merită propria familie de baze de date, ce face familia ca să câștige și pe care dintre cele trei opțiuni reale ar trebui să o iei.

Numărul de titlu, înainte să intrăm în mecanisme, e că o bază de date time-series va depăși o bază de date generală pe workload-uri time-series cu un factor de zece până la cincizeci, pe același hardware, pentru aceleași date. Nu e o afirmație de marketing. E consecința a trei sau patru decizii de design pe care le împart toate store-urile time-series și pe care Postgres, MySQL și prietenii nu le împart. Deciziile sunt interesante în sine: sunt un mic studiu de caz despre ce se întâmplă când specializezi un strat de stocare pentru o singură formă de date.

Forma datelor

Un punct time-series are o structură mică, predictibilă. Există un timestamp (de obicei precizie de nanosecundă sau microsecundă). Există un nume de măsurătoare, care e metrica pe care o înregistrezi: cpu_usage, request_count, temperature. Există un set de tag-uri, care sunt perechi cheie-valoare care identifică despre ce lucru e această măsurătoare: host=web-01, region=eu-west, service=checkout. Și există una sau mai multe valori, numerele efective care se înregistrează.

Conceptual, un singur punct arată așa:

2025-12-12T10:14:32.451Z  cpu_usage{host=web-01, region=eu-west}  47.3

Înregistrezi milioane din astea pe minut. Aceeași combinație (measurement, tag-set), numită serie, primește un nou punct la fiecare interval de scrape, pentru totdeauna. Citirile iau aproape mereu forma „pentru seriile care se potrivesc cu acest filtru, dă-mi punctele între timpul A și timpul B, opțional agregate în bucket-uri de mărime N”. Scrierile sunt aproape mereu insert-uri la capul seriei; update-urile și ștergerile de puncte individuale sunt rare până la inexistente.

Ăsta e un workload mult mai îngust decât cel pentru care e proiectată o bază de date generală. Îngustimea e oportunitatea.

De ce se chinuie bazele de date generale

Postgres poate stoca date time-series. O grămadă de oameni fac fix asta în primele câteva luni ale unui proiect. Necazurile încep pe măsură ce datele cresc.

Prima problemă e dimensiunea indexului. Un B-tree pe (series_id, timestamp) funcționează bine, dar un B-tree de un miliard de intrări ocupă spațiu serios, iar scrierile lovesc nivelurile superioare ale arborelui pe măsură ce noile intrări aterizează la valoarea maximă a cheii. Amplificarea de scriere e reală.

A doua problemă e costul de stocare. Un rând în Postgres are un overhead fix per rând (în jur de 24 de bytes pentru tuple header, plus overhead per coloană, plus datele efective). Un punct time-series e poate 24 de bytes de conținut efectiv (timestamp + valoare + o referință de serie). Overhead-ul îți dublează sau triplează stocarea. La scala la care contează time-series, factura de stocare e factura.

A treia problemă e ștergerea. Datele time-series au retenție naturală: păstrezi datele de săptămâna trecută la rezoluție completă, pe cele de luna trecută la rezoluție downsampled, pe cele de anul trecut și mai grosier. În Postgres, ștergerea unui miliard de rânduri vechi e o operațiune scumpă, iar munca de autovacuum care urmează e și mai scumpă. Tabelele partiționate ajută, iar drop-ul partițiilor e rapid, dar trebuie să o setezi explicit și să ții pasul cu ea.

A patra problemă sunt pattern-urile de query care traversează intervale uriașe. „Media cpu_usage din ultima zi, bucketizată la fiecare minut” pe o tabelă de un miliard de rânduri e un sequential scan peste o felie mare. Fără indexuri specializate și pre-agregare, e pur și simplu lent.

Bazele de date time-series rezolvă fiecare dintre acestea explicit.

Ce fac bazele de date time-series

Optimizările variază în detaliu de-a lungul produselor, dar toate împart cam aceeași trusă de unelte.

Scrieri append-mostly, sortate după timp. Intern, stocarea e organizată astfel încât scrierile recente să intre într-un buffer in-memory care se golește în fișiere imutabile, sortate după timp. Nu există scriere aleatorie pe date vechi. Insert-urile costă aproape nimic. Structura e similară în spirit cu un LSM tree, specializată pentru chei ordonate temporal.

Stocare columnară cu compresie. În loc să stocheze fiecare punct ca un rând, store-urile time-series țin fiecare serie ca o coloană de valori plus o coloană de timestamp-uri. Aceeași metrică repetată rând după rând se comprimă extrem de bine: numerele tipice sunt 10x până la 50x compresie în funcție de metrică (un cpu usage care plutește în jurul a 30% se comprimă mult mai bine decât un ID întreg aleator). Compresia e diferența dintre o factură de stocare de mai mulți terabytes și una de câteva sute de gigabytes.

Agregare la momentul scrierii. Pe măsură ce intră puncte, baza de date poate menține rezumate rolled-up: medii, sume, contorizări per minut, per oră, per zi. Când întrebi „media per oră peste ultima săptămână”, query-ul citește rollup-urile per oră, nu punctele brute. Tabelele de rollup sunt minuscule relativ la datele brute, iar costul query-ului scade cu ordine de mărime.

Retenție bazată pe TTL. „Șterge punctele mai vechi de N zile” e o primitivă încorporată. Implementarea e de obicei să dropezi partiții întregi de timp, ceea ce e o operațiune de metadate în loc de o ștergere rând cu rând. Retenția e ieftină și de încredere.

Astea sunt cele patru mișcări. Fiecare store time-series face o versiune a tuturor. Diferențele sunt în suprafață: limbajul de query, modelul operațional, integrarea cu restul stack-ului tău.

Cele trei opțiuni reale

Există un întreg ecosistem de baze de date time-series, dar în 2026 alegerile practice pentru un proiect nou se restrâng la trei.

Prometheus: standardul de metrici

Prometheus e sistemul de metrici dominant în lumea cloud-native. E pull-based: rulezi Prometheus ca server, iar el scrape-uiește metrici peste HTTP de la o listă de target-uri la fiecare cincisprezece secunde (sau orice interval setezi). Target-urile expun un endpoint /metrics cu valorile curente; Prometheus trage și stochează. Nu există agent client-side care să împingă date în baza de date. Modelul e neobișnuit și, odată ce-l internalizezi, foarte plăcut: serviciile nu trebuie să știe unde merg metricile, expun doar un endpoint.

Prometheus stochează datele local, pe un singur server, în propriul format time-series. Retenția e de obicei cincisprezece până la treizeci de zile. Limbajul de query e PromQL, care e dens, puternic și merită învățat dacă faci ceva muncă serioasă în spațiul ăsta.

Avertismentul crucial: Prometheus nu e un store pe termen lung. E un sistem cu un singur server, iar pattern-ul recomandat pentru păstrarea datelor de ani de zile e să împerechezi Prometheus cu un store pe termen lung. Numele standard sunt Thanos, Cortex și Grafana Mimir, toate susținând Prometheus cu stocare obiect (S3, GCS) și oferind scală orizontală și retenție mai lungă. Alege unul în funcție de modelul operațional preferat; modelul de date e același la toate trei.

InfluxDB: baza de date time-series clasică

InfluxDB e produsul original „hai să construim o bază de date time-series de la zero”, început în 2013 și încă pe-aici. E push-based by default, cu un protocol wire în care clienții pot scrie direct. Politicile de retenție și downsampling-ul sunt încorporate. Limbajul de query a avut o istorie turbulentă: originalul era InfluxQL (gen SQL), apoi au schimbat la Flux (un limbaj de query funcțional), apoi au făcut marșarier pe Flux și s-au întors la InfluxQL plus SQL opțional. Dacă te uiți la InfluxDB azi, asigură-te că înțelegi care versiune te uiți și care limbaj de query e curent.

Povestea produsului a fost suficient de zguduită încât n-aș alege InfluxDB azi pentru un proiect greenfield decât dacă există un motiv specific (o investiție existentă, o funcționalitate pe care doar Influx o are). Cele două opțiuni mai plictisitoare de mai jos acoperă majoritatea nevoilor.

TimescaleDB: time-series în Postgres

TimescaleDB e o extensie Postgres. O instalezi pe serverul tău Postgres existent, creezi hypertables (wrapper-ul Timescale peste tabele partiționate) și primești performanță time-series cu suprafața completă Postgres: SQL, join-uri, indexuri, chei străine, restul ecosistemului.

Pentru echipele care sunt deja pe Postgres, ăsta e adesea răspunsul corect. Nu rulezi a doua bază de date. Limbajul de query e SQL-ul pe care deja îl știi. Poți să faci join între datele tale time-series și datele tale relaționale într-un singur query. Funcționalitățile de downsampling și retenție sunt first-class.

Compromisul e că throughput-ul per nod al Timescale e mai mic decât al unui store specializat ca InfluxDB sau al unui sistem OLAP columnar ca ClickHouse. Pentru majoritatea workload-urilor de tip metrici de aplicație sau metrici de business, throughput-ul e suficient. Pentru date IoT sau de tranzacționare cu volum foarte mare, s-ar putea să-l depășești.

ClickHouse: opțiunea OLAP columnară

Voi menționa ClickHouse aici chiar dacă strict vorbind nu e o bază de date time-series, pentru că face treaba time-series extrem de bine. ClickHouse e o bază de date OLAP columnară proiectată pentru query-uri analitice peste volume mari de date, iar time-series e unul dintre workload-urile la care excelează. Stocarea e columnară, compresia e excelentă, limbajul de query e SQL cu extensii, iar throughput-ul e foarte mare.

Vom acoperi ClickHouse cum trebuie în modulul 8 când ajungem la OLAP. Pentru moment, să știi că dacă workload-ul tău time-series are și query-uri în stil analitic (group-by-uri peste multe dimensiuni, agregări complexe), merită o privire către ClickHouse.

Pipeline-ul

Majoritatea setup-urilor de metrici de producție arată la fel, indiferent care store e la mijloc. Serviciile sunt instrumentate (cu biblioteci client Prometheus, OpenTelemetry sau agenți specifici vendorului). Prometheus le scrape-uiește. Stocarea pe termen lung ține datele istorice. Grafana le interoghează pe ambele pentru dashboard-uri.

flowchart LR
    A[Service A: /metrics] --> P[Prometheus]
    B[Service B: /metrics] --> P
    C[Service C: /metrics] --> P
    P --> LT[Long-term store: Thanos/Mimir]
    P --> G[Grafana]
    LT --> G
    G --> U[User dashboards]

Împărțirea între Prometheus (recent, rapid) și store-ul pe termen lung (istoric, mai ieftin) e pattern-ul standard. Query-urile pentru ultima oră merg la Prometheus; query-urile pentru ultimul trimestru merg la Thanos. Grafana abstractizează diferența.

Capcana cardinalității

Există o capcană operațională care merită semnalată pentru că fiecare echipă care folosește o bază de date time-series o lovește în cele din urmă: explozia de cardinalitate.

Numărul de serii distincte din baza ta de date e produsul numerelor de valori distincte pentru fiecare tag. Dacă ai o metrică cu trei tag-uri (host, region, service), iar fiecare tag are zece valori distincte, ai o mie de serii. Gestionabil. Dacă unul dintre tag-uri e user_id, cu un milion de valori distincte, ai acum o sută de milioane de serii. Baza de date se va chinui. Folosirea memoriei explodează, performanța query-urilor se prăbușește, iar sistemul ar putea pur și simplu să refuze să accepte scrieri noi.

Regula e: tag-urile trebuie să aibă cardinalitate mică. Lucruri ca host, region, service, endpoint, status_code sunt ok. Lucruri ca user_id, request_id, session_id, ip_address sunt periculoase. Dacă te trezești vrând să pui un identificator cu cardinalitate mare ca tag, aproape sigur vrei un sistem diferit: un store de logging (Loki, Elasticsearch) sau un store de tracing (Jaeger, Tempo), nu un store de metrici.

Fiecare echipă învață asta o dată, pe calea dureroasă. Acum tu ai învățat-o pe calea ușoară.

Cazuri de utilizare care se potrivesc

Workload-urile unde bazele de date time-series sunt fără echivoc răspunsul corect:

  • Metrici de aplicație: rate de cereri, latențe, rate de erori, saturație. „Cele patru semnale de aur” standard. Astea alimentează fiecare dashboard de producție.
  • Metrici de infrastructură: CPU, memorie, disc, rețea. Substratul pe care rulează tot restul.
  • Date de senzori IoT: citiri de temperatură, poziții GPS, telemetrie de dispozitiv. Adesea partiționate după ID de dispozitiv, ceea ce înseamnă că trebuie să fii atent la cardinalitate (un milion de dispozitive e ok; un miliard nu, fără design special).
  • Date de tick financiare: prețuri per instrument, eșantionate la frecvență mare. ClickHouse și KDB+ domină aici, dar TimescaleDB apare și în spațiul ăsta.

Punând totul cap la cap

Recomandarea, pentru majoritatea echipelor în 2026, e scurtă. Pentru metrici într-un stack cloud-native, folosește Prometheus, împerecheat cu Thanos sau Mimir pentru stocare pe termen lung, interogat prin Grafana. Pentru workload-uri time-series încorporate într-o aplicație care folosește deja Postgres, folosește TimescaleDB și evită rularea unei a doua baze de date. Pentru time-series intens analitic la volum foarte mare, uită-te la ClickHouse. Dacă te trezești apelând la InfluxDB, asigură-te că ai un motiv specific.

Proprietatea împărtășită a tuturor acestora e că sunt specializate. Bat Postgres pe workload-uri time-series cu o margine largă și pierd la Postgres pe orice nu e workload time-series. Folosește unealta potrivită. Costul rulării unui store specializat lângă baza ta de date principală e aproape mereu mai mic decât costul încercării de a face totul într-una singură.

Citate și lecturi suplimentare

  • Documentația Prometheus, https://prometheus.io/docs/ (consultat 2026-05-01). Referința pentru PromQL, modelul de scrape și opțiunile de stocare pe termen lung.
  • Documentația TimescaleDB, https://docs.timescale.com/ (consultat 2026-05-01). Hypertables, agregate continue, politici de retenție.
  • Documentația InfluxDB, https://docs.influxdata.com/ (consultat 2026-05-01). Linia InfluxDB 3 și revenirea recentă la SQL.
  • Brian Brazil, „Prometheus: Up and Running” (O’Reilly, 2018). Încă cea mai bună carte pentru a intra adânc în PromQL și modelul operațional. O a doua ediție e în lucru.
  • Grafana Labs, „Grafana Mimir documentation”, https://grafana.com/docs/mimir/latest/ (consultat 2026-05-01). Opțiunea de store pe termen lung care a câștigat cel mai mult teren în ultimii ani.
Caută