-
[OPIK-7142] [BE] feat: ingest cost intelligence into dedicated cipx tables (#7302)
发布于
2026-07-01 14:03:11 +00:00 - [OPIK-7142] [BE] feat: ingest cost intelligence into dedicated cipx tables
Populate cipx_spend and cipx_trace_identity asynchronously off span/trace
create and update events, so AI-spend reads no longer scan the spans/traces
metadata blob. Create reuses SpansCreated/TracesCreated; update consumes the
new *CostIntelligenceChanged events (the *Updated events carry neither ids nor
metadata). cipx rows are gated and built in Java, then written via a plain
INSERT that ReplacingMergeTree merges by sorting key. start_time is the real
entity start on create and the UUIDv7-embedded time on update.Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- test(backend): cover cipx ingestion via direct ClickHouse queries
Drive the real span/trace create and update path (API -> event bus ->
listener -> DAO) and assert against cipx_spend / cipx_trace_identity with raw
ClickHouse reads, no new production code. Covers create landing, non-cipx
siblings skipped, cipx-first-on-update inserts, and update merging to a single
row via FINAL.Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
-
refactor
-
bump migration versions
-
fix(cost-intelligence): resolve per-span trace on batch updates; rename cipx tables plural
Address PR review:
- Batch span updates reuse one SpanUpdate across spans that may belong to
different traces, so using update.traceId() for every cipx_spends row wrote
the wrong trace_id. Resolve project_id and trace_id per span from the
persisted spans (SpanDAO.getProjectAndTraceIdsBySpanIds). - Rename tables to plural: cipx_spends, cipx_trace_identities.
- SpanRow / TraceIdentityRow now use @Builder(toBuilder=true) + @NonNull.
- Correct migration comments that claimed the ReplacingMergeTree version came
from the source entity's last_updated_at; it uses the now64() default, same
as spans/traces.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- fix(cost-intelligence): resolve cipx start_time from source span/trace on update
Address PR review: the update path wrote cipx_spends/cipx_trace_identities
start_time from the UUIDv7 timestamp (RetentionUtils.extractInstant), so on
FINAL the update row overwrote the create row's real start_time — wrong for
backfilled/imported entities.- SpanDAO.getSpanRefsBySpanIds now reads FROM spans FINAL and returns
SpanRef(project_id, trace_id, start_time); the listener builds each
cipx_spends row with the stored start_time. - TraceDAO.getStartTimesByTraceIds (FROM traces FINAL) resolves the stored
trace start_time off the request path; the trace listener uses it and skips
traces it can't resolve. - Correct the DAO comments that claimed the UUIDv7 time is used on update.
- Update the two update-path test assertions to expect the stored start_time.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- refactor(cost-intelligence): dedup cipx ref lookups with LIMIT 1 BY id, not FINAL
Address PR review: FINAL on the per-event span/trace resolution reads pays for
a collapse scan on the ingestion hot path. Switch both getSpanRefsBySpanIds and
getStartTimesByTraceIds to the codebase-standard ORDER BY id, last_updated_at
DESC + LIMIT 1 BY id (as BULK_UPDATE and other reads do). Also add
@Builder(toBuilder=true) to SpanRef and construct it via the builder.Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
下载附件