Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 43 additions & 49 deletions cmd/stellar-rpc/internal/fullhistory/storage/rocksdb/rocksdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -586,31 +586,52 @@ func (s *Store) constructAndOpen() error {
return nil
}

// applyTuning splits configuration into wrapper-pinned values
// (every CF, unconditional), per-facade Tuning fields (applied
// only when non-zero), and per-CF overrides (applied last so they
// win over the pinned defaults). BloomFilterBitsPerKey == 0 is
// the documented "no bloom filter" sentinel.
// applyTuning splits configuration into wrapper-pinned values (every CF,
// unconditional), per-CF overrides (applied after the pinned defaults so
// they win), and DB-wide Tuning fields (applied to the shared Options).
// BloomFilterBitsPerKey == 0 is the documented "no bloom filter" sentinel.
func (s *Store) applyTuning(opts *grocksdb.Options, cfNames []string, cfOpts []*grocksdb.Options) {
t := s.cfg.Tuning
for i, o := range cfOpts {
applyPinnedCFOptions(o)
applyCFTuning(o, t)
applyCFOverride(o, s.cfg.PerCFOptions[cfNames[i]])
}
applyDBTuning(opts, t)
s.applySharedTableOptions(cfNames, cfOpts, t)
}

// applyCFOverride applies the per-CF Compression override. BlockSize
// is applied inside applySharedTableOptions when the BBTO is built.
func applyCFOverride(o *grocksdb.Options, override CFOptions) {
// applyCFOverride applies the per-CF memtable/compaction/compression knobs.
// Each is applied only when non-zero, leaving the pinned default otherwise.
// BlockSize and BloomFilterBitsPerKey are applied inside
// applySharedTableOptions when the BBTO is built.
func applyCFOverride(o *grocksdb.Options, c CFOptions) {
// CompressionType is an int alias; NoCompression (the pinned
// default) is 0. A zero-value override is therefore a no-op and
// leaves the pinned NoCompression in place. Non-zero values
// (Snappy, ZSTD, ...) replace it.
if override.Compression != grocksdb.NoCompression {
o.SetCompression(override.Compression)
if c.Compression != grocksdb.NoCompression {
o.SetCompression(c.Compression)
}
if c.WriteBufferMB > 0 {
o.SetWriteBufferSize(uint64(c.WriteBufferMB) << 20)
}
if c.MaxWriteBufferNumber > 0 {
o.SetMaxWriteBufferNumber(c.MaxWriteBufferNumber)
}
if c.Level0FileNumCompactionTrigger > 0 {
o.SetLevel0FileNumCompactionTrigger(c.Level0FileNumCompactionTrigger)
}
if c.Level0SlowdownWritesTrigger > 0 {
o.SetLevel0SlowdownWritesTrigger(c.Level0SlowdownWritesTrigger)
}
if c.Level0StopWritesTrigger > 0 {
o.SetLevel0StopWritesTrigger(c.Level0StopWritesTrigger)
}
if c.DisableAutoCompactions {
o.SetDisableAutoCompactions(true)
}
if c.TargetFileSizeMB > 0 {
o.SetTargetFileSizeBase(uint64(c.TargetFileSizeMB) << 20)
}
}

Expand All @@ -625,33 +646,6 @@ func applyPinnedCFOptions(o *grocksdb.Options) {
o.SetCompression(grocksdb.NoCompression)
}

func applyCFTuning(o *grocksdb.Options, t Tuning) {
if t.WriteBufferMB > 0 {
o.SetWriteBufferSize(uint64(t.WriteBufferMB) << 20)
}
if t.MaxWriteBufferNumber > 0 {
o.SetMaxWriteBufferNumber(t.MaxWriteBufferNumber)
}
if t.Level0FileNumCompactionTrigger > 0 {
o.SetLevel0FileNumCompactionTrigger(t.Level0FileNumCompactionTrigger)
}
if t.Level0SlowdownWritesTrigger > 0 {
o.SetLevel0SlowdownWritesTrigger(t.Level0SlowdownWritesTrigger)
}
if t.Level0StopWritesTrigger > 0 {
o.SetLevel0StopWritesTrigger(t.Level0StopWritesTrigger)
}
if t.DisableAutoCompactions {
o.SetDisableAutoCompactions(true)
}
if t.TargetFileSizeMB > 0 {
o.SetTargetFileSizeBase(uint64(t.TargetFileSizeMB) << 20)
}
if t.MaxBytesForLevelBaseMB > 0 {
o.SetMaxBytesForLevelBase(uint64(t.MaxBytesForLevelBaseMB) << 20)
}
}

func applyDBTuning(opts *grocksdb.Options, t Tuning) {
if t.MaxBackgroundJobs > 0 {
opts.SetMaxBackgroundJobs(t.MaxBackgroundJobs)
Expand All @@ -665,15 +659,15 @@ func applyDBTuning(opts *grocksdb.Options, t Tuning) {
}

// applySharedTableOptions builds one BBTO per CF, referencing the
// shared block cache (when set), a fresh per-CF bloom filter (when
// set), and the per-CF BlockSize override (when set). The Store
// retains every BBTO and the cache; Close destroys them after
// opts/cfOpts.
// DB-wide block cache (when set), a fresh per-CF bloom filter (when
// that CF sets BloomFilterBitsPerKey), and the per-CF BlockSize
// override (when set). The Store retains every BBTO and the cache;
// Close destroys them after opts/cfOpts.
//
// A BBTO is installed on a CF iff any of the cache, a bloom filter,
// or that CF's BlockSize override is configured — preserving the
// previous behavior of leaving RocksDB's default BBTO untouched when
// no table-level knob is set.
// A BBTO is installed on a CF iff the shared cache, that CF's bloom
// filter, or that CF's BlockSize override is configured — preserving
// the previous behavior of leaving RocksDB's default BBTO untouched
// when no table-level knob is set.
//
// The bloom filter is built per CF because SetFilterPolicy MOVES the
// policy into the BBTO (it nils the source pointer), so a single
Expand All @@ -685,15 +679,15 @@ func (s *Store) applySharedTableOptions(cfNames []string, cfOpts []*grocksdb.Opt
}
for i, o := range cfOpts {
override := s.cfg.PerCFOptions[cfNames[i]]
if s.cache == nil && t.BloomFilterBitsPerKey == 0 && override.BlockSize == 0 {
if s.cache == nil && override.BloomFilterBitsPerKey == 0 && override.BlockSize == 0 {
continue
}
bbto := grocksdb.NewDefaultBlockBasedTableOptions()
if s.cache != nil {
bbto.SetBlockCache(s.cache)
}
if t.BloomFilterBitsPerKey > 0 {
bbto.SetFilterPolicy(grocksdb.NewBloomFilter(float64(t.BloomFilterBitsPerKey)))
if override.BloomFilterBitsPerKey > 0 {
bbto.SetFilterPolicy(grocksdb.NewBloomFilter(float64(override.BloomFilterBitsPerKey)))
}
if override.BlockSize > 0 {
bbto.SetBlockSize(override.BlockSize)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,14 @@ func TestNew_TwoStoresSamePathCollide(t *testing.T) {

func TestStore_ConstructAndOpenFailureFreesCacheAndBBTOs(t *testing.T) {
dir := t.TempDir()
tuning := Tuning{BlockCacheMB: 4, BloomFilterBitsPerKey: 10}
tuning := Tuning{BlockCacheMB: 4}
perCF := map[string]CFOptions{defaultCFName: {BloomFilterBitsPerKey: 10}}

holder, err := New(Config{Path: dir, Logger: silentLogger(), Tuning: tuning})
holder, err := New(Config{Path: dir, Logger: silentLogger(), Tuning: tuning, PerCFOptions: perCF})
require.NoError(t, err)
t.Cleanup(func() { _ = holder.Close() })

collider := &Store{cfg: Config{Path: dir, Logger: silentLogger(), Tuning: tuning}}
collider := &Store{cfg: Config{Path: dir, Logger: silentLogger(), Tuning: tuning, PerCFOptions: perCF}}
require.Error(t, collider.constructAndOpen())

assert.Nil(t, collider.cache)
Expand Down Expand Up @@ -665,18 +666,21 @@ func TestStore_TuningRoundTrip(t *testing.T) {
Path: t.TempDir(),
Logger: newTestLogger(&buf),
Tuning: Tuning{
WriteBufferMB: 8,
MaxWriteBufferNumber: 2,
Level0FileNumCompactionTrigger: 4,
Level0SlowdownWritesTrigger: 20,
Level0StopWritesTrigger: 36,
TargetFileSizeMB: 16,
MaxBytesForLevelBaseMB: 64,
MaxBackgroundJobs: 2,
MaxOpenFiles: 500,
BlockCacheMB: 4,
BloomFilterBitsPerKey: 10,
MaxTotalWalSizeMB: 16,
MaxBackgroundJobs: 2,
MaxOpenFiles: 500,
BlockCacheMB: 4,
MaxTotalWalSizeMB: 16,
},
PerCFOptions: map[string]CFOptions{
defaultCFName: {
WriteBufferMB: 8,
MaxWriteBufferNumber: 2,
Level0FileNumCompactionTrigger: 4,
Level0SlowdownWritesTrigger: 20,
Level0StopWritesTrigger: 36,
TargetFileSizeMB: 16,
BloomFilterBitsPerKey: 10,
},
},
})
require.NoError(t, err)
Expand Down
66 changes: 29 additions & 37 deletions cmd/stellar-rpc/internal/fullhistory/storage/rocksdb/tuning.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ package rocksdb

import "github.com/linxGnu/grocksdb"

// CFOptions — per-CF overrides applied after the shared pinned
// defaults and global Tuning. Zero means "inherit the pinned
// default" (NoCompression for Compression, RocksDB's BBTO default
// block size for BlockSize). Use to opt specific CFs into
// compression or non-default block sizes while leaving the rest at
// the wrapper-pinned defaults.
// CFOptions — per-CF overrides applied after the shared pinned defaults.
// Zero means "inherit the default" for every field (NoCompression, RocksDB's
// BBTO block size, no bloom, grocksdb's memtable/compaction defaults). The
// knobs are per-CF because each hot workload differs — one CF's tuning must
// not leak onto the others.
type CFOptions struct {
// Compression overrides the CF's compression type. The wrapper's
// pinned default is NoCompression; CFs that hold compressible
Expand All @@ -25,24 +24,12 @@ type CFOptions struct {
// block holding the target key; larger blocks waste I/O per
// cache miss.
BlockSize int
}

// Tuning — per-store RocksDB knobs. Zero means "leave grocksdb's
// default alone" (wrapper skips the setter). BloomFilterBitsPerKey == 0
// is the documented "install no bloom filter" sentinel.
//
// Wrapper-pinned values not exposed here (applied to every facade):
// MinWriteBufferNumberToMerge=1, CompactionStyle=Level,
// TargetFileSizeMultiplier=1, MaxBytesForLevelMultiplier=10,
// Compression=None, WAL=on, per-write Sync=on.
// Per-CF overrides for Compression and BlockSize live in
// Config.PerCFOptions.
type Tuning struct {
// WriteBufferMB sizes the active memtable per CF, in MB.
// WriteBufferMB sizes the active memtable for this CF, in MB.
WriteBufferMB int

// MaxWriteBufferNumber caps the active + immutable memtable count
// per CF before writes back-pressure.
// for this CF before writes back-pressure.
MaxWriteBufferNumber int

// Level0FileNumCompactionTrigger — L0 file count that starts
Expand All @@ -52,38 +39,43 @@ type Tuning struct {
// Level0SlowdownWritesTrigger — L0 file count that slows writes.
Level0SlowdownWritesTrigger int

// Level0StopWritesTrigger — L0 file count that stalls writes
// entirely.
// Level0StopWritesTrigger — L0 file count that stalls writes entirely.
Level0StopWritesTrigger int

// DisableAutoCompactions turns automatic compaction off per CF —
// for write-once, point-lookup stores where compaction would
// rewrite the same data with no reordering benefit.
// DisableAutoCompactions turns automatic compaction off for this CF —
// for write-once, point-lookup CFs where compaction would rewrite the
// same data with no reordering benefit.
DisableAutoCompactions bool

// TargetFileSizeMB — size at which compaction produces new SSTs.
TargetFileSizeMB int

// MaxBytesForLevelBaseMB — byte budget for level 1; each later
// level is this × MaxBytesForLevelMultiplier (10, pinned).
MaxBytesForLevelBaseMB int
// BloomFilterBitsPerKey installs this CF's bloom filter; 0 = none —
// right for a CF never probed for keys it may not hold.
BloomFilterBitsPerKey int
}

// Tuning — DB-wide RocksDB knobs shared across every CF of one store. Zero
// means "leave grocksdb's default alone" (wrapper skips the setter). Per-CF
// knobs (memtables, compaction, bloom, block size, compression) live in
// Config.PerCFOptions, not here.
//
// Wrapper-pinned per-CF values not exposed anywhere (applied to every CF):
// MinWriteBufferNumberToMerge=1, CompactionStyle=Level,
// TargetFileSizeMultiplier=1, MaxBytesForLevelMultiplier=10,
// Compression=None, WAL=on, per-write Sync=on.
type Tuning struct {
// MaxBackgroundJobs caps background threads for compactions
// and flushes combined. Orthogonal to DisableAutoCompactions:
// this rate-limits work, that turns compaction off entirely.
// and flushes combined, across the whole DB.
MaxBackgroundJobs int

// MaxOpenFiles caps concurrent open SST files.
// MaxOpenFiles caps concurrent open SST files, across the whole DB.
MaxOpenFiles int

// BlockCacheMB sizes the shared LRU block cache, per store.
// BlockCacheMB sizes the LRU block cache shared across every CF in
// the store.
BlockCacheMB int

// BloomFilterBitsPerKey — per-CF bloom filter. 0 = no filter
// installed; positive values install one with that many bits.
// Typical: 10 (~1% false positive), 12 (~0.4%).
BloomFilterBitsPerKey int

// MaxTotalWalSizeMB caps total live WAL size. Crash-recovery
// replay scales with this cap; graceful Close drains the
// memtable so this only bounds ungraceful shutdowns.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"context"
"fmt"
"iter"
"maps"
"slices"
"time"

Expand Down Expand Up @@ -54,18 +55,42 @@ func ColumnFamilies() []string {
return slices.Concat(ledger.CFNames(), eventstore.CFNames(), txhash.CFNames())
}

// config builds the shared store's rocksdb.Config: events' per-CF options (ZSTD
// on DataCF, tuned block sizes) plus the txhash workload's Tuning. Tuning's
// per-CF fields apply to every CF — a benign over-application (ledger/events CFs
// just gain a bloom + larger write buffer); the per-CF overrides keep events
// distinct.
// dbTuning is the DB-wide half of the shared store's configuration, owned
// here because hotchunk owns the DB (each facade only configures its own
// CFs). The values originate from the standalone txhash store's calibration
// — the only pre-unification instance that set them.
func dbTuning() rocksdb.Tuning {
return rocksdb.Tuning{
// Background-job budget for memtable flushes and the
// ledger/events compactions.
MaxBackgroundJobs: 8,
MaxOpenFiles: 10_000,

// 512 MB block cache — txhash bloom-filter blocks are the hot
// working set; the cache needs to hold recently-touched bloom
// blocks at scale.
BlockCacheMB: 512,

// 1 GB WAL cap. Graceful Close auto-Flushes (see
// rocksdb.Store.Close), so this cap only bounds
// ungraceful-shutdown recovery (kernel panic, power loss, OOM
// kill).
MaxTotalWalSizeMB: 1024,
}
}

// config builds the shared store's rocksdb.Config: the DB-wide dbTuning plus
// the per-CF options merged from every facade — each CF keeps its
// pre-unification standalone tuning; the ledgers CF rides on RocksDB defaults.
func config(path string, logger *supportlog.Entry, readOnly, mustExist bool) rocksdb.Config {
perCF := eventstore.CFOptions()
maps.Copy(perCF, txhash.CFOptions())
return rocksdb.Config{
Path: path,
ColumnFamilies: ColumnFamilies(),
Logger: logger,
Tuning: txhash.Tuning(),
PerCFOptions: eventstore.CFOptions(),
Tuning: dbTuning(),
PerCFOptions: perCF,
ReadOnly: readOnly,
MustExist: mustExist,
}
Expand Down
Loading
Loading