fix: change metrics PK for better memory usage#2545
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: 4e5a5f1 The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
🔴 Tier 4 — CriticalTouches auth, data models, config, tasks, OTel pipeline, ClickHouse, or CI/CD. Why this tier:
Review process: Deep review from a domain expert. Synchronous walkthrough may be required. Stats
|
Greptile SummaryThis PR optimises the ClickHouse metrics schema to reduce primary-key memory usage and improve time-range pruning, paired with a query-generation fix that correctly applies
Confidence Score: 5/5Safe to merge — schema changes apply to fresh installations only and the query-generation fix is well-covered by the new test case. All five metrics tables receive the same consistent treatment. The Delta codec without an explicit byte-width correctly auto-sizes to 4 bytes for DateTime columns. The renderChartConfig refactor only changes behavior for the previously-unhandled case where both includedDataInterval and a toStartOf* PK expression are active, and that case is now exercised by the new test. No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[timeFilterExpr called] --> B{includedDataInterval set?}
B -- Yes --> C["rawBound = toStartOfInterval(fromUnixTimestamp64Milli(t), INTERVAL X) ± INTERVAL X"]
B -- No --> D["rawBound = fromUnixTimestamp64Milli(t)"]
C --> E{toStartOf* in PK expr?}
D --> E
E -- Yes --> F["timeCond = toStartOfHour(rawBound)\n(wraps interval-expanded bound for PK pruning)"]
E -- No --> G["timeCond = rawBound"]
F --> H{isDateType?}
G --> H
H -- Yes --> I["WHERE col >= toDate(timeCond)\nAND col <= toDate(timeCond)"]
H -- No --> J["WHERE col >= timeCond\nAND col <= timeCond"]
I --> K[AND all sub-expressions together]
J --> K
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[timeFilterExpr called] --> B{includedDataInterval set?}
B -- Yes --> C["rawBound = toStartOfInterval(fromUnixTimestamp64Milli(t), INTERVAL X) ± INTERVAL X"]
B -- No --> D["rawBound = fromUnixTimestamp64Milli(t)"]
C --> E{toStartOf* in PK expr?}
D --> E
E -- Yes --> F["timeCond = toStartOfHour(rawBound)\n(wraps interval-expanded bound for PK pruning)"]
E -- No --> G["timeCond = rawBound"]
F --> H{isDateType?}
G --> H
H -- Yes --> I["WHERE col >= toDate(timeCond)\nAND col <= toDate(timeCond)"]
H -- No --> J["WHERE col >= timeCond\nAND col <= timeCond"]
I --> K[AND all sub-expressions together]
J --> K
Reviews (6): Last reviewed commit: "Merge branch 'main' into aaron/metrics-r..." | Re-trigger Greptile |
E2E Test Results✅ All tests passed • 221 passed • 3 skipped • 1447s
Tests ran across 4 shards in parallel. |
| `ScopeSchemaUrl` String CODEC(ZSTD(1)), | ||
| `ServiceName` LowCardinality(String) CODEC(ZSTD(1)), | ||
| `MetricName` String CODEC(ZSTD(1)), | ||
| `MetricName` LowCardinality(String) CODEC(ZSTD(1)), |
There was a problem hiding this comment.
Changing to LowCardinality reduces PK memory usage by 2-3x
There was a problem hiding this comment.
Seems to be a good change. I wonder why not apply it to the MetricName to all other metrics tables?
There was a problem hiding this comment.
Good callout. That was a mistake.
| `StartTimeUnix` DateTime64(9) CODEC(Delta(8), ZSTD(1)), | ||
| `TimeUnix` DateTime64(9) CODEC(Delta(8), ZSTD(1)), | ||
| `StartTimeUnix` DateTime CODEC(Delta(8), ZSTD(1)), | ||
| `TimeUnix` DateTime CODEC(Delta(8), ZSTD(1)), | ||
| `Value` Float64 CODEC(ZSTD(1)), | ||
| `Flags` UInt32 CODEC(ZSTD(1)), | ||
| `Exemplars.FilteredAttributes` Array(Map(LowCardinality(String), String)) CODEC(ZSTD(1)), | ||
| `Exemplars.TimeUnix` Array(DateTime64(9)) CODEC(ZSTD(1)), | ||
| `Exemplars.TimeUnix` Array(DateTime) CODEC(ZSTD(1)), |
There was a problem hiding this comment.
Second precision is an appropriate default and reduces the storage unit from 8 bytes -> 4 bytes
| INDEX idx_attr_value mapValues(Attributes) TYPE bloom_filter(0.01) GRANULARITY 1 | ||
| INDEX idx_attr_value mapValues(Attributes) TYPE bloom_filter(0.01) GRANULARITY 1, | ||
| INDEX idx_time_minmax TimeUnix TYPE minmax GRANULARITY 1 |
There was a problem hiding this comment.
minmax is an extremely lightweight index and helps efficiently prune granules, even if the PK is not fully qualified for a query to take advantage of TimeUnix
| ENGINE = MergeTree | ||
| PARTITION BY toDate(TimeUnix) | ||
| ORDER BY (ServiceName, MetricName, Attributes, toUnixTimestamp64Nano(TimeUnix)) | ||
| ORDER BY (ServiceName, MetricName, toStartOfHour(TimeUnix), cityHash64(Attributes), TimeUnix) |
There was a problem hiding this comment.
Time bucketing before the high cardinality Attributes column groups data more efficiently speeding up I/O for queries due to less overall ranges and is very efficient in PK memory
| ORDER BY (ServiceName, MetricName, Attributes, toUnixTimestamp64Nano(TimeUnix)) | ||
| ORDER BY (ServiceName, MetricName, toStartOfHour(TimeUnix), cityHash64(Attributes), TimeUnix) |
There was a problem hiding this comment.
Hashing the Attributes column massively reduces PK memory
There was a problem hiding this comment.
why is that? Is it because some attribute key or values are too long?
There was a problem hiding this comment.
Yes, both the length of keys or values as well as the number in a single entry. Additionally, ClickHouse does well with known sized parameters, like the 64 bit hash value
Deep ReviewScope: Metrics ClickHouse schema redesign ( Intent (author-stated, advisory): reduce metrics primary-key memory usage via a new The 🔴 P0/P1 — must fix
🟡 P2 — recommended
🔵 P3 nitpicks (3)
Reviewers (10): correctness, adversarial, data-migrations, performance, kieran-typescript, testing, maintainability, project-standards, agent-native, learnings-researcher. Testing gaps: No fixture exercises the rate/cumulative CTE against same-second-truncated |
Summary
Change the default schema and PK for metrics to reduce memory usage and introduce a lightweight minmax for better time filtering.
References