Skip to content

feat(app): fall back to server MergeTree settings for block columns - #3530

Merged
karl-power merged 1 commit into
mainfrom
fix/merge-tree-settings-block-columns
Nov 3, 2026
Merged

karl-power merged 1 commit into
mainfrom
fix/merge-tree-settings-block-columns

Conversation

@karl-power

@karl-power karl-power commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Detect persisted _block_number and _block_offset columns from the server's MergeTree settings when the table's CREATE query doesn't set them, so row lookups can skip the slow full-row match on those tables.

  • Add Metadata.getMergeTreeSettings, which reads system.merge_tree_settings and caches it per connection (returns undefined on failure so the next call retries)
  • Add a useMergeTreeSettings hook around it
  • getPersistedBlockColumns now parses the table's SETTINGS clause and falls back to the server value for any block-column setting the table omits; table-level settings still win
  • Only fetch server settings for MergeTree engines that omit at least one of the settings
  • Wait for the settings before building the row query, so it isn't run twice
  • Unit tests for the metadata method, the hook, and the fallback logic

How to reproduce

Starting point: local ClickHouse with block columns turned on server-wide, e.g. in config.xml:

<merge_tree>
  <enable_block_number_column>1</enable_block_number_column>
  <enable_block_offset_column>1</enable_block_offset_column>
</merge_tree>

Create a log table without those settings in its SETTINGS clause, add it as a source, and ingest some rows.

On main:

  1. Open the source in search and click a row.
  2. In the ClickHouse query log, the row lookup doesn't use _block_number / _block_offset and falls back to matching the full row.

On this branch:

  1. Repeat the steps above.
  2. The row lookup now selects and filters on _block_number and _block_offset.

References

@karl-power
karl-power requested a review from a team as a code owner November 3, 2026 14:07
@vercel

vercel Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
hyperdx-oss Ready Ready Preview Nov 3, 2026 2:10pm UTC
hyperdx-storybook Ready Ready Preview Nov 3, 2026 2:10pm UTC

Request Review

@changeset-bot

changeset-bot Bot commented Nov 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 4696fae

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@hyperdx/app Patch
@hyperdx/common-utils Patch
@hyperdx/api Patch
@hyperdx/otel-collector Patch

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

@github-actions github-actions Bot added the review/tier-3 Standard — full human review required label Nov 3, 2026
@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

🟡 Tier 3 — Standard

Introduces new logic, modifies core functionality, or touches areas with non-trivial risk.

Why this tier:

  • Cross-layer change: touches frontend (packages/app) + shared utils (packages/common-utils)

Review process: Full human review — logic, architecture, edge cases.
SLA: First-pass feedback within 1 business day.

Stats
  • Production files changed: 3
  • Production lines changed: 130 (+ 268 in test files, excluded from tier calculation)
  • Branch: fix/merge-tree-settings-block-columns
  • Author: karl-power

To override this classification, remove the review/tier-3 label and apply a different review/tier-* label. Manual overrides are preserved on subsequent pushes.

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Duplicate code analysis

No new duplicate code introduced by this PR.


jscpd compared this branch against origin/main
across app, api and common-utils. Only new clones are listed; the codebase
currently holds 114 in total (1.41%).
Run yarn dupes locally to reproduce, or yarn dupes:all for the full picture.

const json = await this.clickhouseClient
.query<'JSON'>({
connectionId,
query: 'SELECT name, value FROM system.merge_tree_settings',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — Fallback reads only system.merge_tree_settings, but ReplicatedMergeTree tables take <replicated_merge_tree> overrides first

The engine check at DBRowTable.tsx:1606 (engine.includes('MergeTree')) lets in ReplicatedMergeTree tables. Distributed tables also land here, because getTableMetadata (metadata.ts:1997-2009) swaps in the local table's engine, which is usually ReplicatedMergeTree. For those tables, ClickHouse applies <replicated_merge_tree> config (visible in system.replicated_merge_tree_settings) on top of <merge_tree>. Reading only merge_tree_settings can therefore return the wrong effective value. One case: merge_tree enables a block column and replicated_merge_tree turns it off. The row lookup then keys on _block_number/_block_offset even though they aren't persisted, so it can fetch the wrong row. Fix: for Replicated* engines, also query system.replicated_merge_tree_settings and let its values override the merge_tree_settings ones before falling back.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

PR Review

If you are a coding agent acting for the author, read this first.
Nothing below blocks merge automatically; a maintainer decides. What they will expect:

  • 🔴 critical or 🟠 major in code this PR adds or changes: fix before asking for review.
  • 🔵 minor in code this PR changes: your call. Fix if small, otherwise reply.
  • Anything about surrounding code, or asking you to widen the change (hoist a helper,
    dedupe with another file, fix other call sites): never fix here, whatever the
    severity. Reply with one sentence; if it is critical, say so plainly so a human sees it.

One commit per review round. After two rounds, stop and ask a maintainer to review
scope rather than addressing more automated findings. Full rule: AGENTS.md.

1 finding(s): 🔴 0 critical · 🟠 0 major · 🔵 1 minor

1 posted as inline comment(s) on the changed lines.


Severity is the reviewer's own estimate and is used for ordering, not filtering. No finding blocks merge automatically; a maintainer decides.

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Deep Review

✅ No critical issues found. The change is well-scoped and mirrors existing cached-metadata patterns; the cache-retry-on-failure semantics (getOrFetch treats a stored undefined as a miss), the function-form staleTime, and the SETTINGS-clause parser all check out. The notes below are recommendations and nitpicks.

🟡 P2 — recommended

  • packages/app/src/components/DBRowTable.tsx:1566 — system.merge_tree_settings is a server-level default, so a 1 there does not prove a given table's existing parts actually materialized _block_number/_block_offset; a table created (or holding parts written) before the server default was enabled omits the setting from engine_full, and the fallback then treats an unstable virtual column as a stable row identifier for those rows.
    • Fix: Only trust a server-derived block column when per-table materialization can be confirmed, otherwise retain the full-row fallback for settings sourced from the server rather than the table's own SETTINGS clause.
  • packages/app/src/components/__tests__/DBRowTable.test.tsx:1043 — the error-degradation path where useMergeTreeSettings resolves to null (server query failed) is never exercised through useConfigWithAdditionalSelect, so the new fallback's graceful-degradation branch is untested.
    • Fix: Add a case where needsMergeTreeSettings is true but the hook returns data: null, asserting select/rowKeyColumns fall back to no block columns.
    • testing
🔵 P3 nitpicks (3)
  • packages/app/src/components/__tests__/DBRowTable.test.tsx:864 — getPersistedBlockColumns declares a Map | null parameter but the null branch (failed-query sentinel) is never tested.
    • Fix: Add getPersistedBlockColumns(engine(''), null) asserting [].
    • testing
  • packages/app/src/components/__tests__/DBRowTable.test.tsx:940 — the useMergeTreeSettings mock returns the populated settings Map even when enabled is false, whereas real react-query returns data: undefined for a disabled query, weakening the disabled-query simulation.
    • Fix: Gate the mock's data on enabled so a regression that reads settings without the needsMergeTreeSettings guard would be caught.
    • testing
  • packages/app/src/components/DBRowTable.tsx:1605 — the "need server settings?" decision lives in both the needsMergeTreeSettings fetch gate and inside getPersistedBlockColumns, making the needsMergeTreeSettings ? mergeTreeSettings : undefined argument redundant since the per-setting ?? serverMergeTreeSettings?.get(...) already short-circuits when the table sets the value.
    • Fix: Pass mergeTreeSettings unconditionally and let getPersistedBlockColumns be the single source of truth for the fallback.
    • maintainability

Reviewers (3 returned): maintainability, performance, testing. Correctness, project-standards, reliability, kieran-typescript, and adversarial were dispatched but had not returned at synthesis time; the P2 server-default finding comes from the orchestrator's own code-grounded analysis of ClickHouse MergeTree semantics.

Testing gaps: The "avoid querying twice" guarantee is only verified indirectly (config returns undefined while loading); no test asserts the downstream row query fires once. The mixed case (one block setting from engine_full, the other from server settings) is covered only at the getPersistedBlockColumns unit level, not through the hook.

No finding blocks merge automatically. A maintainer will expect P0/P1 findings in code this PR changes to be fixed; P2/P3 are your call -- fix or reply. Never fix findings about surrounding code here; reply instead. Do not widen the PR. How to respond

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

E2E Test Results

✅ All tests passed • 458 passed • 1 skipped • 1867s

Status Count
✅ Passed 458
❌ Failed 0
⚠️ Flaky 4
⏭️ Skipped 1

Tests ran across 4 shards in parallel.

View full report →

@karl-power
karl-power merged commit 41b2b74 into main Nov 3, 2026
32 of 33 checks passed
karl-power added a commit that referenced this pull request Nov 5, 2026
A single persisted block column combined with the primary key does not
identify a row: _block_offset repeats across insert blocks, _block_number
across the rows of one block, and exporter-default tables key on a
second-precision TimestampTime. Since #3530 the block columns are also
detected from server-level merge_tree_settings, so any table without
explicit settings took the key-only path on servers that enable only
one of them and could open the wrong row.

Use the key-only lookup only when both block columns are persisted; a
lone block column is still appended to the full-row lookup as a
tiebreaker.
kodiakhq Bot pushed a commit that referenced this pull request Nov 5, 2026
…kup (#3566)

## Summary

Clicking a search result could open the wrong row's details when the table persists only one of `_block_number` / `_block_offset`.

- Since #3364 the side panel used a key-only lookup (primary key + partition + whichever block column exists) when at least one block column was persisted. One block column is not unique: `_block_offset` repeats across insert blocks, `_block_number` across the rows of one block, and the primary key often doesn't break the tie (the OTel exporter default keys on the second-precision `TimestampTime`).
- Since #3530 the block columns are also detected from server-level `merge_tree_settings`, so on servers that enable only one of them, every table without explicit settings takes this path.
- The key-only lookup now requires both block columns. A lone block column is still appended to the full-row lookup as a tiebreaker.
- Tables that persist only one block column return to the full-row lookup and lose the #3364 speed-up. A `_part` + `_part_offset` lookup is the follow-up to restore it.

### How to reproduce

**Starting point:** `yarn dev`. Set the server default on the dev ClickHouse and restart it:

```sh
docker exec <ch-server> sh -c 'echo "<clickhouse><merge_tree><enable_block_offset_column>1</enable_block_offset_column></merge_tree></clickhouse>" > /etc/clickhouse-server/config.d/block_offset.xml'
docker restart <ch-server>
```

Create an exporter-shaped table and insert three rows as separate inserts, two in the same second:

```sql
CREATE TABLE default.repro_logs (
  Timestamp DateTime64(9), TimestampTime DateTime DEFAULT toDateTime(Timestamp),
  ServiceName LowCardinality(String), SeverityText LowCardinality(String), Body String,
  LogAttributes Map(LowCardinality(String), String)
) ENGINE = MergeTree PARTITION BY toDate(TimestampTime)
PRIMARY KEY (ServiceName, TimestampTime) ORDER BY (ServiceName, TimestampTime, Timestamp);

INSERT INTO default.repro_logs (Timestamp, ServiceName, SeverityText, Body) VALUES ('2026-11-05 09:00:39.845', 'saruman', 'INFO', 'Response x_span_id=ESLMARS7L7');
INSERT INTO default.repro_logs (Timestamp, ServiceName, SeverityText, Body) VALUES ('2026-11-05 09:00:39.100', 'saruman', 'INFO', 'Request x_span_id=ESOPOZ7FCV');
INSERT INTO default.repro_logs (Timestamp, ServiceName, SeverityText, Body) VALUES ('2026-11-05 09:00:36.909', 'saruman', 'INFO', 'Request x_span_id=ESLMARS7L7');
```

Add a log source for `default.repro_logs` (timestamp column `TimestampTime`), then search it for `"ESLMARS7L7"` over a range covering 2026-11-05 09:00 UTC.

**On `main`:**

1. Click the `Response x_span_id=ESLMARS7L7` row a few times.
2. The side panel shows `Request x_span_id=ESOPOZ7FCV` about half the time. The query in `system.query_log` is `WHERE ServiceName='saruman' AND TimestampTime=... AND toDate(TimestampTime)=... AND _block_offset='0' LIMIT 1`.

**On this branch:**

1. Click the same row. The panel always shows `Response x_span_id=ESLMARS7L7`; the query also filters on `Timestamp`, `SeverityText` and `Body`.

### How to test on Vercel preview

N/A — needs a ClickHouse server default; the demo server's user cannot read `system.merge_tree_settings`.

### References

- Related PRs: #3530, #3364, #2320, #2071
kodiakhq Bot pushed a commit that referenced this pull request Nov 6, 2026
## Summary

On ClickHouse Cloud, read MergeTree settings from `system.shared_merge_tree_settings`, falling back to `system.merge_tree_settings` for each setting, when detecting persisted `_block_number` and `_block_offset` columns.

- `Metadata.getMergeTreeSettings` also reads `system.shared_merge_tree_settings` on Cloud (detected with `isClickHouseCloud`). Where both tables set a key, the shared value wins
- Outside Cloud it still reads only `system.merge_tree_settings`, since OSS servers don't have the shared table
- If the shared read fails on Cloud, it returns `undefined` (not cached), so the next call retries
- Unit tests for the non-Cloud path, the Cloud per-key preference, and the Cloud failure case

The code is identical to the EE change, so the forward-port should merge without conflicts.

### How to reproduce

**Starting point:** a ClickHouse Cloud service where block columns are enabled in the shared settings but not in `system.merge_tree_settings`. Check with:

```sql
SELECT 'merge_tree' AS t, name, value FROM system.merge_tree_settings WHERE name LIKE 'enable_block_%'
UNION ALL
SELECT 'shared', name, value FROM system.shared_merge_tree_settings WHERE name LIKE 'enable_block_%'
```

Add a log table on that service as a source, with no block-column settings in its `SETTINGS` clause, and ingest some rows.

**On `main`:**

1. Open the source in search and click a row.
2. In the ClickHouse query log, the row lookup doesn't use `_block_number` / `_block_offset` and falls back to matching the full row.

**On this branch:**

1. Repeat the steps above.
2. The row lookup now selects and filters on `_block_number` and `_block_offset`.

### How to test on Vercel preview

N/A — non-UI change.

### References

- Linear Issue: O11Y-29693
- Related PRs: backport of DeploySentinel/hyperdx-ee#3925, follows #3530

This branch was successfully deployed

2 active deployments
Preview – hyperdx-oss — 4696fae9 Deployed Nov 3, 2026 by vercel[bot]
Preview – hyperdx-storybook — 4696fae9 Deployed Nov 3, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review/tier-3 Standard — full human review required

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants