Repository navigation
perf: cut span index size and let retention keep up on large installs - #41
Merged
Merged
Conversation
…ones The span pages (database, cache, jobs) only aggregate their own ops, a small share of all spans (about 20% on a large install where view spans are 68%). Three indexes over every span (23 GB there, never scanned in 9 days) cost an index write on every insert and retention delete. - Add partial indexes spans_db_start, spans_cache_start and spans_job_start (migration 0037) and drop spans_proj_start_op, spans_op_start and spans_start_op - Drop transactions_id_covering (only the per-span foreign key check read it) and transactions_project_start (never scanned) - Span samples add their op's category filter so the planner can prove the index predicate; all-project queries list project ids, so Postgres before 18 needs no skip scan - An unknown span category matches nothing instead of scanning all spans
Random v4 defaults scatter inserts over the whole primary-key index; on a large spans table that index no longer fits in memory. Generate v7 ids in Go, as transactions already did, so Postgres before 18 still works.
…r lock - Each table gets a one-minute budget per pass instead of 200k rows, and a pass that is still behind is followed after 30 s instead of 5 minutes; the old cap held age retention to about 2.4M rows an hour per table - Batch sizes adapt toward one-second statements; transactions batch at most 1000, since each cascades to its spans - Event purges decrement issue counts by the rows they removed instead of recounting every event of the issue while holding its lock, which stalled grouping on large issues; emptiness is decided by an existence probe
…time - /api/environments walks each (project_id, environment) index one distinct value at a time instead of reading every row - Unfiltered hourly transaction counts read the telemetry_usage counters; the partial hours at either edge are still counted from raw rows - Uptime stats only read the 30 days they report on
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A database review of a large production install (59 GB) found that the slowness comes almost entirely from spans. Spans are 57 GB of it, and 33 GB of that is indexes. Three of those indexes (23 GB) were never scanned in 9 days, but every span insert and every retention delete still had to update them. The server read about 1.4 TB of span index pages from disk in that time.
This PR shrinks the span indexes, makes inserts cheaper, lets retention keep up with heavy ingest, and removes three queries that read whole tables.
Type of change
Changes
Span indexes (migration 0037)
spans_db_start,spans_cache_startandspans_job_start. Each span lands in at most one of them.spans_proj_start_op,spans_op_startandspans_start_op.transactions_id_covering: its only reader was the foreign key check on each span insert, whichtransactions_pkeyserves just as well.transactions_project_start: never scanned.op = $3it can't.project_idcolumn usable on Postgres before 18, which has no skip scan.Ingest
Retention
Queries
/api/environments: walks each(project_id, environment)index one distinct value at a time.telemetry_usagecounters. The partial hours at the window edges are still counted from raw rows.Behaviour changes
event_count. The one exception is an issue left with no events, whose count is reset to 0.Upgrade notes
On a large install, migration 0037 builds three indexes, and each build reads the whole spans table twice. Tindra waits for migrations at startup. Before upgrading, build the indexes by hand. That doesn't block ingest, and the migration then skips them:
Test plan
Span index plans:
TestSpanPageQueriesUseCategoryIndexesrecords the SQL each span function sends and EXPLAINs it with sequential scans disabled. It requires the matching category index for summaries, timeseries and samples, both with and without a project filter. With the samples category filter removed, the test fails as it should.Benchmark, Postgres 18, 3M spans over 7 days (68% view, 10% each db and cache), old indexes vs new:
Ingest:
TestIngestWritesTimeOrderedRowIDschecks that events, logs, transactions and spans get version 7 ids.Retention:
Queries:
go vet, golangci-lint (0 issues), andgo test -race ./...(all packages) pass.Checklist
Contributor License Agreement