Skip to content

Commit b74710a

Browse files
mh0ltclaude
andauthored
[bal-devnet-3] cmd/utils: expose executor perf toggles as --exec.* CLI flags (#20862)
Cherry-pick of #20797 to bal-devnet-3. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1a22f01 commit b74710a

5 files changed

Lines changed: 181 additions & 0 deletions

File tree

cmd/utils/flags.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import (
4646
"github.com/erigontech/erigon/cmd/utils/flags"
4747
"github.com/erigontech/erigon/common"
4848
libkzg "github.com/erigontech/erigon/common/crypto/kzg"
49+
"github.com/erigontech/erigon/common/dbg"
4950
"github.com/erigontech/erigon/common/log/v3"
5051
"github.com/erigontech/erigon/db/config3"
5152
"github.com/erigontech/erigon/db/datadir"
@@ -1149,6 +1150,31 @@ var (
11491150
Name: "erigondb.domain.steps-in-frozen-file",
11501151
Usage: `Override erigondb.toml "steps_in_frozen_file" for the domain merge cap only (history/inverted-index merges are unaffected). Pass a positive integer to set an explicit cap, or "Inf" to leave the domain merge unbounded. Default: unset, meaning the domain uses the same cap as determined by erigondb.toml.`,
11511152
}
1153+
ExecBatchedIOFlag = cli.BoolFlag{
1154+
Name: "exec.batched-io",
1155+
Usage: "Enable BAL-driven I/O and write-dependency optimisations: (1) read-ahead pre-warms the DB page cache with account/code/storage reads before block execution (READ_AHEAD=true), and (2) the parallel executor pre-populates the version map from BAL hints (IGNORE_BAL=false). Disable for cold-read or non-BAL scheduling performance measurements.",
1156+
Value: true,
1157+
}
1158+
ExecStateCacheFlag = cli.BoolFlag{
1159+
Name: "exec.state-cache",
1160+
Usage: "Enable the executor domain-shared read cache (equivalent to USE_STATE_CACHE=true). Disable for cold-read performance measurements.",
1161+
Value: true,
1162+
}
1163+
ExecWorkersFlag = cli.IntFlag{
1164+
Name: "exec.workers",
1165+
Usage: "Parallel executor worker count (equivalent to EXEC3_WORKERS). Default: half the number of CPU cores, other half reserved for snapshots build/merge/prune.",
1166+
Value: runtime.NumCPU() / 2,
1167+
}
1168+
ExecNoMergeFlag = cli.BoolFlag{
1169+
Name: "exec.no-merge",
1170+
Usage: "Disable state-aggregator file merges for Domain / History / Inverted-Index (equivalent to NO_MERGE=true). Diagnostic / perf-comparison use only.",
1171+
Value: false,
1172+
}
1173+
ExecNoPruneFlag = cli.BoolFlag{
1174+
Name: "exec.no-prune",
1175+
Usage: "Disable state-aggregator pruning of historical steps (equivalent to NO_PRUNE=true). Diagnostic / perf-comparison use only.",
1176+
Value: false,
1177+
}
11521178
)
11531179

11541180
var MetricFlags = []cli.Flag{&MetricsEnabledFlag, &MetricsHTTPFlag, &MetricsPortFlag}
@@ -1955,6 +1981,34 @@ func SetEthConfig(ctx *cli.Context, nodeConfig *nodecfg.Config, cfg *ethconfig.C
19551981
cfg.FcuTimeout = ctx.Duration(FcuTimeoutFlag.Name)
19561982
cfg.FcuBackgroundPrune = ctx.Bool(FcuBackgroundPruneFlag.Name)
19571983
cfg.FcuBackgroundCommit = ctx.Bool(FcuBackgroundCommitFlag.Name)
1984+
1985+
// Executor performance toggles. When the user explicitly sets the CLI
1986+
// flag, it overrides the env-var default that dbg read at package init.
1987+
// Otherwise env vars (IGNORE_BAL, USE_STATE_CACHE, EXEC3_WORKERS,
1988+
// NO_MERGE, NO_PRUNE) remain the source of truth.
1989+
if ctx.IsSet(ExecBatchedIOFlag.Name) {
1990+
// --exec.batched-io toggles two BAL-driven optimisations together:
1991+
// read-ahead pre-warming (ReadAhead) and version-map pre-population
1992+
// (IgnoreBAL, inverted). Flipped in lockstep so perf comparisons
1993+
// test "all BAL I/O off" vs "all BAL I/O on".
1994+
v := ctx.Bool(ExecBatchedIOFlag.Name)
1995+
dbg.SetReadAhead(v)
1996+
dbg.SetIgnoreBAL(!v)
1997+
}
1998+
if ctx.IsSet(ExecStateCacheFlag.Name) {
1999+
dbg.SetUseStateCache(ctx.Bool(ExecStateCacheFlag.Name))
2000+
}
2001+
if ctx.IsSet(ExecWorkersFlag.Name) {
2002+
n := ctx.Int(ExecWorkersFlag.Name)
2003+
dbg.SetExec3Workers(n)
2004+
cfg.ExecWorkerCount = n
2005+
}
2006+
if ctx.IsSet(ExecNoMergeFlag.Name) {
2007+
dbg.SetNoMerge(ctx.Bool(ExecNoMergeFlag.Name))
2008+
}
2009+
if ctx.IsSet(ExecNoPruneFlag.Name) {
2010+
dbg.SetNoPrune(ctx.Bool(ExecNoPruneFlag.Name))
2011+
}
19582012
if ctx.IsSet(RPCGlobalGasCapFlag.Name) {
19592013
cfg.RPCGasCap = ctx.Uint64(RPCGlobalGasCapFlag.Name)
19602014
}

cmd/utils/flags_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import (
2626

2727
"github.com/stretchr/testify/require"
2828
"github.com/urfave/cli/v2"
29+
30+
"github.com/erigontech/erigon/common/dbg"
2931
)
3032

3133
func Test_SplitTagsFlag(t *testing.T) {
@@ -97,3 +99,107 @@ func TestResolveChainName(t *testing.T) {
9799
})
98100
}
99101
}
102+
103+
// TestExecPerfFlags_OverrideDbg verifies each --exec.* flag, when explicitly
104+
// set, flips the corresponding dbg package toggle. Also asserts the no-flag
105+
// path leaves dbg values untouched so env vars remain the source of truth.
106+
func TestExecPerfFlags_OverrideDbg(t *testing.T) {
107+
// Snapshot and restore dbg values so tests don't leak into each other.
108+
origIgnoreBAL := dbg.IgnoreBAL
109+
origReadAhead := dbg.ReadAhead
110+
origUseStateCache := dbg.UseStateCache
111+
origExec3Workers := dbg.Exec3Workers
112+
origNoPrune := dbg.NoPrune()
113+
origNoMerge := dbg.NoMerge()
114+
t.Cleanup(func() {
115+
dbg.SetIgnoreBAL(origIgnoreBAL)
116+
dbg.SetReadAhead(origReadAhead)
117+
dbg.SetUseStateCache(origUseStateCache)
118+
dbg.SetExec3Workers(origExec3Workers)
119+
dbg.SetNoPrune(origNoPrune)
120+
dbg.SetNoMerge(origNoMerge)
121+
})
122+
123+
apply := func(ctx *cli.Context) error {
124+
if ctx.IsSet(ExecBatchedIOFlag.Name) {
125+
v := ctx.Bool(ExecBatchedIOFlag.Name)
126+
dbg.SetReadAhead(v)
127+
dbg.SetIgnoreBAL(!v)
128+
}
129+
if ctx.IsSet(ExecStateCacheFlag.Name) {
130+
dbg.SetUseStateCache(ctx.Bool(ExecStateCacheFlag.Name))
131+
}
132+
if ctx.IsSet(ExecWorkersFlag.Name) {
133+
dbg.SetExec3Workers(ctx.Int(ExecWorkersFlag.Name))
134+
}
135+
if ctx.IsSet(ExecNoMergeFlag.Name) {
136+
dbg.SetNoMerge(ctx.Bool(ExecNoMergeFlag.Name))
137+
}
138+
if ctx.IsSet(ExecNoPruneFlag.Name) {
139+
dbg.SetNoPrune(ctx.Bool(ExecNoPruneFlag.Name))
140+
}
141+
return nil
142+
}
143+
144+
run := func(args ...string) {
145+
app := cli.NewApp()
146+
app.Flags = []cli.Flag{
147+
&ExecBatchedIOFlag, &ExecStateCacheFlag, &ExecWorkersFlag,
148+
&ExecNoMergeFlag, &ExecNoPruneFlag,
149+
}
150+
app.Action = apply
151+
require.NoError(t, app.Run(append([]string{"test"}, args...)))
152+
}
153+
154+
t.Run("no flags set leaves dbg untouched", func(t *testing.T) {
155+
dbg.SetIgnoreBAL(false)
156+
dbg.SetReadAhead(true)
157+
dbg.SetUseStateCache(true)
158+
dbg.SetExec3Workers(42)
159+
dbg.SetNoMerge(false)
160+
dbg.SetNoPrune(false)
161+
run()
162+
require.Equal(t, false, dbg.IgnoreBAL)
163+
require.Equal(t, true, dbg.ReadAhead)
164+
require.Equal(t, true, dbg.UseStateCache)
165+
require.Equal(t, 42, dbg.Exec3Workers)
166+
require.Equal(t, false, dbg.NoMerge())
167+
require.Equal(t, false, dbg.NoPrune())
168+
})
169+
170+
t.Run("batched-io=false disables read-ahead and sets IgnoreBAL", func(t *testing.T) {
171+
dbg.SetIgnoreBAL(false)
172+
dbg.SetReadAhead(true)
173+
run("--exec.batched-io=false")
174+
require.True(t, dbg.IgnoreBAL)
175+
require.False(t, dbg.ReadAhead)
176+
})
177+
178+
t.Run("batched-io=true enables read-ahead and clears IgnoreBAL", func(t *testing.T) {
179+
dbg.SetIgnoreBAL(true)
180+
dbg.SetReadAhead(false)
181+
run("--exec.batched-io=true")
182+
require.False(t, dbg.IgnoreBAL)
183+
require.True(t, dbg.ReadAhead)
184+
})
185+
186+
t.Run("state-cache=false flips UseStateCache", func(t *testing.T) {
187+
dbg.SetUseStateCache(true)
188+
run("--exec.state-cache=false")
189+
require.False(t, dbg.UseStateCache)
190+
})
191+
192+
t.Run("workers=7 sets Exec3Workers", func(t *testing.T) {
193+
dbg.SetExec3Workers(1)
194+
run("--exec.workers=7")
195+
require.Equal(t, 7, dbg.Exec3Workers)
196+
})
197+
198+
t.Run("no-merge and no-prune set to true", func(t *testing.T) {
199+
dbg.SetNoMerge(false)
200+
dbg.SetNoPrune(false)
201+
run("--exec.no-merge=true", "--exec.no-prune=true")
202+
require.True(t, dbg.NoMerge())
203+
require.True(t, dbg.NoPrune())
204+
})
205+
}

common/dbg/experiments.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ var (
111111
UseTxDependencies = EnvBool("USE_TX_DEPENDENCIES", false)
112112
UseStateCache = EnvBool("USE_STATE_CACHE", true)
113113
AssertStateCache = EnvBool("ASSERT_STATE_CACHE", false)
114+
ReadAhead = EnvBool("READ_AHEAD", true)
114115

115116
BorValidateHeaderTime = EnvBool("BOR_VALIDATE_HEADER_TIME", true)
116117
TraceDeletion = EnvBool("TRACE_DELETION", false)
@@ -132,6 +133,16 @@ func NoMergeHistory() bool { return noMergeHistory }
132133
func NoDeepMergeHistory() bool { return noDeepMergeHistory }
133134
func PruneTotalDifficulty() bool { return pruneTotalDifficulty }
134135

136+
// CLI-override setters for the performance toggles that also have env-var
137+
// twins. The env var sets the initial value at package init; the CLI layer
138+
// calls these at node startup only when the user explicitly set the flag.
139+
func SetIgnoreBAL(b bool) { IgnoreBAL = b }
140+
func SetUseStateCache(b bool) { UseStateCache = b }
141+
func SetReadAhead(b bool) { ReadAhead = b }
142+
func SetExec3Workers(n int) { Exec3Workers = n }
143+
func SetNoPrune(b bool) { noPrune = b }
144+
func SetNoMerge(b bool) { noMerge = b }
145+
135146
var (
136147
dirtySace uint64
137148
dirtySaceOnce sync.Once

execution/exec/blocks_read_ahead.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"golang.org/x/sync/errgroup"
1111

1212
"github.com/erigontech/erigon/common"
13+
"github.com/erigontech/erigon/common/dbg"
1314
"github.com/erigontech/erigon/common/length"
1415
"github.com/erigontech/erigon/common/log/v3"
1516
"github.com/erigontech/erigon/db/kv"
@@ -108,6 +109,10 @@ func AddSendersToGlobalReadAheader(senders []byte, blockHash common.Hash) {
108109
func (bra *blockReadAheader) warmBody(ctx context.Context, db kv.RoDB, header *types.Header, body *types.Body, workers int) {
109110
defer bra.warming.Store(false)
110111

112+
if !dbg.ReadAhead {
113+
return
114+
}
115+
111116
if workers <= 0 {
112117
workers = 1
113118
}

node/cli/default_flags.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ var DefaultFlags = []cli.Flag{
4747
&utils.FcuTimeoutFlag,
4848
&utils.FcuBackgroundPruneFlag,
4949
&utils.FcuBackgroundCommitFlag,
50+
&utils.ExecBatchedIOFlag,
51+
&utils.ExecStateCacheFlag,
52+
&utils.ExecWorkersFlag,
53+
&utils.ExecNoMergeFlag,
54+
&utils.ExecNoPruneFlag,
5055
&BatchSizeFlag,
5156
&BodyCacheLimitFlag,
5257
&DatabaseVerbosityFlag,

0 commit comments

Comments
 (0)