Skip to content

fix(app): resolve row details with PK + whichever block column the table persists - #3364

Merged
kodiakhq[bot] merged 4 commits into
mainfrom
cursor/pk-block-offset-row-lookup-d062
Oct 13, 2026
Merged

kodiakhq[bot] merged 4 commits into
mainfrom
cursor/pk-block-offset-row-lookup-d062

Conversation

@knudtty

@knudtty knudtty commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Opening log details on large text-log tables is much slower than the search itself (HDX-5696). The side panel already has a fast lookup that filters only on primary key + partition + block columns, but it was gated on the table persisting both _block_number and _block_offset. The affected table only sets enable_block_offset_column = 1, so every click fell back to the full-row WHERE, which filters on every selected column (including Body) and triggers expensive index loading.

This PR relaxes the gate: the fast path is taken when the table persists either block column, and the WHERE uses whichever of _block_number / _block_offset is available. Combined with the primary key, either one is unique enough in practice: two rows would need identical key values (including a nanosecond timestamp) and the same insert block or the same offset within their blocks. Tables that persist both columns keep the current behavior; tables with neither still fall back to __hdx_id / the full row.

Changes:

  • getPersistedBlockColumns(engineFull) in DBRowTable.tsx decides which block columns to inject; useConfigWithAdditionalSelect uses it for both the extra SELECT keys and rowKeyColumns.
  • Unit tests for the helper and renderHook coverage of useConfigWithAdditionalSelect (offset only, number only, both, __hdx_id fallback, no sourceId).
  • Changeset for @hyperdx/app.

Known trade-off: rows with identical PK that also share the block number (or the offset) still collide and LIMIT 1 picks one; the thread on the Linear issue accepted that given nanosecond timestamps.

Verified against ClickHouse 26.8 with tables that set only one of the two settings: engine_full exposes the setting as matched here, the persisted column survives OPTIMIZE FINAL unchanged, and the WHERE shape HyperDX now generates (PK expressions + the block column, no Body) returns the clicked row.

How to test on Vercel preview

N/A — the demo tables persist both block columns, so the fast path was already taken there; this change only affects tables that persist one of them. To verify locally, create a table with SETTINGS enable_block_offset_column = 1 (or enable_block_number_column = 1) alone, add it as a log source, click a row and check the side-panel query filters on PK columns + that block column only.

References

Linear Issue: HDX-5696

Open in Web Open in Cursor 

…e_block_offset_column is set

Tables that persist _block_offset but not _block_number fell back to the
full-row lookup, which filters on every selected column (incl. Body) and
is far slower than the search itself. PK + _block_offset is unique enough
with nanosecond timestamps, so require only the offset column and add
_block_number when it is also persisted.

Co-authored-by: Aaron Knudtson <knudtty@users.noreply.github.com>
@vercel

vercel Bot commented Oct 13, 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 Oct 13, 2026 6:04pm UTC
hyperdx-storybook Ready Ready Preview Oct 13, 2026 6:04pm UTC

Request Review

@changeset-bot

changeset-bot Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 05048a6

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

This PR includes changesets to release 3 packages
Name Type
@hyperdx/app 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

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 125 in total (1.75%).
Run yarn dupes locally to reproduce, or yarn dupes:all for the full picture.

@greptile-apps

greptile-apps Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[Medium impact] The PR appears safe to merge with its documented chance of row-key collisions.

Summary

Row details now use the primary key, partition keys, and whichever block columns the table persists.

  • Row details use whichever persisted block column the table has.

Reviews (4) · Last reviewed commit: "Merge branch 'main' into cursor/pk-block..." · Reviewed by Greptile

…olumn is set

Co-authored-by: Aaron Knudtson <knudtty@users.noreply.github.com>
@cursor cursor Bot changed the title fix(app): resolve row details with PK + _block_offset when only enable_block_offset_column is set fix(app): resolve row details with PK + whichever block column the table persists Oct 13, 2026
@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

E2E Test Results

✅ All tests passed • 406 passed • 1 skipped • 1595s

Status Count
✅ Passed 406
❌ Failed 0
⚠️ Flaky 2
⏭️ Skipped 1

Tests ran across 4 shards in parallel.

View full report →

@knudtty
knudtty marked this pull request as ready for review October 13, 2026 17:10
@github-actions github-actions Bot added the review/tier-2 Low risk — AI review + quick human skim label Oct 13, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🔵 Tier 2 — Low Risk

Small, isolated change with no API route or data model modifications.

Why this tier:

  • Standard feature/fix — introduces new logic or modifies core functionality

Review process: AI review + quick human skim (target: 5–15 min). Reviewer validates AI assessment and checks for domain-specific concerns.
SLA: Resolve within 4 business hours.

Stats
  • Production files changed: 1
  • Production lines changed: 35 (+ 178 in test files, excluded from tier calculation)
  • Branch: cursor/pk-block-offset-row-lookup-d062
  • Author: knudtty

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

@github-actions

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 · 🟠 1 major · 🔵 0 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

Copy link
Copy Markdown
Contributor

Deep Review

Relaxing the fast row-detail lookup to fire on either persisted block column is a sound performance fix, and the dedicated reviewer confirmed it achieves the HDX-5696 goal with no perf regression (sargability comes from the PK/partition predicates; Body stays out of the WHERE in both the one- and two-column cases). One correctness trade-off in the _block_number-only path is worth surfacing before merge.

🟡 P2 — recommended

  • packages/app/src/components/DBRowTable.tsx:1493 — When a table persists only _block_number, the row-detail WHERE (primary key + partition + _block_number, with Body dropped) can match multiple rows, because _block_number is constant for every row in one insert block and so adds no intra-block disambiguation; LIMIT 1 then can show a different row's details, whereas these tables previously used the correct full-row fallback.
    • Fix: For the _block_number-only case, keep the full-row fallback (or require a per-row-unique column such as _block_offset/__hdx_id), since unlike the offset-only path uniqueness collapses entirely onto the primary key within a single insert block.
    • ce-correctness-reviewer, ce-previous-comments-reviewer
🔵 P3 nitpicks (1)
  • packages/app/src/components/DBRowTable.tsx:1493 — getPersistedBlockColumns detects persistence via engineFull.includes('enable_block_number_column = 1'), coupling it to ClickHouse's exact single-space = 1 rendering of engine_full; a format drift would silently disable the fast path (fails safe to the slow lookup, not incorrect results), and the matching style predates this PR.
    • Fix: Use a whitespace-tolerant regex such as /enable_block_number_column\s*=\s*1/ and add a negative test for a non-canonical format.

Reviewers (6): ce-correctness-reviewer, ce-testing-reviewer, ce-maintainability-reviewer, ce-performance-reviewer, ce-kieran-typescript-reviewer, ce-previous-comments-reviewer.

Testing gaps:

  • No end-to-end test runs a single-block-column rowKeyColumns through useRowWhere to assert the generated SQL contains the block column and excludes Body — the halves are tested in isolation (rowKeyColumns membership in DBRowTable.test.tsx, WHERE filtering in useRowWhere.test.tsx, but every useRowWhere SQL test uses both block columns).
  • The number-only and both-columns tests assert only has() membership; tighten to a full toEqual(new Set([...])) like the offset-only case so an unexpected leaked column would fail.
  • getPersistedBlockColumns whitespace/substring edge cases (e.g. enable_block_number_column=1, = 0, prefix-name collisions) are untested.
  • Uncovered useConfigWithAdditionalSelect branches: the null metadata early return, and sourceId present with neither a block column nor __hdx_id.

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

@pulpdrew pulpdrew left a comment

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.

LGTM

@kodiakhq
kodiakhq Bot merged commit d341ba0 into main Oct 13, 2026
29 checks passed
@kodiakhq
kodiakhq Bot deleted the cursor/pk-block-offset-row-lookup-d062 branch October 13, 2026 18:09
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

This branch was successfully deployed

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

Labels

automerge review/tier-2 Low risk — AI review + quick human skim

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants