Skip to content
Phase M1: Client (1, 8, 32, 64 workers)

Phase M1: Client (1, 8, 32, 64 workers)

Final pure-Go client (pkg/fdbgo, 267a41434a81723b07e7150869d702636a1b91a9) against Apple’s Go binding over libfdb_c 7.3.77 (CGo) across four closed-loop concurrency phases (M1w1 = 1 worker, M1w8 = 8 workers, M1w32 = 32 workers, M1w64 = 64 workers) over an isolated /24 private VPC against a 3-node double-redundancy ssd-2 FoundationDB 7.3.77 cluster (17 server processes, 9 storage servers). This is phase M1 of hcloud-multinode-vpc-20261014.

Design and scope

  • After M2skip completed, data distribution was re-enabled (datadistribution on), the cluster settled to moving_data.in_flight_bytes == 0 and in_queue_bytes == 0, check-roles passed, and a 1-case R1+R3+R4 diagnostic (M1w1/grv, 2 blocks, 32 ops) reconciled 1/1 clean before timed M1 execution began.
  • Sealed manifest: plans/client/manifest.json (SHA cbbcc5481cebb1df6823f6b817d7fb49f7c783242b15709a3d97e0c1565b1735), 6 counterbalanced blocks per workload (all permutations of final and cgo), executed in the preregistered phase order M1w1 → M1w64 → M1w8 → M1w32:
    • M1w1 (1 worker): 3,000 measured + 200 warmup tx/run (18,000 measured tx/arm/workload).
    • M1w8 (8 workers): 12,000 measured + 800 warmup tx/run (72,000 measured tx/arm/workload).
    • M1w32 (32 workers): 24,000 measured + 1,600 warmup tx/run (144,000 measured tx/arm/workload).
    • M1w64 (64 workers): 32,000 measured + 1,600 warmup tx/run (192,000 measured tx/arm/workload).
  • Claim scope: worker levels ran in a fixed phase sequence (M1w1, M1w64, M1w8, M1w32) with different per-level total transaction counts. Primary estimands are within-worker-level paired block ratios (final / CGo); cross-worker comparisons are descriptive only.
  • Metric scopes (same as single-node Phase A): Shutdown process CPU (E−A) is user+system CPU from after binary identity hashing through measurement, finalizer drain, database close, and native network stop, divided by measured transactions. Go bytes and mallocs per transaction count the Go heap only and exclude the unobserved native libfdb_c heap.
  • R4 reconciliation and retained adverse cases:
    • In M1w1, all 7/7 workloads completed and reconciled clean in R4.
    • In M1w8, M1w32, and M1w64, the first five workloads (grv, get100, get1000, range100, set100) completed and reconciled clean in R4 (5/7 clean per phase).
    • On the sixth workload (batchset50, 50 writes per transaction across 8, 32, or 64 concurrent workers), the heavy multi-worker write burst pushed the FDB transaction-log queue (cluster.qos.worst_queue_bytes_log_server) past the preregistered 100 MB post-run gate (113.7 MB in M1w8, 193.4 MB in M1w32, 141.9 MB in M1w64). R1 (client_campaign_mn.py) aborted batchset50 as environment-aborted (R4 verdict: contaminated), and the phase driver conservatively stopped the phase before batchset50x1000 (retained as unexecuted / R4 verdict: unverifiable, rather than a measured negative). No partial ratios are computed for those two cases at 8, 32, or 64 workers.

Results: Final pure-Go / CGo

Figure M1.1: Mean latency, pure-Go / CGoBelow 1.0× means pure-Go is faster.

Pure-Go has lower mean latency in 18 of 22 cells, down to 0.554×. CGo is faster in one: grv at 32 workers (1.041×). Three cells are inconclusive. There are no results for batchset50 and batchset50x1000 at 8–64 workers: the server's log queue exceeded the 100 MB gate during batchset50 and the run stopped.

0.35×0.35×0.5×0.5×0.7×0.7×1.0×1.0×1.4×1.4×2.0×2.0×← Pure-Go fasterCGo faster →M1w1 (1 worker — 7/7 clean)grvgrv: 0.579× [0.568, 0.591]0.579× [0.568, 0.591]get100get100: 0.702× [0.690, 0.712]0.702× [0.690, 0.712]get1000get1000: 0.673× [0.656, 0.691]0.673× [0.656, 0.691]range100range100: 0.633× [0.579, 0.672]0.633× [0.579, 0.672]set100set100: 0.886× [0.878, 0.894]0.886× [0.878, 0.894]batchset50batchset50: 0.920× [0.914, 0.926]0.920× [0.914, 0.926]batchset50x1000batchset50x1000: 0.954× [0.949, 0.958]0.954× [0.949, 0.958]M1w8 (8 workers — 5/7 clean)grvgrv: 0.996× [0.949, 1.038]0.996× [0.949, 1.038]get100get100: 0.888× [0.866, 0.907]0.888× [0.866, 0.907]get1000get1000: 0.942× [0.921, 0.961]0.942× [0.921, 0.961]range100range100: 0.868× [0.846, 0.891]0.868× [0.846, 0.891]set100set100: 0.980× [0.966, 0.993]0.980× [0.966, 0.993]batchset50N/A (omitted)batchset50x1000N/A (omitted)M1w32 (32 workers — 5/7 clean)grvgrv: 1.041× [1.007, 1.081]1.041× [1.007, 1.081]get100get100: 0.770× [0.750, 0.793]0.770× [0.750, 0.793]get1000get1000: 0.715× [0.688, 0.747]0.715× [0.688, 0.747]range100range100: 0.869× [0.753, 1.148]0.869× [0.753, 1.148]set100set100: 0.938× [0.931, 0.946]0.938× [0.931, 0.946]batchset50N/A (omitted)batchset50x1000N/A (omitted)M1w64 (64 workers — 5/7 clean)grvgrv: 1.029× [0.990, 1.069]1.029× [0.990, 1.069]get100get100: 0.676× [0.644, 0.710]0.676× [0.644, 0.710]get1000get1000: 0.632× [0.601, 0.669]0.632× [0.601, 0.669]range100range100: 0.554× [0.389, 0.673]0.554× [0.389, 0.673]set100set100: 0.887× [0.869, 0.897]0.887× [0.869, 0.897]batchset50N/A (omitted)batchset50x1000N/A (omitted)
95% CI below 1.0× 95% CI includes 1.0× (inconclusive) 95% CI above 1.0×
Figure M1.2: p99 latency, pure-Go / CGoBelow 1.0× means pure-Go has lower p99.

Pure-Go has lower p99 latency in 17 of 22 cells, down to 0.385× (get1000, 64 workers). CGo is lower in one: range100 at 32 workers (1.247×). Four cells are inconclusive.

0.25×0.25×0.35×0.35×0.5×0.5×0.7×0.7×1.0×1.0×1.4×1.4×2.0×2.0×← Pure-Go lower p99CGo lower p99 →M1w1 (1 worker — 7/7 clean)grvgrv: 0.498× [0.408, 0.601]0.498× [0.408, 0.601]get100get100: 0.865× [0.734, 1.037]0.865× [0.734, 1.037]get1000get1000: 0.705× [0.654, 0.779]0.705× [0.654, 0.779]range100range100: 0.715× [0.595, 0.861]0.715× [0.595, 0.861]set100set100: 0.794× [0.756, 0.837]0.794× [0.756, 0.837]batchset50batchset50: 0.913× [0.857, 0.975]0.913× [0.857, 0.975]batchset50x1000batchset50x1000: 0.957× [0.924, 0.990]0.957× [0.924, 0.990]M1w8 (8 workers — 5/7 clean)grvgrv: 1.101× [0.869, 1.457]1.101× [0.869, 1.457]get100get100: 0.815× [0.741, 0.882]0.815× [0.741, 0.882]get1000get1000: 0.931× [0.859, 0.993]0.931× [0.859, 0.993]range100range100: 0.925× [0.861, 0.987]0.925× [0.861, 0.987]set100set100: 0.916× [0.860, 0.976]0.916× [0.860, 0.976]batchset50N/A (omitted)batchset50x1000N/A (omitted)M1w32 (32 workers — 5/7 clean)grvgrv: 0.871× [0.754, 1.017]0.871× [0.754, 1.017]get100get100: 0.815× [0.679, 0.944]0.815× [0.679, 0.944]get1000get1000: 0.481× [0.423, 0.551]0.481× [0.423, 0.551]range100range100: 1.247× [1.048, 1.644]1.247× [1.048, 1.644]set100set100: 0.904× [0.856, 0.948]0.904× [0.856, 0.948]batchset50N/A (omitted)batchset50x1000N/A (omitted)M1w64 (64 workers — 5/7 clean)grvgrv: 0.960× [0.757, 1.190]0.960× [0.757, 1.190]get100get100: 0.679× [0.601, 0.776]0.679× [0.601, 0.776]get1000get1000: 0.385× [0.336, 0.438]0.385× [0.336, 0.438]range100range100: 0.732× [0.506, 0.912]0.732× [0.506, 0.912]set100set100: 0.872× [0.815, 0.927]0.872× [0.815, 0.927]batchset50N/A (omitted)batchset50x1000N/A (omitted)
95% CI below 1.0× 95% CI includes 1.0× (inconclusive) 95% CI above 1.0×
Figure M1.3: Process CPU per transaction, pure-Go / CGoBelow 1.0× means pure-Go uses less CPU. Includes startup, GC, and shutdown (E−A).

At 1 worker, pure-Go uses less CPU on all 7 workloads. At 8–64 workers it uses more CPU in 11 of the 12 cells other than grv, up to 2.162× (set100, 32 workers). grv still uses less.

0.5×0.5×0.7×0.7×1.0×1.0×1.4×1.4×2.0×2.0×2.5×2.5×← Pure-Go less CPUCGo less CPU →M1w1 (1 worker — 7/7 clean)grvgrv: 0.570× [0.551, 0.588]0.570× [0.551, 0.588]get100get100: 0.660× [0.648, 0.671]0.660× [0.648, 0.671]get1000get1000: 0.629× [0.603, 0.653]0.629× [0.603, 0.653]range100range100: 0.747× [0.731, 0.761]0.747× [0.731, 0.761]set100set100: 0.688× [0.677, 0.700]0.688× [0.677, 0.700]batchset50batchset50: 0.667× [0.659, 0.673]0.667× [0.659, 0.673]batchset50x1000batchset50x1000: 0.748× [0.729, 0.770]0.748× [0.729, 0.770]M1w8 (8 workers — 5/7 clean)grvgrv: 0.904× [0.878, 0.931]0.904× [0.878, 0.931]get100get100: 1.291× [1.264, 1.331]1.291× [1.264, 1.331]get1000get1000: 1.362× [1.342, 1.382]1.362× [1.342, 1.382]range100range100: 1.544× [1.476, 1.651]1.544× [1.476, 1.651]set100set100: 2.142× [2.123, 2.160]2.142× [2.123, 2.160]batchset50N/A (omitted)batchset50x1000N/A (omitted)M1w32 (32 workers — 5/7 clean)grvgrv: 0.765× [0.745, 0.779]0.765× [0.745, 0.779]get100get100: 1.368× [1.334, 1.411]1.368× [1.334, 1.411]get1000get1000: 1.537× [1.494, 1.575]1.537× [1.494, 1.575]range100range100: 1.723× [1.616, 1.921]1.723× [1.616, 1.921]set100set100: 2.162× [2.108, 2.216]2.162× [2.108, 2.216]batchset50N/A (omitted)batchset50x1000N/A (omitted)M1w64 (64 workers — 5/7 clean)grvgrv: 0.736× [0.721, 0.752]0.736× [0.721, 0.752]get100get100: 1.429× [1.385, 1.478]1.429× [1.385, 1.478]get1000get1000: 1.608× [1.509, 1.705]1.608× [1.509, 1.705]range100range100: 1.069× [0.923, 1.180]1.069× [0.923, 1.180]set100set100: 1.981× [1.935, 2.021]1.981× [1.935, 2.021]batchset50N/A (omitted)batchset50x1000N/A (omitted)
95% CI below 1.0× 95% CI includes 1.0× (inconclusive) 95% CI above 1.0×
Figure M1.4: Mean and p99 latency at 1 and 64 workersBar is the mean, whisker is p99, in µs per transaction.

Mean latency is about 1–7 ms per transaction for both clients at both worker counts. Each worker count ran as a separate phase, so compare within a count.

0 µs2000 µs4000 µs6000 µs8000 µs10000 µs12000 µsM1w1 grvM1w1 grv Pure-Go: mean 967.1 µs, p99 1477.4 µsM1w1 grv CGo: mean 1673.5 µs, p99 3133.0 µsGo 967 / p99 1477 µsCGo 1674 / p99 3133 µsM1w1 get100M1w1 get100 Pure-Go: mean 1557.2 µs, p99 3588.2 µsM1w1 get100 CGo: mean 2210.4 µs, p99 3967.9 µsGo 1557 / p99 3588 µsCGo 2210 / p99 3968 µsM1w1 get1000M1w1 get1000 Pure-Go: mean 1494.3 µs, p99 2642.5 µsM1w1 get1000 CGo: mean 2223.1 µs, p99 3861.8 µsGo 1494 / p99 2642 µsCGo 2223 / p99 3862 µsM1w1 range100M1w1 range100 Pure-Go: mean 1580.8 µs, p99 3000.0 µsM1w1 range100 CGo: mean 2408.4 µs, p99 4244.1 µsGo 1581 / p99 3000 µsCGo 2408 / p99 4244 µsM1w1 set100M1w1 set100 Pure-Go: mean 4234.5 µs, p99 6418.8 µsM1w1 set100 CGo: mean 4786.2 µs, p99 7991.7 µsGo 4234 / p99 6419 µsCGo 4786 / p99 7992 µsM1w1 batchset50M1w1 batchset50 Pure-Go: mean 5039.9 µs, p99 7509.2 µsM1w1 batchset50 CGo: mean 5481.1 µs, p99 8253.0 µsGo 5040 / p99 7509 µsCGo 5481 / p99 8253 µsM1w1 batchset50x1000M1w1 batchset50x1000 Pure-Go: mean 6241.4 µs, p99 9383.0 µsM1w1 batchset50x1000 CGo: mean 6577.5 µs, p99 10055.1 µsGo 6241 / p99 9383 µsCGo 6578 / p99 10055 µsM1w64 grvM1w64 grv Pure-Go: mean 2017.9 µs, p99 4237.9 µsM1w64 grv CGo: mean 1931.8 µs, p99 4015.5 µsGo 2018 / p99 4238 µsCGo 1932 / p99 4016 µsM1w64 get100M1w64 get100 Pure-Go: mean 2229.2 µs, p99 4538.2 µsM1w64 get100 CGo: mean 3271.6 µs, p99 6740.7 µsGo 2229 / p99 4538 µsCGo 3272 / p99 6741 µsM1w64 get1000M1w64 get1000 Pure-Go: mean 2409.9 µs, p99 4544.4 µsM1w64 get1000 CGo: mean 3758.9 µs, p99 11686.4 µsGo 2410 / p99 4544 µsCGo 3759 / p99 11686 µsM1w64 range100M1w64 range100 Pure-Go: mean 3033.1 µs, p99 7695.6 µsM1w64 range100 CGo: mean 4659.5 µs, p99 8704.0 µsGo 3033 / p99 7696 µsCGo 4660 / p99 8704 µsM1w64 set100M1w64 set100 Pure-Go: mean 5918.8 µs, p99 9591.9 µsM1w64 set100 CGo: mean 6659.4 µs, p99 10868.1 µsGo 5919 / p99 9592 µsCGo 6659 / p99 10868 µs
Pure-Go CGo (libfdb_c 7.3.77)

Geometric mean of six paired block ratios, with nominal 95% block-bootstrap intervals (20,000 resamples, preregistered seed 492817; initial exploratory analysis with seed 391706 is retained in the campaign archive). Below 1 means pure-Go is faster or uses less CPU.

M1w1 (1 worker, 18,000 tx/arm per workload — 7/7 clean)

WorkloadMean latencyp50 latencyp99 latencyShutdown process CPU (E−A) per tx
grv0.579 [0.568, 0.591]0.580 [0.571, 0.591]0.498 [0.408, 0.601]0.570 [0.551, 0.588]
get1000.702 [0.690, 0.712]0.698 [0.691, 0.703]0.865 [0.734, 1.037]0.660 [0.648, 0.671]
get10000.673 [0.656, 0.691]0.662 [0.652, 0.673]0.705 [0.654, 0.779]0.629 [0.603, 0.653]
range1000.633 [0.579, 0.672]0.627 [0.567, 0.665]0.715 [0.595, 0.861]0.747 [0.731, 0.761]
set1000.886 [0.878, 0.894]0.894 [0.882, 0.906]0.794 [0.756, 0.837]0.688 [0.677, 0.700]
batchset500.920 [0.914, 0.926]0.921 [0.914, 0.928]0.913 [0.857, 0.975]0.667 [0.659, 0.673]
batchset50x10000.954 [0.949, 0.958]0.953 [0.951, 0.956]0.957 [0.924, 0.990]0.748 [0.729, 0.770]

M1w8 (8 workers, 72,000 tx/arm per workload — 5/7 clean)

WorkloadMean latencyp50 latencyp99 latencyShutdown process CPU (E−A) per tx
grv0.996 [0.949, 1.038]1.137 [1.116, 1.159]1.101 [0.869, 1.457]0.904 [0.878, 0.931]
get1000.888 [0.866, 0.907]0.889 [0.874, 0.904]0.815 [0.741, 0.882]1.291 [1.264, 1.331]
get10000.942 [0.921, 0.961]0.946 [0.923, 0.971]0.931 [0.859, 0.993]1.362 [1.342, 1.382]
range1000.868 [0.846, 0.891]0.882 [0.856, 0.909]0.925 [0.861, 0.987]1.544 [1.476, 1.651]
set1000.980 [0.966, 0.993]0.998 [0.986, 1.009]0.916 [0.860, 0.976]2.142 [2.123, 2.160]
batchset50N/A (R4 contaminated: log queue 113.7 MB > 100 MB gate)———
batchset50x1000N/A (R4 unverifiable: unexecuted after batchset50 abort)———

M1w32 (32 workers, 144,000 tx/arm per workload — 5/7 clean)

WorkloadMean latencyp50 latencyp99 latencyShutdown process CPU (E−A) per tx
grv1.041 [1.007, 1.081]1.143 [1.112, 1.177]0.871 [0.754, 1.017]0.765 [0.745, 0.779]
get1000.770 [0.750, 0.793]0.777 [0.763, 0.793]0.815 [0.679, 0.944]1.368 [1.334, 1.411]
get10000.715 [0.688, 0.747]0.739 [0.719, 0.764]0.481 [0.423, 0.551]1.537 [1.494, 1.575]
range1000.869 [0.753, 1.148]0.851 [0.736, 1.130]1.247 [1.048, 1.644]1.723 [1.616, 1.921]
set1000.938 [0.931, 0.946]0.959 [0.954, 0.964]0.904 [0.856, 0.948]2.162 [2.108, 2.216]
batchset50N/A (R4 contaminated: log queue 193.4 MB > 100 MB gate)———
batchset50x1000N/A (R4 unverifiable: unexecuted after batchset50 abort)———

M1w64 (64 workers, 192,000 tx/arm per workload — 5/7 clean)

WorkloadMean latencyp50 latencyp99 latencyShutdown process CPU (E−A) per tx
grv1.029 [0.990, 1.069]1.090 [1.064, 1.121]0.960 [0.757, 1.190]0.736 [0.721, 0.752]
get1000.676 [0.644, 0.710]0.682 [0.649, 0.721]0.679 [0.601, 0.776]1.429 [1.385, 1.478]
get10000.632 [0.601, 0.669]0.665 [0.628, 0.710]0.385 [0.336, 0.438]1.608 [1.509, 1.705]
range1000.554 [0.389, 0.673]0.532 [0.369, 0.653]0.732 [0.506, 0.912]1.069 [0.923, 1.180]
set1000.887 [0.869, 0.897]0.884 [0.871, 0.893]0.872 [0.815, 0.927]1.981 [1.935, 2.021]
batchset50N/A (R4 contaminated: log queue 141.9 MB > 100 MB gate)———
batchset50x1000N/A (R4 unverifiable: unexecuted after batchset50 abort)———

Absolute values

Medians of six run-level statistics per arm.

PhaseWorkloadArmTransactionsMean µsp50 µsp99 µsShutdown CPU (E−A) µs/txGo bytes/txGo mallocs/tx
M1w1grvfinal pure-Go18000967.1945.81477.4260.4383038.1
M1w1grvCGo180001673.51629.83133.0451.042410.0
M1w1get100final pure-Go180001557.21509.63588.2496.4635869.2
M1w1get100CGo180002210.42149.33967.9749.866416.0
M1w1get1000final pure-Go180001494.31422.32642.5503.1822769.2
M1w1get1000CGo180002223.12162.83861.8799.3158316.0
M1w1range100final pure-Go180001580.81528.43000.0690.54972775.7
M1w1range100CGo180002408.42318.44244.1923.024078224.0
M1w1set100final pure-Go180004234.54144.46418.8569.310226101.4
M1w1set100CGo180004786.24641.57991.7833.144010.0
M1w1batchset50final pure-Go180005039.94927.27509.2892.965571321.4
M1w1batchset50CGo180005481.15334.28253.01333.4161859.0
M1w1batchset50x1000final pure-Go180006241.46140.39383.01290.8209046322.3
M1w1batchset50x1000CGo180006577.56438.410055.11737.1161659.0
M1w8grvfinal pure-Go720001693.11861.03502.2115.9354935.8
M1w8grvCGo720001703.91641.63292.9127.542410.0
M1w8get100final pure-Go720002083.42008.63661.8242.1627967.7
M1w8get100CGo720002350.62251.14492.1186.366416.0
M1w8get1000final pure-Go720002201.12128.34164.4248.8814167.7
M1w8get1000CGo720002349.02268.84509.2182.8157716.0
M1w8range100final pure-Go720002539.32518.74495.4438.44961574.5
M1w8range100CGo720002946.12858.34943.5291.024073224.0
M1w8set100final pure-Go720005007.44978.97292.3421.510250101.1
M1w8set100CGo720005087.44989.97976.9197.244110.0
M1w32grvfinal pure-Go1440001821.81911.83283.863.5347434.6
M1w32grvCGo1440001781.21681.23720.082.942410.0
M1w32get100final pure-Go1440002153.02083.44115.7153.5631666.7
M1w32get100CGo1440002803.62682.85034.3113.566416.0
M1w32get1000final pure-Go1440002210.52145.13875.4164.0817266.7
M1w32get1000CGo1440003029.12887.87994.1106.0157616.0
M1w32range100final pure-Go1440002884.32722.06618.7353.34954773.2
M1w32range100CGo1440003792.23672.25977.7215.224072224.0
M1w32set100final pure-Go1440005497.25474.38003.5245.21043899.2
M1w32set100CGo1440005858.05717.69033.9113.244010.0
M1w64grvfinal pure-Go1920002017.91995.64237.954.3346434.3
M1w64grvCGo1920001931.81802.14015.574.342510.0
M1w64get100final pure-Go1920002229.22136.34538.2132.9631566.5
M1w64get100CGo1920003271.63173.46740.793.366416.0
M1w64get1000final pure-Go1920002409.92308.94544.4142.5816866.5
M1w64get1000CGo1920003758.93459.811686.487.4157616.0
M1w64range100final pure-Go1920003033.12795.87695.6207.84953272.8
M1w64range100CGo1920004659.54440.88704.0179.124072224.0
M1w64set100final pure-Go1920005918.85795.69591.9203.41046298.8
M1w64set100CGo1920006659.46517.010868.1102.944010.0