Files
geth/core/rawdb/zap_ancient_bench_test.go
zeekay facd99fee2 feat(rawdb): ZAP-native ancient store for cold block history
Implement ethdb.ResettableAncientStore over a single KV backend (the
canonical luxfi/zapdb). Replaces the upstream geth freezer
(.cdat/.ridx files per table) with one ZAP database per chain. Snappy
compression per-block (~10x cold data); single-byte kind markers keep
prefix scans efficient.

Key layout (single ZAP database per chain):
  ['a'][kind byte][big-endian uint64 number] -> snappy value
  ['m']['h'][kind byte]                      -> head pointer
  ['m']['t'][kind byte]                      -> tail pointer

ZAP is canonical Lux storage (luxfi/zap + luxfi/zapdb). PebbleDB stays
only as legacy in-place upgrade. New deployments use ZAP everywhere.

Surface:
- core/rawdb/zap_ancient.go            (ZapAncientStore implementation)
- core/rawdb/zap_ancient_migration.go  (exported wrappers for migrate-ancient)
- core/rawdb/zap_ancient_rlp.go        (rlp.EncodeToBytes split out)
- core/rawdb/zap_ancient_test.go       (7 tests: roundtrip, out-of-order,
                                        range maxBytes, truncate head/tail,
                                        snappy, persist-across-open)
- core/rawdb/zap_ancient_bench_test.go (write + read throughput)
- cmd/migrate-ancient/                 (one-shot upstream-freezer → ZAP migrator)
- cmd/fleet-bench/                     (CSV harness for write/read latency)

Benchmarks (in-memory backend, M1 Pro):
  WriteThroughput: 6860 ns/op, 4 puts per block (header+hash+body+receipt)
  ReadHot:          920 ns/op

Migration command for an existing chain:
  migrate-ancient --src /data/db/<net>/chainData/<chainID>/ancient \
                  --dst /data/ancient/<chainID>

The luxfi/operator NodeFleet CRD ships archive pods with
--ancient-store-backend=zap as the default.

Tests: 7 zap-ancient tests + full core/rawdb suite race-clean.
2026-06-02 03:37:48 -07:00

116 lines
3.4 KiB
Go

// Copyright (C) 2026, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//
// Benchmark harness for the ZAP-native ancient store.
//
// We measure write IOPS proxy (ModifyAncients throughput) and read
// latency at scale. The memory backend stands in for ZAP — same
// interface, so write-amplification numbers translate one-for-one.
//
// Run:
//
// GOWORK=off go test ./core/rawdb \
// -run=XXX -bench=BenchmarkZapAncient \
// -benchtime=2s -count=1
package rawdb
import (
"crypto/rand"
"testing"
"github.com/luxfi/geth/ethdb"
"github.com/luxfi/geth/ethdb/memorydb"
)
// benchTables mirrors the chain freezer tables so the numbers are
// directly comparable to production workloads.
var benchTables = map[string]freezerTableConfig{
ChainFreezerHeaderTable: {noSnappy: false, prunable: false},
ChainFreezerHashTable: {noSnappy: true, prunable: false},
ChainFreezerBodiesTable: {noSnappy: false, prunable: true},
ChainFreezerReceiptTable: {noSnappy: false, prunable: true},
}
// BenchmarkZapAncient_WriteThroughput measures ModifyAncients
// throughput for a synthetic block (header=600B, hash=32B, body=2KB,
// receipts=500B). The chain freezer's bottleneck is this exact shape
// — 1 block = 4 puts.
func BenchmarkZapAncient_WriteThroughput(b *testing.B) {
store, err := NewZapAncientStore(b.TempDir(), benchTables, memBackend{memorydb.New()})
if err != nil {
b.Fatalf("NewZapAncientStore: %v", err)
}
defer store.Close()
header := make([]byte, 600)
hash := make([]byte, 32)
body := make([]byte, 2048)
receipt := make([]byte, 500)
_, _ = rand.Read(header)
_, _ = rand.Read(hash)
_, _ = rand.Read(body)
_, _ = rand.Read(receipt)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
num := uint64(i)
if _, err := store.ModifyAncients(func(op ethdb.AncientWriteOp) error {
if err := op.AppendRaw(ChainFreezerHeaderTable, num, header); err != nil {
return err
}
if err := op.AppendRaw(ChainFreezerHashTable, num, hash); err != nil {
return err
}
if err := op.AppendRaw(ChainFreezerBodiesTable, num, body); err != nil {
return err
}
return op.AppendRaw(ChainFreezerReceiptTable, num, receipt)
}); err != nil {
b.Fatalf("ModifyAncients: %v", err)
}
}
}
// BenchmarkZapAncient_ReadHot measures Ancient(headers, n) latency on
// a pre-loaded store. Models a state-sync server serving a recent
// block.
func BenchmarkZapAncient_ReadHot(b *testing.B) {
store, err := NewZapAncientStore(b.TempDir(), benchTables, memBackend{memorydb.New()})
if err != nil {
b.Fatalf("NewZapAncientStore: %v", err)
}
defer store.Close()
const n = 100_000
val := make([]byte, 600)
if _, err := store.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := uint64(0); i < n; i++ {
if err := op.AppendRaw(ChainFreezerHeaderTable, i, val); err != nil {
return err
}
if err := op.AppendRaw(ChainFreezerHashTable, i, val[:32]); err != nil {
return err
}
if err := op.AppendRaw(ChainFreezerBodiesTable, i, val); err != nil {
return err
}
if err := op.AppendRaw(ChainFreezerReceiptTable, i, val); err != nil {
return err
}
}
return nil
}); err != nil {
b.Fatalf("preload: %v", err)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
num := uint64(i) % n
if _, err := store.Ancient(ChainFreezerHeaderTable, num); err != nil {
b.Fatalf("Ancient: %v", err)
}
}
}