From faa3d51ab2f226438572c8f8f3d16a00e26b1449 Mon Sep 17 00:00:00 2001 From: George Date: Tue, 23 Dec 2025 12:19:34 -0800 Subject: [PATCH 1/3] Run sqlite optimization after ingestion db connection closes: This is recommended practice for SQLite databases with short-lived connections such as the connection for ingestion: https://sqlite.org/pragma.html#pragma_optimize Note the callout of "low-quality indexes" on this page: https://sqlite.org/queryplanner-ng.html#howtofix Due to the nature of both topics and contract IDs, we have a huge variance in the K -> V ratio. Things like XLM have tons of event rows given fee events occur on every txn, and "transfer" is obviously a popular topic1 symbol. This explicitly breaks an indexing rule: > A low-quality index is one where there are **more than 10 or 20 > rows in the table that have the same value** for the > left-most column of the index. The solution is running `ANALYZE events;`, but this would take too long and grabs an exclusive lock on the DB, so we must periodically run `ANALYZE` via the `PRAGMA optimize;` directive which we execute at the end of every ingestion commit. --- cmd/stellar-rpc/internal/db/db.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/stellar-rpc/internal/db/db.go b/cmd/stellar-rpc/internal/db/db.go index fe5c24fc..6dbba718 100644 --- a/cmd/stellar-rpc/internal/db/db.go +++ b/cmd/stellar-rpc/internal/db/db.go @@ -241,7 +241,7 @@ func (rw *readWriter) NewTx(ctx context.Context) (WriteTx, error) { postCommit: func(durationMetrics map[string]time.Duration) error { // TODO: this is sqlite-only, it shouldn't be here startTime := time.Now() - _, err := db.ExecRaw(ctx, "PRAGMA wal_checkpoint(TRUNCATE)") + _, err := db.ExecRaw(ctx, "PRAGMA wal_checkpoint(TRUNCATE); PRAGMA optimize;") if err != nil { return err } From bc02021136e51a41f23aca6a204dc06edb81cff2 Mon Sep 17 00:00:00 2001 From: George Date: Tue, 23 Dec 2025 12:24:29 -0800 Subject: [PATCH 2/3] Remove unused ledger entry cache (Core does it now) --- cmd/stellar-rpc/internal/db/db.go | 13 ++++--------- cmd/stellar-rpc/internal/ingest/service.go | 2 +- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/cmd/stellar-rpc/internal/db/db.go b/cmd/stellar-rpc/internal/db/db.go index 6dbba718..4dd1ac27 100644 --- a/cmd/stellar-rpc/internal/db/db.go +++ b/cmd/stellar-rpc/internal/db/db.go @@ -49,7 +49,6 @@ type WriteTx interface { type dbCache struct { latestLedgerSeq uint32 latestLedgerCloseTime int64 - ledgerEntries transactionalCache // Just like the DB: compress-encoded ledger key -> ledger entry XDR sync.RWMutex } @@ -60,8 +59,8 @@ type DB struct { func openSQLiteDB(dbFilePath string) (*db.Session, error) { // 1. Use Write-Ahead Logging (WAL). - // 2. Disable WAL auto-checkpointing (we will do the checkpointing ourselves with wal_checkpoint pragmas - // after every write transaction). + // 2. Disable WAL auto-checkpointing (we will do the checkpointing ourselves + // with wal_checkpoint pragmas after every write transaction). // 3. Use synchronous=NORMAL, which is faster and still safe in WAL mode. session, err := db.Open("sqlite3", fmt.Sprintf("file:%s?_journal_mode=WAL&_wal_autocheckpoint=0&_synchronous=NORMAL", dbFilePath)) @@ -85,9 +84,7 @@ func OpenSQLiteDBWithPrometheusMetrics(dbFilePath string, namespace string, sub } result := DB{ SessionInterface: db.RegisterMetrics(session, namespace, sub, registry), - cache: &dbCache{ - ledgerEntries: newTransactionalCache(), - }, + cache: &dbCache{}, } return &result, nil } @@ -99,9 +96,7 @@ func OpenSQLiteDB(dbFilePath string) (*DB, error) { } result := DB{ SessionInterface: session, - cache: &dbCache{ - ledgerEntries: newTransactionalCache(), - }, + cache: &dbCache{}, } return &result, nil } diff --git a/cmd/stellar-rpc/internal/ingest/service.go b/cmd/stellar-rpc/internal/ingest/service.go index 3963abfa..62e6492a 100644 --- a/cmd/stellar-rpc/internal/ingest/service.go +++ b/cmd/stellar-rpc/internal/ingest/service.go @@ -219,7 +219,7 @@ func (s *Service) ingest(ctx context.Context, sequence uint32) error { s.logger. WithField("duration", time.Since(startTime).Seconds()). - Debugf("Ingested ledger %d", sequence) + Infof("Ingested ledger %d", sequence) s.metrics.ingestionDurationMetric. With(prometheus.Labels{"type": "total"}). From 95a830465ad9d0e895e3ebb132f8893312585d74 Mon Sep 17 00:00:00 2001 From: George Date: Tue, 3 Feb 2026 15:58:06 -0800 Subject: [PATCH 3/3] Track metrics on db optimization --- cmd/stellar-rpc/internal/db/db.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cmd/stellar-rpc/internal/db/db.go b/cmd/stellar-rpc/internal/db/db.go index 4dd1ac27..c79455f5 100644 --- a/cmd/stellar-rpc/internal/db/db.go +++ b/cmd/stellar-rpc/internal/db/db.go @@ -236,13 +236,20 @@ func (rw *readWriter) NewTx(ctx context.Context) (WriteTx, error) { postCommit: func(durationMetrics map[string]time.Duration) error { // TODO: this is sqlite-only, it shouldn't be here startTime := time.Now() - _, err := db.ExecRaw(ctx, "PRAGMA wal_checkpoint(TRUNCATE); PRAGMA optimize;") - if err != nil { + if _, err := db.ExecRaw(ctx, "PRAGMA wal_checkpoint(TRUNCATE);"); err != nil { return err } if durationMetrics != nil { durationMetrics["wal_checkpoint"] = time.Since(startTime) } + + startTime = time.Now() + if _, err := db.ExecRaw(ctx, "PRAGMA optimize;"); err != nil { + return err + } + if durationMetrics != nil { + durationMetrics["db_optimize"] = time.Since(startTime) + } return nil }, tx: txSession,