Skip to content

spans: drop the generated duration_ms column (schema v10) - #88

Closed
Fl0p wants to merge 2 commits into
mainfrom
flo-574-spans-drop-duration-ms
Closed

spans: drop the generated duration_ms column (schema v10)#88
Fl0p wants to merge 2 commits into
mainfrom
flo-574-spans-drop-duration-ms

Conversation

@Fl0p

@Fl0pFl0p commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Root-cause fix for the DuckDB filter-pushdown wrong-results trap, per ADR-0013.

What changed

  • schema.sql → version 10.duration_ms is gone from CREATE TABLE spans, and the migration section drops it from existing databases. DuckDB refuses to ALTER a table an index depends on, so the four secondary indexes are dropped first and the existing CREATE INDEX IF NOT EXISTS block rebuilds them. No backfill, no UPDATE, no row data moved.
  • Four query sites compute the duration inlineCAST(epoch_ms(end_time) - epoch_ms(start_time) AS DOUBLE), aliased back to duration_ms: the session spans projection, the tools stats CTE, the bash-commands CTE, and the retention roll-up. The CAST keeps the expression DOUBLE exactly as the old column was, so SUM does not become HUGEINT and no scan target changes.
  • The COALESCE(tool_name, '') = 'Bash' workaround is removed along with the comment block explaining it. It was engine-fragile: DuckDB 1.4+ pushes COALESCE into the scan too.
  • pushdownBrokenSpanCols is now empty and TestSpansEqualityUnderFilterPushdown passes with it empty — every VARCHAR column of spans agrees under bare equality. The guard test stays as the detector for a reintroduced generated column.
  • New TestUpgradeDropsGeneratedDurationColumn seeds a populated v9 database (generated column mid-table + the four indexes) and asserts the migration removes the column, keeps the rows, and makes tool_name = 'Bash' correct. That test is what caught the index dependency — without the DROP INDEX statements, storage.Open fails outright on every existing database with Dependency Error: Cannot alter entry "spans".

No response shape changes: duration_ms / avg_duration_ms keep their JSON names and values, and the export ZIP is untouched (writeSpansCSV already derived the duration in Go, and the spans export query never selected the column), so format_version does not move. The importer reads by header name and never inserted the column.

Verification

go test ./... and go vet ./... green (Docker golang:1.23-bookworm, arm64 — no native Go toolchain on this box). VitePress docs build green.

Against a docker cp copy of the production database (108 MB + 0.4 MB WAL, 34 706 spans, DuckDB v1.1.3):

before migrationafter
schema_version910
spans rows34 70634 706
duration_ms declaredyesno
WHERE tool_name = 'Bash'06 440
WHERE service_name = 'claude-code'034 705
indexes on spans44

Timings on that copy: raw open (WAL replay only) 9.48 s → storage.Open with the migration 9.84 s, so the whole upgrade adds ~0.4 s to a cold start dominated by replay. A subsequent open with the hash already recorded is 9 ms, and a forced full re-apply of schema.sql (what every future schema edit costs now, including the index rebuild) is 138 ms. The file shrank 108.3 MB → 98.3 MB as the WAL folded in on close.

Docs

  • CHANGELOG: one Fixed entry for the wrong results and one Changed entry for the v10 migration, its measured cost, and the absence of a downgrade path.
  • ADR-0001's "Known engine trap" section rewritten: the trap is now described as a property of generated columns on indexed tables rather than of the current spans layout, plus the ALTER/index dependency finding.

ADR-0013 is referenced by name in docs/decisions/0001-storage.md without a link, since the file lands in #87 and a relative link to a not-yet-merged page fails the docs build. The CHANGELOG entry does link it — worth merging #87 first.

Closes FLO-574.

… nothing
duration_ms was a VIRTUAL generated column declared mid-table. It takes a
logical slot but no storage slot, so every column after it had a logical index
one ahead of its physical index, and `WHERE col = <constant>` on such a column
probed an unrelated ART index and matched nothing - a silent wrong answer that
reads correctly in review. That is why /api/v1/bash-commands shipped
permanently empty.
Schema version 10 drops the column and the four queries that read it compute
epoch_ms(end_time) - epoch_ms(start_time) instead, aliased back to duration_ms
so no response shape or sort key moves. The COALESCE(tool_name, '') workaround
goes with it: DuckDB 1.4+ pushes COALESCE into the scan too, so it was on
borrowed time.
DuckDB refuses to ALTER a table an index depends on, so the migration drops the
four secondary indexes first and the existing CREATE INDEX IF NOT EXISTS block
rebuilds them. It moves no row data.
Verified against a copy of production (108 MB, 34 706 spans): tool_name =
'Bash' counted 0 rows before and 6 440 after, service_name = 'claude-code' 0
before and 34 705 after, span count unchanged; storage.Open went 9.5s -> 9.8s
against a cold start dominated by WAL replay, and a later re-apply of
schema.sql costs 138 ms. The new upgrade test covers the same path on a seeded
v9 database, and the pushdown guard now passes with an empty broken-column list.
Co-Authored-By: Wayland <wayland@agents.flopbut.local>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Aug 14, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@Fl0p, you've reached your PR review limit, so we couldn't start this review.

Next review available in:103 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ad2062a1-3112-448f-a3f8-1176676daad9

📥 Commits

Reviewing files that changed from the base of the PR and between 1dcbc09 and aadf9b8.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • docs/decisions/0001-storage.md
  • internal/api/handler.go
  • internal/api/handler_test.go
  • internal/storage/pushdown_test.go
  • internal/storage/retention.go
  • internal/storage/schema.sql

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…presence
handleSession ignores the Scan error, so a projection that no longer scans
would have returned zero-valued spans and still passed the len() > 0 check.
Pin the computed value instead.
Co-Authored-By: Wayland <wayland@agents.flopbut.local>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Fl0p

Fl0p commented Aug 14, 2026

Copy link
Copy Markdown
ContributorAuthor

Squash-merged to main as a555c65 (locally, so author and committer stay an agent identity rather than the shared OAuth). ADR-0013 landed first as ae1e954, so the CHANGELOG link resolves.

@Fl0pFl0p closed this Aug 14, 2026
@Fl0p
Fl0p deleted the flo-574-spans-drop-duration-ms branch August 14, 2026 17:08
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Fl0p