From 850c030642f9a27f3b4f522d53b7fe8c1a501d17 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Wed, 22 Apr 2026 16:21:52 +0200 Subject: [PATCH] search: compose ORDER BY with execution_id when afterExecutionId set Follow-up to Task 1.2 flagged by Task 1.5 review (I-1). Single-column ORDER BY could drop tail rows in a same-millisecond group >50 when paginating via the composite cursor. Appending ', execution_id ' as secondary key only when afterExecutionId is set preserves existing behaviour for UI/stats callers. --- .../server/app/search/ClickHouseSearchIndex.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/cameleer-server-app/src/main/java/com/cameleer/server/app/search/ClickHouseSearchIndex.java b/cameleer-server-app/src/main/java/com/cameleer/server/app/search/ClickHouseSearchIndex.java index ed3ff4a3..246557c1 100644 --- a/cameleer-server-app/src/main/java/com/cameleer/server/app/search/ClickHouseSearchIndex.java +++ b/cameleer-server-app/src/main/java/com/cameleer/server/app/search/ClickHouseSearchIndex.java @@ -81,13 +81,24 @@ public class ClickHouseSearchIndex implements SearchIndex { String sortColumn = SORT_FIELD_MAP.getOrDefault(request.sortField(), "start_time"); String sortDir = "asc".equalsIgnoreCase(request.sortDir()) ? "ASC" : "DESC"; + // Composite-cursor callers (afterExecutionId set) need a deterministic tiebreak inside + // same-millisecond groups so the client-side last-row pick matches ClickHouse's row order. + // Without this, a same-start_time tail >LIMIT can silently drop rows: the page ends mid-ms, + // the cursor advances past the returned lastRowId, and the skipped rows with smaller + // execution_id values never reappear. Other callers (UI/stats) keep the unchanged + // single-column ORDER BY — they don't use the composite cursor. + String orderBy = sortColumn + " " + sortDir; + if (request.afterExecutionId() != null) { + orderBy += ", execution_id " + sortDir; + } + String dataSql = "SELECT execution_id, route_id, instance_id, application_id, " + "status, start_time, end_time, duration_ms, correlation_id, " + "error_message, error_stacktrace, diagram_content_hash, attributes, " + "has_trace_data, is_replay, " + "input_body, output_body, input_headers, output_headers, root_cause_message " + "FROM executions FINAL WHERE " + whereClause - + " ORDER BY " + sortColumn + " " + sortDir + + " ORDER BY " + orderBy + " LIMIT ? OFFSET ?"; List dataParams = new ArrayList<>(params);