-
[OPIK-6747] [BE] perf: defer wide-column reads past pagination in traces/spans list queries (#6930)
发布于
2026-06-08 10:27:13 +00:00 - [OPIK-6747] [BE] perf: defer wide-column reads past pagination in traces/spans list queries
The list-by-project query (SELECT_BY_PROJECT_ID in TraceDAO and SpanDAO)
materialized all wide text columns (input, output, metadata, input_slim,
output_slim) for the ENTIRE filtered set in the *_deduped CTE, then applied
the page LIMIT/OFFSET only one CTE later in *_final. On large projects this
reads millions of rows and GiBs of column data to return a ~100-row page,
spiking ClickHouse memory (multiple GiB per query, tens of GiB on the largest
projects) and triggering OOM alerts; large projects can exceed the per-query
memory limit and fail outright (error 241).Fix: the *_deduped / *_final CTEs now carry only the columns needed to filter,
sort and dedup. The wide display columns (input, output, metadata,
truncated_input, truncated_output) are re-read from the base table via a new
page_wide CTE restricted to the paginated id set (id IN (SELECT id FROM
*_final)), preserving ReplacingMergeTree dedup (same ORDER BY + LIMIT 1 BY id).
input_slim/output_slim are dropped entirely (unused by this query's output).A sort_needs_wide flag (SortableFields.sortsByWideTextColumn) keeps
input/output/metadata in the pre-pagination scan only when the sort references
them, so sorting by those columns still works; the flag is set by a shared
FilterUtils.addSortNeedsWideFlag helper used by all renderers (paginated +
stream paths). page_wide and its join are omitted when input/output/metadata
are all excluded.Tests: regression test for sorting by a wide field while excluding that same
field, plus a unit test for SortableFields.sortsByWideTextColumn covering base
and dynamic-path (input.x) detection.Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- [OPIK-6747] Collapse traces/spans list query to a single deduped scan
The two-phase list query previously referenced the paginated set twice — once
as the final FROM and once inside page_wide'sid IN (...). Because ClickHouse
inlines CTEs, this re-evaluated the whole deduped/filtered scan a second time
(extra read I/O on wide-column filter/sort paths).Restructure into page_ids (deduped/ordered/limited ids — referenced once) ->
page_wide (full page rows read once for those ids) -> final SELECT reading
from page_wide directly. Each CTE is now referenced exactly once, so the
filtered scan runs a single time. Wide columns are still read only for the
~page-size rows.Applied to both TraceDAO and SpanDAO (preserving the stream vs non-stream
dedup/sort keys for spans). No behavioral change — validated by
GetTracesByProjectResourceTest, TracesResourceTest, and SpansResourceTest.Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- [OPIK-6747] [BE] Address review: full-content + paginated sort/exclude tests, @NonNull, query javadoc
Addresses review feedback on the deferred-wide-column sort/exclude query:
- Traces & spans: parameterized sort x exclude full-content tests, plus
paginated null/sparse all-pages tests that prove the page_ids pre-filter
carries the sort key (a single full page can't catch a broken pre-filter). - FilterUtils.addSortNeedsWideFlag: @NonNull on the template arg.
- SELECT_BY_PROJECT_ID: two-phase (page_ids/page_wide, deferred wide columns)
design javadoc on SpanDAO and TraceDAO. - SortableFields: WIDE_TEXT_FIELDS comment -> javadoc.
- Remove SortableFieldsTest: the util is now covered by the black-box tests.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- [OPIK-6747] [BE] Address review round 2: consolidate sort/exclude tests, tidy query comments
- SpanDAO/TraceDAO: drop the inline two-phase SQL comments (the rationale
already lives in the SELECT_BY_PROJECT_ID javadoc). - Tests: replace the overlapping whenSortingAndExcludingField + null/sparse
paginated tests with a single focused, deterministic, paginated
whenPaginatingSortedAndExcludingField (spans + traces). It sorts by a field
while excluding it, walks every page, and asserts the FULL page content vs
the expected sorted slice (not ids). No nulls, no AtomicInteger. - Move the per-page fetch helper into TraceResourceClient#getTracesByPage.
Verified over real ClickHouse; mutation-checked (dropping sort_fields from
page_ids fails the paginated tests).Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- [OPIK-6747] [BE] Address review: defensively dedup EXCEPT column lists
The two-phase query could emit EXCEPT lists that ClickHouse only tolerates
leniently: the same wide column in two EXCEPT clauses (when !sort_needs_wide
and that column is also excluded), and the final SELECT EXCEPT'ing
truncated_input/truncated_output even when truncate=false (those columns are
not in page_wide). A stricter CH EXCEPT impl would fail-compile the whole
list endpoint.Dedup, both DAOs (spans_deduped/traces_deduped, page_wide, final SELECT):
- gate the deferred wide-column drops on <if(!exclude_*)> so they don't
overlap the exclude_fields EXCEPT, - drop the redundant wide-column conditionals from page_wide (exclude_fields
already covers them), - gate input/output/metadata in the final SELECT on <if(!exclude_)> and
truncated_ on <if(truncate)>.
SQL EXCEPT is a read optimization; response-level exclusion is unchanged.
Regression-verified: sort (all fields), exclude (all fields), image
truncation, and pagination — 72 span + 73 trace tests green.Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- [OPIK-6747] [BE] Address review: gate truncated_* EXCEPT on field exclusion
The final SELECT EXCEPT'd truncated_input/truncated_output under <if(truncate)>
unconditionally, but page_wide only projects them when the matching wide field
is NOT excluded. So truncate=true + exclude input/output EXCEPT'd a column that
page_wide had dropped (ClickHouse tolerates it today, but it's fragile on a
stricter CH). Gate them on <if(!exclude_*)> to mirror page_wide, in both DAOs.Add whenTruncatingAndExcludingWideField guard tests (spans + traces) as a
regression lock for the truncate+exclude combination. Correctness of the
rendered EXCEPT lists was verified against ClickHouse query_log.Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- [OPIK-6747] [DOCS] Add backend skill guidance for sorting/pagination/exclusion SQL
Codify the lessons from the deferred-wide-column work so future query changes
follow the same bar:- clickhouse.md: the two-phase page_ids/page_wide invariants (pre-filter carries
the sort key; final SELECT re-sorts; spans and traces change together). - testing.md: sorting/pagination/field-exclusion SQL changes must be covered by
full-page-content tests (not ids), custom sort_fields, the sort x exclude
combination, and pagination — for both spans and traces.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- [OPIK-6747] [BE] Address review: full actual-vs-expected assertion in truncate+exclude guard tests
The whenTruncatingAndExcludingWideField guard tests only spot-checked input()/
output(). Assert the full page content instead (assertSpan / assertTraces):
build expected = created row with the excluded wide field nulled, using plain
JSON so truncate is a no-op and the expectation is exact.Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- test(backend): add filter+sort+exclude multi-page regression for deferred wide columns
Covers the OPIK-6747 production scenario end-to-end on both spans and traces:
filter (input CONTAINS), custom sort (input ASC), deferred wide-column exclusion
(output), paged. Each page is asserted byte-for-byte against an independent
Java-side filter+sort reference and against the single full page, with full row
content. Non-matching rows must be filtered out and excluded from the total.Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- docs(backend): capture ClickHouse test-reactor collision gotcha in testing skill
Running two ClickHouse-migrating resource test classes (spans + traces) in one
mvn reactor fails with REPLICA_ALREADY_EXISTS; document the separate-invocation
fix and the @Nested/parameterized surefire selector tips alongside it.Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
Co-authored-by: Claude Opus 4.8 (1M context) noreply@anthropic.com
下载附件