Add RPC ingestion load test driven by synthetic apply-load ledger bundles#741
Conversation
|
⏳ Load test launching on |
📈 Ingest load test —
|
| Profile | Ledgers | Wall-clock | Ledgers/sec | ms/ledger | p50 / p95 / p99 ms |
|---|---|---|---|---|---|
| apply-load-v27-oz | 1000 | 1234.224s | 0.81 | 1235.46 | 1150 / 1674.999 / 1925.001 |
| apply-load-v27-sac | 1000 | 1138.025s | 0.88 | 1138.02 | 1149.999 / 1225 / 1275 |
| apply-load-v27-soroswap | 1000 | 829.299s | 1.21 | 829.30 | 849.994 / 900.001 / 975 |
| Metric | Value |
|---|---|
| Ledgers replayed | 3000 |
| Initial DB ledger count | 120960 |
| Overall throughput | 0.94 ledgers/sec |
| Overall ingest wall-clock | 3201.548s |
| Per-ledger p50 / p95 / p99 | 1099.998 / 1449.999 / 1824.999 ms |
| Golden DB fetch+decompress | 2436s |
| stellar-core | v27.0.0 |
| Workflow run | #27982167923 |
📈 Ingest load test —
|
| Profile | Ledgers | Wall-clock | Ledgers/sec | ms/ledger | p50 / p95 / p99 ms |
|---|---|---|---|---|---|
| load-test-ledgers-v27-oz | 1000 | 1234.299s | 0.81 | 1235.54 | 1150 / 1699.998 / 1924.999 |
| load-test-ledgers-v27-sac | 1000 | 1137.524s | 0.88 | 1137.52 | 1149.999 / 1225 / 1275.001 |
| load-test-ledgers-v27-soroswap | 1000 | 829.149s | 1.21 | 829.15 | 825.001 / 900.002 / 974.999 |
| Metric | Value |
|---|---|
| Ledgers replayed | 3000 |
| Initial DB ledger count | 120960 |
| Overall throughput | 0.94 ledgers/sec |
| Overall ingest wall-clock | 3200.973s |
| Per-ledger p50 / p95 / p99 | 1099.998 / 1450 / 1824.999 ms |
| Golden DB fetch+decompress | 2438s |
| stellar-core | v27.0.0 |
| Workflow run | #27984593953 |
|
⏳ Load test launching on |
📈 Ingest load test —
|
| Profile | Ledgers | Wall-clock | Ledgers/sec | ms/ledger | p50 / p95 / p99 ms |
|---|---|---|---|---|---|
| load-test-ledgers-v27-oz | 1000 | 1235.075s | 0.81 | 1236.31 | 1150 / 1675.001 / 1925 |
| load-test-ledgers-v27-sac | 1000 | 1138.124s | 0.88 | 1138.12 | 1149.999 / 1225 / 1275 |
| load-test-ledgers-v27-soroswap | 1000 | 829.550s | 1.21 | 829.55 | 849.997 / 900.004 / 974.999 |
| Metric | Value |
|---|---|
| Ledgers replayed | 3000 |
| Initial DB ledger count | 120960 |
| Overall throughput | 0.94 ledgers/sec |
| Overall ingest wall-clock | 3202.749s |
| Per-ledger p50 / p95 / p99 | 1099.998 / 1450 / 1824.999 ms |
| Golden DB fetch+decompress | 2454s |
| stellar-core | v27.0.0 |
| Workflow run | #28002422012 |
|
⏳ Load test launching on |
📈 Ingest load test —
|
| Profile | Ledgers | Wall-clock | Ledgers/sec | ms/ledger | p50 / p95 / p99 ms |
|---|---|---|---|---|---|
| load-test-ledgers-v27-oz | 1000 | 1235.175s | 0.81 | 1236.41 | 1150 / 1699.997 / 1924.999 |
| load-test-ledgers-v27-sac | 1000 | 1137.950s | 0.88 | 1137.95 | 1149.999 / 1225 / 1275 |
| load-test-ledgers-v27-soroswap | 1000 | 829.075s | 1.21 | 829.07 | 825.002 / 924.997 / 974.996 |
| Metric | Value |
|---|---|
| Ledgers replayed | 3000 |
| Initial DB ledger count | 120960 |
| Overall throughput | 0.94 ledgers/sec |
| Overall ingest wall-clock | 3202.199s |
| Per-ledger p50 / p95 / p99 | 1099.991 / 1450 / 1825.001 ms |
| Golden DB fetch+decompress | 2439s |
| stellar-core | v27.0.0 |
| Workflow run | #28050597017 |
|
⏳ Load test launching on |
📈 Ingest load test —
|
| Profile | Ledgers | ms/ledger | p50 / p95 / p99 ms | max ms |
|---|---|---|---|---|
| load-test-ledgers-v27-oz | 1000 | 1198.542 | 1117.151 / 1628.251 / 1918.960 | 2787.028 |
| load-test-ledgers-v27-sac | 1000 | 1096.993 | 1106.204 / 1182.071 / 1239.080 | 1304.020 |
| load-test-ledgers-v27-soroswap | 1000 | 792.811 | 804.091 / 867.349 / 925.778 | 1034.136 |
| Metric | Value |
|---|---|
| Ledgers replayed | 3000 |
| Initial DB ledger count | 120960 |
| Throughput | 0.94 ledgers/sec |
| Elapsed wall-clock | 3188.772s |
| Ingest busy-time | 3088.347s (96.9% utilization) |
| Per-ledger p50 / p95 / p99 | 1052.177 / 1422.112 / 1732.868 ms |
| Golden DB fetch+decompress | 2439s |
| stellar-core | v27.0.0 |
| Workflow run | #28125731936-1 |
Resolve go.mod/go.sum SDK conflict in favor of 6181cdf8bda5 (descendant of main's fc9acb3f1ba6), which carries the merged loadtest API. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the temporary @789121c0... pin added during load-test CI iteration; main tracks system-test master per repo convention. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
This is a PR implementing a repeatable CI ingestion load test on a full database of 7 days of ledgers. The approximate design is here:

This GHA workflow for this test, currently, is triggered on pushes to this branch (
apply-load), but will later be modified to trigger on any release or on PR comments stating "run load test".The workflow benchmarks RPC ingestion end-to-end on an ephemeral c5.2xlarge: it launches the box, pulls a mainnet-scale golden DB (~307GB, 1-week retention window), a BUILD_TESTS stellar-core, and three apply-load ledger bundles from S3 (sha-verified). After the box downloads and decompresses this data, its gp3 volume is throttled to 125 MiB/s, ingests the bundles, and posts a per-profile results table to the run summary / PR.
Main Pieces:
integrationtest/ingest_loadtest_test.go::TestIngestSyntheticLedgers: byte-concatenates N bundles into one continuous stream (the backend rebases ledger seqs per ledger, so per-bundle seq resets are harmless), ingests onto the golden DB with retention trimming live, verifies exact classic/soroban op counts via parallelgetTransactionswalkers, and reports per-profile wall-clock/ledgers-sec/ms-ledger/latency quantiles.loadtest/testdata/apply-load-v27-*-cfg: config files specifying three O3 target tx profiles, 1,000 ledgers each: sac (1,000 soroban TPL), oz (900), soroswap (250). All generate these + 1,000 classic payments/ledger to create ledger bundles (for local usage or S3) offline bystellar-core apply-load..github/workflows/load-test.yml: push-triggered orchestrator. OIDC-assumes into AWS, launches an ephemeralc5.2xlarge(Ubuntu 22.04, 500GB gp3) with the runner script as user-data (shipped verbatim, TARGET_SHA/RUN_ID passed via a two-line env preamble), waits for SSM registration, delegates polling to the script, writes the results table to the step summary (and PR comment when one exists), fails the job on a fail verdict or timeout, and always terminates the instance.run-load-test.sh: both halves of the run in one self-contained script, coordinated by a /tmp marker protocol.orchestrate(on the GHA runner): polls the box over SSM, drives the gp3 downshift handshake (500 -> 125 MiB/s after downloads complete, so fetches are fast but the benchmark runs on throttled I/O), and relays verdict + results as step outputs.Why
CI testing of RPC ingestion performance; benchmarking. This also serves as an automated regression testing framework, though future work should expand this to report some metric that allows one to compare a run's results to historical results.
Known limitations
This is purely intended as a test of RPC's ingestion pipeline and seeks to see how it handles load in isolation (i.e. without captive core running). Future work should also seek to automatically refresh the S3 DB + ledger bundles on some pre-determined cadence.