Repository navigation
perf: cache and validate small Parquet files to cut object-store round trips per query - #147
Merged
Merged
Conversation
The floating quay.io/parseablehq/quest:main image now expects 202 from stream deletion. This server still deletes synchronously and returns 200, so TestSmokeRbacBasic and TestSmokeRoles fail on main and on every pull request. The previous image digest is no longer available from the registry, so build Quest from the last source commit before that change. The compose files take the image and pull policy from QUEST_IMAGE and QUEST_PULL_POLICY and keep their previous defaults. Refs #151 Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
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.
Problem
Parseable uploads one small Parquet file per stream per minute, and every query re-reads the same files from object storage. For anything beyond a bare
COUNT(*), each file costs about five requests (footer, page index, column chunks), issued one after another per file. When the bucket is far from the server, query time is almost entirely round trips: on a standalone node about 150 ms from its S3-compatible bucket, a 25-tile dashboard made about 6,500 object-store calls per load while the server used 10–240 ms of CPU per query.Change
src/query/small_file_cache.rs). A Parquet file whose manifest size is at most 256 KiB is fetched whole with one bounded GET and kept in memory. Footer, page-index and column reads are then served from that body. Larger files keep DataFusion's reader and its metadata cache unchanged.If-None-Match). A 304 reuses the bytes; a changed object replaces them; a 404 or error propagates and evicts. Weak or missing ETags are never reused across requests. Nothing validated in one request is reused by another without validating again.scan()captures staging before the firststream.jsonread, which removes the duplicate read in standalone mode. The ordering invariant (publication precedes staging unlink) is unchanged and has its own tests.GET /api/v1/logstreamrun up to 16 at a time instead of serially.parseable_storage_read_bytes_totalcounts bytes actually returned; a 304 is its own outcome with zero bytes instead of being charged the requested range.Nothing stored in the bucket changes, and no new configuration is required.
Settings
P_SMALL_FILE_CACHE_ENABLEDtruefalserestores the original reader everywhereP_SMALL_FILE_CACHE_SIZE671088640also disables the cacheMeasurement
A standalone release build against a real S3-compatible bucket about 150 ms away, replaying a 25-tile dashboard (29 requests, 4 in flight) over a fixed past hour holding 60 one-minute files of OTel host metrics. Quiet machine, medians after warm-up:
main(d6a155f2)Four queries over that fixed window (a count, two grouped SQL queries, one PromQL range) returned byte-identical responses on both builds. A floating-point
SUMvaries in its last digit between identical runs on either build, so it is not a useful comparison.Per one-hour query the count went from 63–393 calls, depending on shape, to about 61: two metadata reads plus one validation per file.
What this does not do
stream.jsonand manifests are not cached across requests, because another writer can change them.Tests
main. These needP_CACHE_TEST_MINIO_ENDPOINTand are skipped visibly without it.COUNT(*)and PromQL, and that a second request validates again.request_context, forced byte-range splits and a UNION, asserting one GET per file per request and fresh validation on the next request.Review notes
The first commit was reviewed by two independent reviewers for wrong-data, freshness and failure behaviour; the second commit addresses their twelve findings. The second and third commits were then reviewed independently (no path serves another query's rows or stale bytes; two major findings: silent row loss on a grown ranged file, and unbounded request-view memory), and the fourth commit addresses those findings. The fourth commit has not been independently reviewed. Design notes:
docs/performance/validated-small-file-cache.md.🤖 Generated with Claude Code