Repository navigation
perf(db): refresh statistics after a tenth of the store changes - #101
Merged
Merged
Conversation
Statistics rescanned the catalog and the reverse index after every 10,000 member and slot changes. A bulk load in transactions of 1,000 edges makes about 5,000 changes per commit, so it paid for a full scan every second commit and the total scan work grew with the square of the load. The change-feed subscriber now waits for max(10,000, size / 10) changes, where size is the atoms plus memberships the last refresh counted. A new or small store still refreshes after its first 10,000 changes. STATS and catalog() behave as before.
A fresh store still refreshes once its first 10,000 changes arrive. A store of 450,000 atoms and memberships takes 45,000 changes without a refresh and refreshes once after that. A load of 1,350,000 changes in commits of 9,000 refreshes 28 times, where the old rule refreshed 75 times.
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.
Statisticssubscribes to the change feed and rebuilt its catalog histograms after every 10,000 member and slot changes. Each rebuild streams the whole catalog and the reverse index. The benchmark loads hyperedges in transactions of 1,000, which comes to more than 5,000 changes per commit, so the load paid for a full scan every second commit. The number of scans grows with the load and each scan grows with the store, which makes the total work quadratic.The subscriber now waits for
max(10,000, size / 10)changes, wheresizeis the atoms plus memberships the last refresh counted (atoms + cardinality.sum() × stride). Those are the same units the change counter uses: in the engine, an edge withkmembers is one catalog change andkmember changes. Small stores behave as before and refresh after their first 10,000 changes. Large stores refresh about every 10% of growth, so a bulk load doesO(log N)scans. The tradeoff is that catalog statistics on a large store can be up to a tenth of its size out of date.STATSstill callsrefresh()first, andcatalog()still refreshes when nothing has been computed yet. The planner reads only the cardinality feedback, not the catalog, so plans do not change.What was measured
The probe runs the same steps as the first two
Comparison runworkloads with the same options (--store hstore --durability async --history 64, default cache): it ingests nodes, then edges, in transactions of 1,000 using the benchmark'sDataset. Before reading counters it waits until the statistics subscriber has acknowledged the last commit, so the async refresh work is counted. Allocation isgetTotalThreadAllocatedBytesacross all threads."main" is origin/main with the same package-private counter and subscriber hook as this branch, and the threshold fixed at 10,000. The branch is this PR. Both builds used the same probe, heap (4 GB at scale 1, 8 GB at scale 4) and dataset seed.
ingest.edgesingest.edges(median)ingest.nodesThree runs each at scale 1, two at scale 4 (the scale 4 median is the mean of the two). Scan counts were the same in every run. Allocation during
ingest.edgesdrops by about 7% at scale 1 and 12% at scale 4. Scale 1 has 50,000 nodes, 100,000 edges and 362,757 incidences. Scale 4 has 200,000 nodes, 400,000 edges and 1,453,427 incidences. I did not compare timings because the machine was busy with other builds.Tests
StatisticsTest(new,database/src/test/java/io/hstore/db/stats):aSmallStoreGetsStatisticsFromItsFirstChanges: a fresh store has no refresh after 4,500 changes, refreshes once 10,800 changes are in, andcatalog()then returns that refresh without scanning again.aLargeStoreWaitsForATenthOfItsSizeToChange: a store of 450,000 atoms and memberships takes 45,000 changes without refreshing, then refreshes exactly once when it goes past that. On main it refreshes twice in the same window.aBulkLoadRefreshesLogarithmicallyOften: 1,350,000 changes in commits of 9,000 give 28 refreshes. The test allows at most 38 (10 plus one per 10% growth after 100,000) and fewer than a quarter ofchanges / 10,000. On main the same load gives 75.With the threshold line reverted, the second and third tests fail and the first still passes. The first test is there to make sure small stores keep getting statistics quickly. To let the tests see the subscriber,
Statisticshas two package-private methods,refreshes()andacknowledged()../mvnw installpasses: 179 engine, 38 database and 15 server tests.Docs
docs/database/views-signals-stats.mdexplains the new rule and what it costs.docs/database/planner.mdanddocs/storage/change-feed.mdhave the updated one-line summaries.