PostgreSQL-Cache-Benchmarks: 3.1.0

Diese Messungen vergleichen pg_local_cache RESP MGET, Valkey und vorbereitetes SQL auf zwei VMs in einem privaten Netzwerk. Alle folgenden Ergebnisse stammen vom finalen 3.1.0-Build c431bcc. Mediane wiederholter Läufe gelten für Lese- und Schreibkostentests; beide gepinnten Ein-Kern-Fälle mit Valkey io-threads=1 haben zwei Läufe, die übrigen gepinnten Fälle fünf. Gemischte Tests, 120-Sekunden-Stale-Probes und einstündiger Soak-Test sind Einzelläufe. Diese Lastmessungen sind keine Zusage für Produktionskapazität.

Ergebnisse des finalen Builds

Lesen pro gepinntem Server-vCPU-Paar

Der Go/pgx-Client verwendete 256 Clients und einen Schlüssel pro MGET. Jeder Fall lief 20 Sekunden. Die beiden Ein-Kern-Fälle mit Valkey io-threads=1 liefen zweimal; alle übrigen gepinnten Fälle fünfmal. PostgreSQL und Valkey waren auf N vCPU-Paare (2N vCPU) gepinnt. Valkey lief mit io-threads=2N und io-threads=1; vorbereitetes SQL verwendete dieselben Schlüssel.

vCPU-Paare Schlüssel pg_local_cache Anfragen/s (p99 ms, vCPU) Valkey io-threads=2N (Anfragen/s, p99 ms, vCPU) Valkey io-threads=1 (Anfragen/s, p99 ms, vCPU) Vorbereitetes SQL (Anfragen/s, p99 ms, vCPU)
1 Hot 209,696 (2.46, 1.99) 91,478 (3.24, 2.00) 180,141 (2.59, 0.85) 23,200 (24.90, 2.00)
1 100k zufällig 195,950 (2.65, 2.00) 88,672 (3.38, 2.00) 175,853 (2.65, 0.92) 22,433 (25.95, 2.00)
2 Hot 336,691 (2.02, 3.78) 266,340 (1.69, 4.00) 162,230 (2.85, 0.66) 46,386 (16.38, 4.00)
2 100k zufällig 326,125 (1.98, 3.83) 267,826 (1.72, 4.00) 160,220 (2.92, 0.69) 45,211 (17.56, 4.00)
4 Hot 360,968 (2.33, 5.24) 357,161 (2.20, 7.95) 160,633 (2.85, 0.66) 88,604 (6.75, 8.00)
4 100k zufällig 353,941 (2.33, 5.27) 351,344 (2.20, 7.95) 159,414 (2.92, 0.68) 86,790 (6.88, 8.00)

Bei einem Kern war Valkey mit einem Thread die stärkste Valkey-Konfiguration. Bei zwei Kernen war io-threads=4 am stärksten; Valkey hatte trotz geringeren Durchsatzes ein niedrigeres p99. Bei vier Kernen erreichten pg_local_cache und io-threads=8 nahezu denselben clientbegrenzten Durchsatz, bei geringerer Server-CPU für pg_local_cache.

Raw runs: Valkey io-threads=2N, Valkey io-threads=1.

Lesen mit allen 16 vCPUs

Diese Läufe waren nicht gepinnt und wurden dreimal wiederholt. Der Load-Client nutzte in clientbegrenzten Fällen etwa 7–13 CPU-Kerne. Mit Valkey io-threads=8 lagen die beobachteten Median-Durchsatzunterschiede für MGET 16/64 zwischen etwa −2,15 % und +1,26 %; bei allen vier 64-Schlüsselvergleichen überlappten sich die Bereiche nicht. Diese Stichproben belegen keine statistische Gleichwertigkeit. Bei zufälligen MGET 16 mit 256 Clients lag pg_local_cache mit io-threads=1 bei 116.130 gegenüber 97.489 Reads/s um 19,1 % vorn. Breite MGETs waren client-/netzwerkbegrenzt; für io-threads=1 gibt es kein Ergebnis mit 64 Schlüsseln.

Schlüssel MGET-Schlüssel Clients pg_local_cache (Anfragen/s, p99 ms, vCPU) Valkey io-threads=8 (Anfragen/s, p99 ms, vCPU) Valkey io-threads=1 (Anfragen/s, p99 ms, vCPU) Vorbereitetes SQL (Anfragen/s, p99 ms, vCPU)
Hot 1 64 217,724 (0.70, 2.89) 206,634 (0.70, 7.85) 167,929 (0.71, 0.66) 132,433 (0.96, 11.32)
Hot 1 256 372,683 (2.39, 4.21) 365,947 (2.33, 7.92) 161,455 (2.92, 0.68) 173,571 (4.26, 13.14)
Hot 16 64 82,858 (3.38, 3.13) 83,098 (3.38, 6.41) 79,179 (3.38, 0.69) 73,512 (2.05, 8.78)
Hot 16 256 122,496 (9.83, 4.07) 122,817 (9.83, 7.42) 111,790 (8.06, 0.83) 100,966 (6.36, 11.49)
Hot 64 64 30,241 (8.78, 3.40) 30,845 (9.04, 3.57) — 27,790 (6.88, 9.05)
Hot 64 256 39,531 (43.52, 4.26) 39,995 (47.71, 5.66) — 34,519 (30.67, 12.23)
100k zufällig 1 64 215,310 (0.70, 2.89) 205,081 (0.70, 7.85) 162,778 (0.73, 0.67) 131,563 (0.97, 11.34)
100k zufällig 1 256 366,860 (2.39, 4.31) 360,894 (2.33, 7.92) 158,444 (2.98, 0.68) 169,884 (4.26, 13.19)
100k zufällig 16 64 79,505 (3.51, 3.45) 79,556 (3.44, 6.68) 74,270 (3.38, 0.79) 69,262 (2.13, 9.12)
100k zufällig 16 256 116,130 (10.35, 4.42) 114,683 (9.83, 7.53) 97,489 (7.67, 0.95) 91,572 (7.01, 11.96)
100k zufällig 64 64 29,000 (8.78, 3.81) 29,464 (9.04, 4.61) — 26,169 (7.14, 9.39)
100k zufällig 64 256 37,356 (43.52, 4.93) 38,175 (40.37, 7.58) — 32,756 (31.20, 13.03)

Bei zufälligen Schlüsseln, MGET 64 und 256 Clients betrug p99 für pg_local_cache 43,52 ms gegenüber 40,37 ms für Valkey. Vorbereitetes SQL erreichte bei gepinnten Einzel-Key-Tests deutlich weniger Durchsatz; bei größeren Batches unterscheiden sich Durchsatz und Latenz.

Raw runs: Valkey io-threads=8, Valkey io-threads=1.

Schreibkosten

Drei Läufe zu je 15 Sekunden vergleichen einfache Tabellentransaktionen, Transaktionen über pg_local_cache und einfache Transaktionen mit anschließendem Valkey DEL. Prozentwerte beziehen sich auf den passenden Fall mit einfachen Tabellentransaktionen.

Vorgang synchronous_commit Clients Einfache Tabelle tx/s (DB µs/tx) pg_local_cache tx/s (DB µs/tx, Änderung) Einfache Tabelle + Valkey DEL tx/s (DB µs/tx, Änderung)
Update on 32 46,747 (109) 45,065 (119, −3.6%) 33,234 (204, −28.9%)
Update on 64 63,479 (108) 61,027 (121, −3.9%) 37,659 (238, −40.7%)
Update off 32 119,156 (75) 110,987 (87, −6.9%) 53,374 (158, −55.2%)
Update off 64 148,358 (73) 136,194 (83, −8.2%) 53,853 (189, −63.7%)
Insert on 32 48,831 (97) 47,020 (105, −3.7%) 34,863 (184, −28.6%)
Insert on 64 65,688 (97) 63,011 (108, −4.1%) 39,642 (218, −39.7%)
Insert off 32 123,679 (63) 117,868 (72, −4.7%) 58,168 (135, −53.0%)
Insert off 64 138,454 (68) 130,805 (77, −5.5%) 58,361 (165, −57.8%)

Bei UPDATE lag pg_local_cache 3,6–8,2 % unter einfachen Schreibvorgängen; bei INSERT betrug der Aufwand 3,7–5,5 %. Einfaches UPDATE plus Valkey DEL lag beim UPDATE-Durchsatz 28,9–63,7 % darunter; beim INSERT-Vergleich waren es 28,6–57,8 %.

Raw runs: write overhead.

Lesen während Schreibvorgängen

Jeder Fall lief einmal 30 Sekunden mit 64 Lesern über 60k Zeilen; Writer verwendeten synchronous_commit=off. Valkey nutzte cache-aside (SQL-Schreibvorgang, danach DEL) mit io-threads=8. „Veraltet“ ist die Sentinel-Prüfung am Laufende; n/a bedeutet, dass der Stack keine Cache-Einträge zum Prüfen hat.

Stack Schlüsselverteilung Writer Lesen/s Lese-p99 ms Trefferquote Schreiben/s Veraltete Einträge
pg_local_cache Uniform Keine 214,508 0.71 1.000 0 0
pg_local_cache Uniform Update 10,000/s 195,384 0.86 0.948 10,000 0
pg_local_cache Uniform Update 30,000/s 161,919 1.04 0.837 29,999 0
pg_local_cache Uniform Update unlimited 82,640 1.69 0.432 111,013 0
pg_local_cache Zipf Keine 216,297 0.70 1.000 0 0
pg_local_cache Zipf Update 10,000/s 198,454 0.86 0.964 10,000 0
pg_local_cache Zipf Update 30,000/s 168,875 1.01 0.923 30,000 0
pg_local_cache Zipf Update unlimited 96,297 1.46 0.834 113,505 0
Valkey cache-aside Uniform Keine 223,537 0.61 1.000 0 0
Valkey cache-aside Uniform Update 10,000/s 156,983 1.92 0.936 10,000 0
Valkey cache-aside Uniform Update 30,000/s 42,294 7.14 0.577 29,998 0
Valkey cache-aside Uniform Update unlimited 29,692 8.78 0.444 38,481 1
Valkey cache-aside Zipf Keine 222,575 0.63 0.999 0 0
Valkey cache-aside Zipf Update 10,000/s 168,911 1.65 0.960 10,000 0
Valkey cache-aside Zipf Update 30,000/s 75,642 4.78 0.887 29,999 0
Valkey cache-aside Zipf Update unlimited 48,993 6.88 0.852 40,735 0
Prepared SQL Uniform Keine 129,714 0.97 — 0 n/a
Prepared SQL Uniform Update 10,000/s 121,567 1.13 — 10,000 n/a
Prepared SQL Uniform Update 30,000/s 106,949 1.33 — 30,000 n/a
Prepared SQL Uniform Update unlimited 77,574 2.02 — 93,955 n/a
Prepared SQL Zipf Keine 129,937 0.97 — 0 n/a
Prepared SQL Zipf Update 10,000/s 122,182 1.13 — 10,000 n/a
Prepared SQL Zipf Update 30,000/s 107,852 1.33 — 29,999 n/a
Prepared SQL Zipf Update unlimited 78,601 2.08 — 95,199 n/a
pg_local_cache Uniform Insert unlimited 117,531 1.20 1.000 114,066 0
Valkey cache-aside Uniform Insert unlimited 70,302 4.92 1.000 46,622 0
Prepared SQL Uniform Insert unlimited 84,294 1.98 — 95,094 n/a

pg_local_cache beendete alle aufgeführten Mischläufe ohne veraltete Einträge. Valkey ließ nur in einem UPDATE-Fall einen veralteten Sentinel-Eintrag zurück: Uniform-Schlüssel bei unbegrenzter Update-Rate. Bei 30k UPDATE/s und Uniform-Schlüsseln lagen die Leseraten bei 161.919/s für pg_local_cache, 42.294/s für Valkey cache-aside und 106.949/s für vorbereitetes SQL.

Raw runs: mixed reads and writes.

120-Sekunden-Prüfungen auf veraltete Einträge

Jeder Probe-Lauf erfolgte einmal mit 64 Lesern und 64 unbegrenzten UPDATE-Writern, danach folgte die vollständige Prüfung veralteter Einträge. Valkey verwendete io-threads=8.

Stack Schlüsselverteilung Lesen/s Lese-p99 ms Schreiben/s Veraltete Einträge Hinweis
pg_local_cache Uniform 82,565 1.687552 108,580 0 —
pg_local_cache Zipf 0 n/a 101,797 0 Siehe Hinweis unten.
Valkey cache-aside Uniform 30,210 8.781824 36,508 2 —
Valkey cache-aside Zipf 28,568 8.781824 35,789 1 —

Der pg_local_cache-Zipf-Leser bestand während paralleler Schreibvorgänge die Äquivalenzprüfung beim Start nicht (Harness-Problem) und meldete daher null Lesevorgänge. Die Prüfung lief trotzdem und fand null veraltete Einträge.

Raw runs: 120-second stale probes.

Einstündiger Soak-Test

Einzellauf: MGET 16 über 100k zufällige Schlüssel, 64 Clients, plus 20.000 UPDATE/s. RssAnon der RESP-Worker wurde minütlich erfasst.

Raw runs: one-hour soak.

Teststand und Methodik

Laut lscpu meldete jede VM 16 vCPU in 2 Sockets × 4 Kernen pro Socket × 2 Threads pro Kern (VM-Topologie), 62 GB RAM und Debian 13 mit Kernel 6.12.111+deb13-amd64. Die DB-VM nutzte PostgreSQL 16.15 (max_connections=1000, shared_buffers=1048576) und pg_local_cache 3.1.0 c431bcc (aktiviert, memory_budget_mb=1024, workers=8). Die Tabelle hatte 100.000 JSON-Zeilen mit durchschnittlich 166 Byte. Valkey 8.1.1 nutzte io-threads=8 bei Schreibkosten-, Misch- und Stale-Probe-Läufen. Vom Load- zur DB-VM maß iperf3 10,4 Gbit/s; ping meldete RTT min/avg/max 0,090/0,143/0,871 ms. Der Go/pgx-Client lief auf der zweiten VM. Leseläufe dauerten 20 Sekunden; Schreibtests 15 Sekunden, gemischte Läufe 30 Sekunden, Stale-Probes 120 Sekunden und der Soak-Test 3.600 Sekunden.

Die gepinnte Matrix verwendete 256 Clients; jeder Fall hatte fünf Läufe außer den beiden Ein-Kern-Fällen mit Valkey io-threads=1, die zwei Läufe hatten. All-Core-Lesematrix und Schreibkosten hatten je drei Läufe; gemischte Tests, Stale-Probes und Soak-Test je einen. NIC-Interrupts waren nicht gepinnt. Alle Tabellen verwenden den finalen Build c431bcc. Durchsatz ist Anfragen/s, sofern nicht Transaktionen/s angegeben sind; Latenz ist p99, CPU ist Server-vCPU.

Umgebung und JSONL-Rohdaten: Umgebung, Standnachweis, gepinntes Lesen, gepinntes Lesen mit Valkey io-threads=1, Lesen auf allen Kernen, alle Kerne, Valkey io-threads=1, Schreibkosten, Lesen während Schreibvorgängen, 120-Sekunden-Stale-Probes und einstündiger Soak-Test.

Mit bench/ reproduzieren

Benötigt werden Python 3 und SSH-Aliase für Datenbank- und Load-VM. Die Datenbank-VM braucht PostgreSQL 16, Valkey, mpstat, sar, ip und systemd; die Load-VM den kompilierten Go-Client unter /root/bench-client. Siehe bench/README.md und den Go/pgx-Client.

Standardmatrix für Reads: 20 Sekunden, drei Wiederholungen, 1/16/64 Schlüssel, 16/64/256 Clients, Hot-Set und 100k Schlüsselraum:

python3 bench/run_bench.py reads.jsonl

Beispiel für ein gepinntes vCPU-Paar mit 256 Clients und fünf Wiederholungen; 0-1 durch die logischen CPU-IDs dieses vCPU-Paars ersetzen:

PGLC_SERVER_CPUS=0-1 REPEATS=5 BATCHES=1 CLIENTS=256 KEY_SPACES=0,100000 python3 bench/run_bench.py pinned.jsonl

Der Runner setzt AllowedCPUs für PostgreSQL und Valkey und hebt die Begrenzung danach auf. Er erfasst Server-CPU, Latenz, Netzwerk-RX/TX und Client-CPU. Valkeys io-threads vor dem Lauf passend zur gewünschten Konfiguration setzen.

Schreibkosten und 30-Sekunden-Mischlast starten:

REPEATS=3 WRITE_SECONDS=15 WRITE_CLIENTS=32,64 python3 bench/write_bench.py writes.jsonl
REPEATS=1 MIXED_SECONDS=30 MIXED_RATES=none,10000,30000,unlimited MIXED_KEY_DISTS=uniform,zipf python3 bench/write_bench.py mixed.jsonl

Die Skripte hängen JSONL-Zeilen an und behalten abgeschlossene Samples bei Fehlern. Der Schreibtest misst DB-CPU pro Transaktion; der Mischtest enthält außerdem Leselatenz, Treffer/Fehlschläge, Schreibrate und die abschließende Prüfung veralteter Einträge.

Historische 2.x-Ergebnisse auf dem MacBook

Diese älteren JSON-Dateien sind historische Apple-M3-Max-Messungen mit PostgreSQL 16; Umgebung und Client-Topologie unterscheiden sich vom Zwei-VM-Test oben. Sie enthalten die in 3.0.0 entfernte SQL-mget-API aus 2.x. Die Dokumentation zu 2.0.4 beschreibt diese API; aktuelle Cache-Reads verwenden RESP MGET.

Aktualisiert .