Skip to content

Add streaming-loadtest ledger backend for apply-load replay - #678

Closed
aditya1702 wants to merge 1 commit into
mainfrom
replay-loadtest-backend
Closed

Add streaming-loadtest ledger backend for apply-load replay#678
aditya1702 wants to merge 1 commit into
mainfrom
replay-loadtest-backend

Conversation

@aditya1702

Copy link
Copy Markdown
Contributor

What

  • New dev-only LEDGER_BACKEND_TYPE=streaming-loadtest: reads ledger meta from named pipes written by stellar-core apply-load (one FIFO per tx profile), merges the streams into one ledger sequence, renumbers so restarts on either side never need a DB reset, and stamps advancing close times (apply-load emits closeTime 0).
  • Two flags: --loadtest-meta-pipe-paths, --loadtest-ledger-close-duration (pacing; FIFO backpressure throttles the generators to match).
  • This backend type skips the history-archive connect and checkpoint bootstrap (apply-load publishes no archive); ingestion starts from ledger 1 on an empty DB.

Why

Load-test ingestion at rates the dev loadtest network can't produce (thousands of soroban TPL, sub-second ledgers). Only the ledger backend is swapped — the live ingest loop, processors, and persistence are unchanged, so results predict production behavior.

Testing

  • Unit tests for renumbering, 3-pipe merge, per-pipe generator restart, truncated frames, pacing, retry idempotence (green under -race).
  • Opt-in corpus test (STREAMING_LOADTEST_CORPUS) replayed real v27 sac/custom_token/soroswap apply-load output: 200 merged ledgers, ~57k txs, all parsed by the production transaction reader.
  • rpc/datastore behavior unchanged (validation branches exercised manually).

🤖 Generated with Claude Code

A dev-only LEDGER_BACKEND_TYPE=streaming-loadtest that reads stream-framed
LedgerCloseMeta from named pipes written by stellar-core apply-load (one
FIFO per transaction profile), renumbers each stream onto the consumer's
requested sequence with per-pipe diffs, merges the per-sequence frames
into one mixed-traffic ledger via the SDK's loadtest.MergeLedgers, and
stamps monotone wall-clock close times (apply-load emits closeTime 0).

Renumbering makes both sides restartable without a database reset: a
restarted apply-load resets to raw sequence 1 and is mapped onto the next
requested ledger; a restarted consumer resumes from its cursor. Pacing via
--loadtest-ledger-close-duration bounds the ledger rate, and FIFO
backpressure throttles the generators to match.

Because apply-load's benchmark mode publishes no history archive, this
backend type skips the archive connection and the cursor-0 checkpoint
bootstrap: ingestion starts from ledger 1 on an empty database and balance
state accumulates from the ledger stream. Everything downstream of the
backend (live ingest loop, processors, persistence) is unchanged, so a
load test exercises the same code path as production ingestion.

Includes an opt-in corpus test (STREAMING_LOADTEST_CORPUS) that replays
real apply-load output through the backend and the production transaction
reader; verified against v27 sac/custom_token/soroswap corpora.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings August 6, 2026 22:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a development-only streaming ledger backend for replaying and merging stellar-core apply-load metadata through live ingestion.

Changes:

  • Adds FIFO-based ledger reading, merging, renumbering, pacing, and restart handling.
  • Adds configuration flags and archive-free startup behavior.
  • Adds unit and opt-in corpus tests.

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
internal/services/ingest_live.go Adds archive-free startup.
internal/ingest/streaming_loadtest_ledger_backend.go Implements the streaming backend.
internal/ingest/streaming_loadtest_ledger_backend_test.go Tests backend behavior.
internal/ingest/streaming_loadtest_corpus_test.go Adds real-corpus replay testing.
internal/ingest/ledger_backend.go Registers the backend factory.
internal/ingest/ingest.go Adds configuration and skips archive connection.
go.mod Adds indirect dependencies.
go.sum Records dependency checksums.
cmd/utils/custom_set_value.go Adds string-list parsing.
cmd/utils/custom_set_value_test.go Tests string-list parsing.
cmd/protocol_migrate.go Rejects streaming migration use.
cmd/ingest.go Adds flags and validation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +261 to +267
} else if int64(frameSeq)+p.seqDiff != int64(sequence) {
// apply-load emits every ledger it closes, in order, with no
// gaps. A mismatch inside an epoch means frames were lost or
// reordered — unrecoverable, unlike a writer restart.
return xdr.LedgerCloseMeta{}, fmt.Errorf(
"sequence mismatch within stream epoch: raw ledger %d + diff %d != requested %d",
frameSeq, p.seqDiff, sequence)
Comment on lines +308 to +315
err = db.RunInTransaction(ctx, m.models.DB, func(dbTx pgx.Tx) error {
return m.initializeCursors(ctx, dbTx, startLedger)
})
if err != nil {
return fmt.Errorf("initializing cursors without archive bootstrap: %w", err)
}
m.appMetrics.Ingestion.LatestLedger.Set(float64(startLedger))
m.appMetrics.Ingestion.OldestLedger.Set(float64(startLedger))
Comment on lines +200 to +203
if i == 0 {
merged = frame
if err := loadtest.UpdateLedgerSeqInLedgerEntries(&merged, mapSeq); err != nil {
return xdr.LedgerCloseMeta{}, fmt.Errorf("renumbering entries from pipe %s: %w", p.path, err)
Comment on lines +281 to +284
go func(path string, ch chan<- openPipeResult) {
f, err := os.OpenFile(path, os.O_RDONLY, 0)
ch <- openPipeResult{file: f, err: err}
}(p.path, p.opening)
Comment on lines +114 to +115
func NewStreamingLoadtestLedgerBackend(cfg StreamingLoadtestBackendConfig) (*StreamingLoadtestLedgerBackend, error) {
if len(cfg.MetaPipePaths) == 0 {
@aditya1702

Copy link
Copy Markdown
Contributor Author

Superseded by #679, which targets blend/pr6-integration-tests (what dev currently runs) so the backend can be tested against the live dev deployment.

@aditya1702 aditya1702 closed this Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants