Skip to content

Auswahl der besten Tabelleneinstellung für TimescaleDB

Aufgabenbeschreibung

Eine gängige Aufgabe für Marktplätze besteht darin, das Volumen einiger Produkte in einem Zeitraum von 1 Tag/7 Tagen/30 Tagen in nahezu Echtzeit zu berechnen. TimescaleDB oder jede andere Zeitreihendatenbank ist für diese Art von Aufgabe perfekt geeignet. Lassen Sie uns TimescaleDB testen, da es in meinem aktuellen Projekt bereits PostgreSQL verwendet wird. Ich werde ein sehr vereinfachtes Modell aus meinem aktuellen Projekt (NFT-Marktplatz) verwenden. Wir haben Tausende von Sammlungen für eine Vielzahl von Blockchains und erfassen jede Listungs-, Gebots- oder Verkaufs-Transaktion in der Tabelle. Die Gesamtzahl der Transaktionen ist enorm: Milliarden von Zeilen. Die Idee, wie wir die Berechnung beschleunigen können, besteht darin, unsere Tabelle in zeitliche Buckets aufzuteilen und die Zahl mit ihnen zu berechnen.

Vorbereitung

Dies ist ein Schema der Tabelle und einiger Indizes:

sql
create table public.sale_changes
(
    time          timestampz not null,
    id            varchar(1024),
    collection_id varchar(512),
    blockchain    varchar(512),
    floor         numeric(78, 10),
    sales         bigint,
    volume        numeric(78, 10)
);
select create_hypertable('sale_changes', 'time', chunk_time_interval => interval '1 day');
create unique index ON sale_changes(blockchain, id, time);
create index ON sale_changes(collection_id, time);
create * from add_dimension('sale_changes', by_hash('blockchain', 16));

Wir haben einige zusätzliche Indizes erstellt:

  • CREATE UNIQUE INDEX ON sale_changes(blockchain, id, time); -- dies ist für die Datenkonsistenz erforderlich
  • CREATE INDEX ON sale_changes(collection_id, time); -- dies dient der schnelleren Aggregation nach Sammlung

Außerdem haben wir eine zusätzliche Dimension für eine bessere Performance von blockchain-spezifischen Abfragen hinzugefügt. 16 ist die Anzahl der Buckets, die derzeit für Tests ausreichend ist.

Ich habe ein Backup mit Daten für das aktuelle Jahr erstellt und in meine lokale TimescaleDB geladen:

  • POLYGON: 4.917.155 Zeilen
  • ETHEREUM: 3.032.708 Zeilen
  • APTOS: 129.760 Zeilen
  • CELO: 13.692 Zeilen
  • ZKSYNC: 8.832 Zeilen

Insgesamt haben wir lokal 8.102.147 (~8m).

Lassen Sie uns zwei Abfragen für Leistungstests verwenden:

sql
-- alle Abfragen
select csc.*,
       cs.floor,
       cs.owners from (select collection_id,
       SUM(volume),
       SUM(sales)  AS total_sales,
       CASE
           WHEN first(floor, time) = 0 THEN 0
           ELSE ROUND(((last(floor, time) - first(floor, time)) / first(floor, time)) * 100, 2)
           END         AS floor_change
from sale_changes
where time >= NOW() - INTERVAL '$period'
group by collection_id
order by floor_change DESC
limit ?

-- Spezifische Kettenabfrage:
select csc.*,
       cs.floor,
       cs.owners
from (select collection_id,
             SUM(volume),
             SUM(sales) AS total_sales,
             CASE
                 WHEN first(floor, time) = 0 THEN 0
          ELSE ROUND(((last (floor, time) - first (floor, time)) / first (floor, time)) * 100, 2)
          END AS floor_change
                           from $table
WHERE blockchain = ? and time >= NOW() - INTERVAL '$period'
group by collection_id
order by floor_change DESC
limit ?

Testen

Wir werden die Performance testen, indem wir jede Abfrage 10 Mal ausführen und Metriken zur Ausführungszeit sammeln. Meine lokale Umgebung:

  • MacBook M1 mit 32 GB RAM
  • TimescaleDB 2.17.2-pg17 in Docker
  • erhöhter Arbeitsspeicher für pg: shared_buffers=8GB, work_mem=8GB

Erster Versuch

Dies ist das Ergebnis der ersten Abfrage:

ZeitraumLimitMin Zeit (ms)Max Zeit (ms)Durchschnittszeit (ms)Medianzeit (ms)Std Dev (ms)
1 Stunde1007110577,374,59,7
1 Tag100738176,075,02,4
7 Tage100161192168,1166,08,5
30 Tage100515630536,3526,532,2

Dies ist das Ergebnis der zweiten Abfrage mit der kleinen Blockchain CELO:

ZeitraumLimitMin Zeit (ms)Max Zeit (ms)Durchschnittszeit (ms)Medianzeit (ms)Std Dev (ms)
1 Stunde1001310123,715,025,8
1 Tag100139923,414,525,2
7 Tage100149924,515,524,9
30 Tage1001610426,117,526,0

Dies ist das Ergebnis der zweiten Abfrage mit der großen Blockchain ETHEREUM:

ZeitraumLimitMin Zeit (ms)Max Zeit (ms)Durchschnittszeit (ms)Medianzeit (ms)Std Dev (ms)
1 Stunde1002711437,829,525,5
1 Tag1002711237,028,025,0
7 Tage1006617279,668,530,9
30 Tage100206382251,6220,061,9

Wir sehen, dass Abfragen für ein 30-Sekunden-Intervall nicht so schnell sind wie andere, außer bei der CELO-Blockchain. Dies ist absolut zu erwarten, da die Datenbank mehr Zeilen verarbeiten muss. Lassen Sie uns eine andere Konfiguration versuchen und sehen, ob wir die Performance verbessern können.

Zweiter Versuch

Schalten wir die Komprimierung ein. Wir werden collection_id als Komprimierungssegment verwenden, da es sinnvoll ist, diese Zahlen zusammenzuhalten.

sql
ALTER TABLE sale_changes SET (
    timescaledb.compress,
    timescaledb.compress_segmentby = 'collection_id'
    );
SELECT compress_chunk(c) from show_chunks('sale_changes') c;

Dies ist das Ergebnis der ersten Abfrage:

ZeitraumLimitMin Zeit (ms)Max Zeit (ms)Durchschnittszeit (ms)Medianzeit (ms)Std Dev (ms)
1 Stunde1002491091355,9261,5247,0
1 Tag100237286255,4251,015,4
7 Tage100247278259,4260,58,9
30 Tage100349412370,2358,521,0

Dies ist das Ergebnis der zweiten Abfrage mit der kleinen Blockchain CELO:

ZeitraumLimitMin Zeit (ms)Max Zeit (ms)Durchschnittszeit (ms)Medianzeit (ms)Std Dev (ms)
1 Stunde1001913133,121,032,8
1 Tag1001812832,921,532,1
7 Tage1002112933,322,031,9
30 Tage1002515142,125,036,9

Dies ist das Ergebnis der zweiten Abfrage mit der großen Blockchain ETHEREUM:

ZeitraumLimitMin Zeit (ms)Max Zeit (ms)Durchschnittszeit (ms)Medianzeit (ms)Std Dev (ms)
1 Stunde10090203115,799,034,6
1 Tag10094208110,599,532,8
7 Tage10095219111,198,536,3
30 Tage100149272170,7159,534,2

Komprimierte Tabelle gab uns schlechtere Ergebnisse... Selbst für Abfragen für kleine Intervalle, doch für 30 Tage im Fall aller Blockchains verbesserte sich das Ergebnis. Ich nehme an, dass das Halten von Zahlen zusammen nach collection_id hier half.

Dritter Versuch

Lassen Sie uns versuchen, das Fragment (Chunk) für unsere Hypertabelle zu vergrößern, da wir viel RAM haben. Laut der offiziellen Empfehlung ist es gut, wenn das Fragment 25% des Speichers einnehmen kann. Derzeit habe ich 24 GB RAM für Docker zugeteilt.

sql
select create_hypertable('sale_changes', 'time', chunk_time_interval => interval '7 day');
CREATE UNIQUE INDEX ON sale_changes(blockchain, id, time);
CREATE INDEX ON sale_changes(collection_id, time);
SELECT * FROM add_dimension('sale_changes', by_hash('blockchain', 16));

Dies ist das Ergebnis der ersten Abfrage:

ZeitraumLimitMin Zeit (ms)Max Zeit (ms)Durchschnittszeit (ms)Medianzeit (ms)Std Dev (ms)
1 Stunde100106117,111,014,8
1 Tag100101811,611,02,2
7 Tage1008711991,588,09,3
30 Tage100367514390,4373,542,9

Dies ist das Ergebnis der zweiten Abfrage mit der kleinen Blockchain CELO:

ZeitraumLimitMin Zeit (ms)Max Zeit (ms)Durchschnittszeit (ms)Medianzeit (ms)Std Dev (ms)
1 Stunde1002145,04,53,2
1 Tag1002125,04,02,7
7 Tage1003156,25,03,4
30 Tage1006157,67,02,6

Dies ist das Ergebnis der zweiten Abfrage mit der großen Blockchain ETHEREUM:

ZeitraumLimitMin Zeit (ms)Max Zeit (ms)Durchschnittszeit (ms)Medianzeit (ms)Std Dev (ms)
1 Stunde1003145,95,02,8
1 Tag1005136,05,02,4
7 Tage100324737,634,55,4
30 Tage100129140133,3132,03,2

Jetzt haben wir viel bessere Ergebnisse! Mir gefällt nur die Abfrage für alle Ketten über einen Zeitraum von 30 Tagen nicht. Die CELO/ETHEREUM-Abfragen sind wirklich schnell.

Vierter Versuch

Lassen Sie uns versuchen, die Komprimierung für einen 7-Tage-Bucket zu verwenden.

Dies ist das Ergebnis der ersten Abfrage:

ZeitraumLimitMin Zeit (ms)Max Zeit (ms)Durchschnittszeit (ms)Medianzeit (ms)Std Dev (ms)
1 Stunde1004715360,349,531,0
1 Tag100506254,854,04,4
7 Tage100627367,167,03,6
30 Tage100139182150,4145,012,5

Dies ist das Ergebnis der zweiten Abfrage mit der kleinen Blockchain CELO:

ZeitraumLimitMin Zeit (ms)Max Zeit (ms)Durchschnittszeit (ms)Medianzeit (ms)Std Dev (ms)
1 Stunde100183922,620,06,1
1 Tag100203625,123,05,7
7 Tage1006218,07,04,4
30 Tage1007219,48,04,0

Dies ist das Ergebnis der zweiten Abfrage mit der großen Blockchain ETHEREUM:

ZeitraumLimitMin Zeit (ms)Max Zeit (ms)Durchschnittszeit (ms)Medianzeit (ms)Std Dev (ms)
1 Stunde100213423,823,03,5
1 Tag100235929,024,510,5
7 Tage100315838,236,57,4
30 Tage100597464,563,54,2

Schließlich sind unsere Abfragen blitzschnell. Die langsamste ist immer noch die 30-Tage-Abfrage für alle Ketten, aber die Medianzeit beträgt <0,2s, also würde ich sagen, sie ist schnell für die Aggregation unter Millionen von Zeilen.

Diagramme

Fazit

Werfen wir einen Blick auf die Tabellengrößen mit der Abfrage:

sql
select 
    hypertable_name, 
    pg_size_pretty(hypertable_size(format('%I.%I', hypertable_schema, hypertable_name)::regclass))
from timescaledb_information.hypertables;
  • 1-Tage-Bucket-Tabelle: 2,4 GB
  • 1-Tage-Bucket-Tabelle mit Komprimierung: 2,6 GB
  • 7-Tage-Bucket-Tabelle: 2,2 GB
  • 7-Tage-Bucket-Tabelle mit Komprimierung: 0,3 GB

Es ist fast ein 10-facher Unterschied zwischen der ersten und der letzten Tabelle, was erklärt, warum die 7-Tage-Bucket-Tabelle mit Komprimierung so schnell ist. Die Konfiguration mit einem 7-Tage-Fragment und Komprimierung ist ein klarer Sieger.