feat(tests): add tests for low and high cardinality request attribute… - #38
Open
ArnabChatterjee20k wants to merge 8 commits into
Open
feat(tests): add tests for low and high cardinality request attribute…#38ArnabChatterjee20k wants to merge 8 commits into
ArnabChatterjee20k wants to merge 8 commits into
Conversation
…s in ClickHouse adapter
|
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds set(0) indexes for low-cardinality equality dims (protocol, timeZone, weatherCode) and bloom_filter for the high-cardinality ones (accept, acceptLanguage, queryKeys, postalCode, latitude, longitude), matching the existing getEventIndexes convention. Definitions only; the index is applied to the live table via a separately-run migration. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…adapter The shared getEventIndexes() is also consumed by the SQL adapter, whose max index key length is 768 bytes. accept and queryKeys (size 1024) exceed it, so they are indexed on a 255-char prefix like path; ClickHouse ignores the prefix and indexes the whole value. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…y equality dims These request attributes are matched by exact equality (like country), not substring, and have bounded distinct counts. Store them as LowCardinality(Nullable(String)) and index with set(0) instead of plain Nullable(String) + bloom_filter — cuts the dominant column storage cost (dictionary encoding) and gives cheaper equality pruning. Drop the latitude/longitude skip indexes: they are display-only with no exact-match filtering. postalCode keeps its bloom_filter. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QssJBktSLWpuEuWzVB23do
…858a) The prior commit inadvertently committed a locally-regenerated lock that resolved a broken utopia-php/client combination (undefined array_last() in the Curl adapter). Restore the lock to its pre-session state. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QssJBktSLWpuEuWzVB23do
…ith ZSTD These store raw, un-normalized caller input (full Accept/Accept-Language headers, arbitrary query-key names), so their distinct count is unbounded. Revert them from LowCardinality + set(0) back to Nullable(String) + bloom_filter (equality lookups without a cardinality bet) and add CODEC(ZSTD(3)) to compress the high-entropy text. protocol/timeZone/ weatherCode remain LowCardinality + set(0) (genuinely bounded). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QssJBktSLWpuEuWzVB23do
Adds an ipReputation dimension to the events schema, ready to populate later. It holds a bounded IP-reputation verdict (clean/low/suspicious/block), so it is typed LowCardinality(Nullable(String)) with a set(0) equality index, like protocol/country. Column is nullable and unpopulated until the producer is ready. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QssJBktSLWpuEuWzVB23do
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.
What
Follow-up to #36 / #37, which added new request-attribute and premium-geo dimension columns to the events table. Those PRs created the columns as generic
stringattributes, so the ClickHouse adapter rendered them all as plainNullable(String)with no index — the column existed but the data type, indexing, and compression weren't right for how they're queried. This PR picks the correct ClickHouse type, index, and codec per column, driven by the actual query surface and the FRA table's scale.Query surface (why these choices)
Confirmed from the consuming PRs (appwrite-labs/cloud#5633, appwrite/appwrite#13463): all of these are used like existing console dimensions — selected, filtered (
queries[]), and grouped-by (dimensions[]) — and filtered by exact equality (equal/in), not substring. Every query is primary-key-pruned to a(tenant, metric, time)slice first, so secondary indexes are an optimization on an already-small slice.Schema — new columns
protocolLowCardinality(Nullable(String))set(0)acceptNullable(String)bloom_filterZSTD(3)acceptLanguageNullable(String)bloom_filterZSTD(3)queryKeysNullable(String)bloom_filterZSTD(3)ipReputation(placeholder)LowCardinality(Nullable(String))set(0)postalCodeNullable(String)bloom_filterlatitudeNullable(String)longitudeNullable(String)timeZoneLowCardinality(Nullable(String))set(0)weatherCodeLowCardinality(Nullable(String))set(0)Type rationale
LowCardinality+set(0)forprotocol/timeZone/weatherCode— intrinsically bounded value sets (protocol enum, ~400 IANA zones, code enum). Dictionary encoding + unlimited-set equality index are ideal and cheap.Nullable(String)+bloom_filterforaccept/acceptLanguage/queryKeys— these store raw, un-normalized caller input (fullAccept/Accept-Languageheaders, arbitrary query-key names), so their distinct count is unbounded.LowCardinalitywould risk dictionary/insert bloat at 6B rows;bloom_filterserves the exact-equality filters without a cardinality bet.ZSTD(3)compresses the high-entropy text (matching siblingcity/isp).postalCode— high-cardinality, equality lookups →bloom_filter.latitude/longitude— display-only; nobody filters exact coordinates and a breakdown is meaningless → no index.ipReputation— placeholder, not populated yet. Holds a bounded IP-reputation verdict (clean/low/suspicious/block), exact-matched →LowCardinality+set(0), shaped correctly now so no second migration is needed when the producer is ready.Indexes
set(0):protocol,ipReputation,timeZone,weatherCodebloom_filter:accept,acceptLanguage,queryKeys,postalCodelatitude,longitudeaccept/queryKeys(size 1024) carry a 255-char prefix (lengths) for the SQL adapter's 768-byte index-key limit; ClickHouse ignores the prefix and indexes the full value.Index definitions live in
Metric::getEventIndexes(). There is no auto-migration:createTable()emits them on fresh tables only. On the existing FRA table they are applied via a separately-runALTER TABLE … ADD INDEX IF NOT EXISTS(metadata-only), and theZSTDcodec viaALTER TABLE … MODIFY COLUMN.Storage cost (FRA: 6B+ rows, 1TB+)
Existing 6B rows were added via metadata-only
ALTER ADD COLUMN, so they store NULL and cost ~nothing per value — the 1TB does not jump. Growth is incremental. Premium-geo is sparse (~20% of rows assumed); request attrs on ~100% of request rows.Columns — per 1B new rows (~29 GB total):
accept~13,queryKeys~8,acceptLanguage~4.5 (all ZSTD(3) — saves ~20 GB/1B vs LZ4),postalCode+lat+long~3.4,protocol/timeZone/weatherCode~0.3.Indexes — ~8–9 GB @ 6B (~1.4 GB/1B): dominated by the
queryKeysbloom (~5.5 GB); accrues only on new/merged parts unlessMATERIALIZE INDEXis run over history.ipReputationis unpopulated today → stores NULL, costs ~nothing (its populated cost is added separately below, since it lands later).Later: when
ipReputationis populated (added on top of the total above)ipReputationships as an unpopulated placeholder, so it contributes nothing until the producer is wired up. Once it carries values it's a ~5-value verdict enum (clean/low/suspicious/block+ unknown):LowCardinality)set(0)index (≤5 entries/granule)Effectively free — a 5-value dictionary compresses the reference stream to well under 0.1 B/row, and the per-granule set holds at most the handful of distinct verdicts. Even at full 6B-row coverage it adds well under 1 GB on top of the totals above.
Bottom line
The FRA 1 TB does not jump — history stays NULL (~free). Going 6B → 8B ≈ +0.06 TB (~60 GB). The design is the cardinality-safe option:
LowCardinalityonly where value sets are genuinely bounded,bloom_filter+ZSTD(3)for un-normalized text (equality-serving, no cardinality bet), and no wasted indexes on display-only coordinates.ALTER ADD COLUMN/ADD INDEXare metadata-only and safe at this scale; the only heavy operation (MATERIALIZE INDEXover history) is left as an explicit, out-of-band ops choice.🤖 Generated with Claude Code
https://claude.ai/code/session_01QssJBktSLWpuEuWzVB23do