@mastra/clickhouse 1.20.0-alpha.2 → 1.20.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2546,6 +2546,8 @@ const TABLE_TRACE_BRANCHES_DELTA = "mastra_trace_branches_delta";
2546
2546
  const TABLE_METRIC_EVENTS = "mastra_metric_events";
2547
2547
  const TABLE_LOG_EVENTS = "mastra_log_events";
2548
2548
  const TABLE_SCORE_EVENTS = "mastra_score_events";
2549
+ const TABLE_SCORE_EVENTS_CURRENT = "mastra_score_events_current";
2550
+ const TABLE_SCORE_EVENTS_CURRENT_BACKFILL = "mastra_score_events_current_backfill";
2549
2551
  const TABLE_FEEDBACK_EVENTS = "mastra_feedback_events";
2550
2552
  const TABLE_DELETION_REQUESTS = "mastra_deletion_requests";
2551
2553
  const TABLE_METRIC_EVENTS_DELTA = "mastra_metric_events_delta";
@@ -2561,6 +2563,7 @@ const MV_TRACE_BRANCHES_DELTA = "mastra_mv_trace_branches_delta";
2561
2563
  const MV_METRIC_EVENTS_DELTA = "mastra_mv_metric_events_delta";
2562
2564
  const MV_LOG_EVENTS_DELTA = "mastra_mv_log_events_delta";
2563
2565
  const MV_SCORE_EVENTS_DELTA = "mastra_mv_score_events_delta";
2566
+ const MV_SCORE_EVENTS_CURRENT = "mastra_mv_score_events_current";
2564
2567
  const MV_FEEDBACK_EVENTS_DELTA = "mastra_mv_feedback_events_delta";
2565
2568
  const MV_DISCOVERY_VALUES = "mastra_mv_discovery_values";
2566
2569
  const MV_DISCOVERY_PAIRS = "mastra_mv_discovery_pairs";
@@ -3078,19 +3081,53 @@ FROM (
3078
3081
  )
3079
3082
  `;
3080
3083
  }
3081
- const SCORE_EVENTS_DDL = `
3082
- CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS} (
3083
- -- Timestamp
3084
+ const SCORE_EVENT_COLUMN_NAMES = [
3085
+ "timestamp",
3086
+ "scoreId",
3087
+ "writeVersion",
3088
+ "traceId",
3089
+ "spanId",
3090
+ "experimentId",
3091
+ "scoreTraceId",
3092
+ "entityType",
3093
+ "entityId",
3094
+ "entityName",
3095
+ "entityVersionId",
3096
+ "parentEntityVersionId",
3097
+ "parentEntityType",
3098
+ "parentEntityId",
3099
+ "parentEntityName",
3100
+ "rootEntityVersionId",
3101
+ "rootEntityType",
3102
+ "rootEntityId",
3103
+ "rootEntityName",
3104
+ "userId",
3105
+ "organizationId",
3106
+ "resourceId",
3107
+ "runId",
3108
+ "sessionId",
3109
+ "threadId",
3110
+ "requestId",
3111
+ "environment",
3112
+ "executionSource",
3113
+ "serviceName",
3114
+ "scorerId",
3115
+ "scorerVersion",
3116
+ "scoreSource",
3117
+ "score",
3118
+ "reason",
3119
+ "tags",
3120
+ "metadata",
3121
+ "scope"
3122
+ ];
3123
+ const SCORE_EVENT_COLUMNS_DDL = `
3084
3124
  timestamp DateTime64(3, 'UTC'),
3085
-
3086
- -- IDs
3087
3125
  scoreId String,
3126
+ writeVersion UInt64 DEFAULT 0,
3088
3127
  traceId Nullable(String),
3089
3128
  spanId Nullable(String),
3090
3129
  experimentId Nullable(String),
3091
3130
  scoreTraceId Nullable(String),
3092
-
3093
- -- Entity hierarchy
3094
3131
  entityType LowCardinality(Nullable(String)),
3095
3132
  entityId Nullable(String),
3096
3133
  entityName Nullable(String),
@@ -3103,8 +3140,6 @@ CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS} (
3103
3140
  rootEntityType LowCardinality(Nullable(String)),
3104
3141
  rootEntityId Nullable(String),
3105
3142
  rootEntityName Nullable(String),
3106
-
3107
- -- Context
3108
3143
  userId Nullable(String),
3109
3144
  organizationId Nullable(String),
3110
3145
  resourceId Nullable(String),
@@ -3115,30 +3150,48 @@ CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS} (
3115
3150
  environment LowCardinality(Nullable(String)),
3116
3151
  executionSource LowCardinality(Nullable(String)),
3117
3152
  serviceName LowCardinality(Nullable(String)),
3118
-
3119
- -- Scorer identity
3120
3153
  scorerId LowCardinality(String),
3121
3154
  scorerVersion LowCardinality(Nullable(String)),
3122
3155
  scoreSource LowCardinality(Nullable(String)),
3123
-
3124
- -- Score value
3125
3156
  score Float64,
3126
-
3127
- -- Information-only
3128
3157
  reason Nullable(String),
3129
-
3130
- -- Query-relevant flexible fields
3131
3158
  tags Array(LowCardinality(String)) DEFAULT [],
3132
-
3133
- -- Information-only JSON payloads
3134
3159
  metadata Nullable(String),
3135
3160
  scope Nullable(String)
3161
+ `;
3162
+ const SCORE_EVENTS_DDL = `
3163
+ CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS} (
3164
+ ${SCORE_EVENT_COLUMNS_DDL},
3165
+ INDEX idx_scoreId scoreId TYPE bloom_filter(0.01) GRANULARITY 1
3136
3166
  )
3137
3167
  ENGINE = ReplacingMergeTree
3138
3168
  PARTITION BY toDate(timestamp)
3139
3169
  ORDER BY (traceId, timestamp, scoreId)
3140
3170
  SETTINGS allow_nullable_key = 1
3141
3171
  `;
3172
+ const SCORE_EVENTS_CURRENT_DDL = `
3173
+ CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS_CURRENT} (
3174
+ ${SCORE_EVENT_COLUMNS_DDL}
3175
+ )
3176
+ ENGINE = ReplacingMergeTree(writeVersion)
3177
+ PARTITION BY cityHash64(scoreId) % 64
3178
+ ORDER BY scoreId
3179
+ `;
3180
+ const SCORE_EVENTS_CURRENT_BACKFILL_DDL = `
3181
+ CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS_CURRENT_BACKFILL} (
3182
+ marker String,
3183
+ completedAt DateTime64(3, 'UTC')
3184
+ )
3185
+ ENGINE = ReplacingMergeTree(completedAt)
3186
+ ORDER BY marker
3187
+ `;
3188
+ const SCORE_EVENTS_CURRENT_MV_DDL = `
3189
+ CREATE MATERIALIZED VIEW IF NOT EXISTS ${MV_SCORE_EVENTS_CURRENT}
3190
+ TO ${TABLE_SCORE_EVENTS_CURRENT}
3191
+ AS
3192
+ SELECT ${SCORE_EVENT_COLUMN_NAMES.join(", ")}
3193
+ FROM ${TABLE_SCORE_EVENTS}
3194
+ `;
3142
3195
  function buildScoreEventsDeltaDDL() {
3143
3196
  return `
3144
3197
  CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS_DELTA} (
@@ -3412,6 +3465,8 @@ const BASE_TABLE_DDL = [
3412
3465
  METRIC_EVENTS_DDL,
3413
3466
  LOG_EVENTS_DDL,
3414
3467
  SCORE_EVENTS_DDL,
3468
+ SCORE_EVENTS_CURRENT_DDL,
3469
+ SCORE_EVENTS_CURRENT_BACKFILL_DDL,
3415
3470
  FEEDBACK_EVENTS_DDL,
3416
3471
  DELETION_REQUESTS_DDL,
3417
3472
  DISCOVERY_VALUES_DDL,
@@ -3430,7 +3485,11 @@ function buildDeltaTableDDL() {
3430
3485
  function buildAllTableDDL() {
3431
3486
  return [...BASE_TABLE_DDL, ...buildDeltaTableDDL()];
3432
3487
  }
3433
- const BASE_MV_DDL = [TRACE_ROOTS_MV_DDL, TRACE_BRANCHES_MV_DDL];
3488
+ const BASE_MV_DDL = [
3489
+ TRACE_ROOTS_MV_DDL,
3490
+ TRACE_BRANCHES_MV_DDL,
3491
+ SCORE_EVENTS_CURRENT_MV_DDL
3492
+ ];
3434
3493
  function buildDeltaMvDDL(strategy) {
3435
3494
  return [
3436
3495
  buildTraceRootsDeltaMvDDL(strategy),
@@ -3452,11 +3511,11 @@ const addColumn = (table, name, type) => ({
3452
3511
  name,
3453
3512
  sql: `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${name} ${type}`
3454
3513
  });
3455
- const addBloomIndex = (table, name, column) => ({
3514
+ const addBloomIndex = (table, name, column, granularity = 2) => ({
3456
3515
  kind: "index",
3457
3516
  table,
3458
3517
  name,
3459
- sql: `ALTER TABLE ${table} ADD INDEX IF NOT EXISTS ${name} ${column} TYPE bloom_filter(0.01) GRANULARITY 2`
3518
+ sql: `ALTER TABLE ${table} ADD INDEX IF NOT EXISTS ${name} ${column} TYPE bloom_filter(0.01) GRANULARITY ${granularity}`
3460
3519
  });
3461
3520
  const ALL_MIGRATIONS = [
3462
3521
  addColumn(TABLE_SPAN_EVENTS, "entityVersionId", "Nullable(String)"),
@@ -3471,9 +3530,11 @@ const ALL_MIGRATIONS = [
3471
3530
  addColumn(TABLE_LOG_EVENTS, "entityVersionId", "Nullable(String)"),
3472
3531
  addColumn(TABLE_LOG_EVENTS, "parentEntityVersionId", "Nullable(String)"),
3473
3532
  addColumn(TABLE_LOG_EVENTS, "rootEntityVersionId", "Nullable(String)"),
3533
+ addColumn(TABLE_SCORE_EVENTS, "writeVersion", "UInt64 DEFAULT 0"),
3474
3534
  addColumn(TABLE_SCORE_EVENTS, "entityVersionId", "Nullable(String)"),
3475
3535
  addColumn(TABLE_SCORE_EVENTS, "parentEntityVersionId", "Nullable(String)"),
3476
3536
  addColumn(TABLE_SCORE_EVENTS, "rootEntityVersionId", "Nullable(String)"),
3537
+ addBloomIndex(TABLE_SCORE_EVENTS, "idx_scoreId", "scoreId", 1),
3477
3538
  addColumn(TABLE_FEEDBACK_EVENTS, "writeVersion", "UInt64 DEFAULT 0"),
3478
3539
  addColumn(TABLE_FEEDBACK_EVENTS, "entityVersionId", "Nullable(String)"),
3479
3540
  addColumn(TABLE_FEEDBACK_EVENTS, "reviewStatus", "LowCardinality(String) DEFAULT 'needs-review'"),
@@ -3499,6 +3560,8 @@ const ALL_TABLE_NAMES = [
3499
3560
  TABLE_METRIC_EVENTS,
3500
3561
  TABLE_LOG_EVENTS,
3501
3562
  TABLE_SCORE_EVENTS,
3563
+ TABLE_SCORE_EVENTS_CURRENT,
3564
+ TABLE_SCORE_EVENTS_CURRENT_BACKFILL,
3502
3565
  TABLE_FEEDBACK_EVENTS,
3503
3566
  TABLE_DELETION_REQUESTS,
3504
3567
  TABLE_METRIC_EVENTS_DELTA,
@@ -3516,6 +3579,7 @@ const SIGNAL_TTL_COLUMNS = {
3516
3579
  [TABLE_METRIC_EVENTS]: "timestamp",
3517
3580
  [TABLE_LOG_EVENTS]: "timestamp",
3518
3581
  [TABLE_SCORE_EVENTS]: "timestamp",
3582
+ [TABLE_SCORE_EVENTS_CURRENT]: "timestamp",
3519
3583
  [TABLE_FEEDBACK_EVENTS]: "timestamp"
3520
3584
  };
3521
3585
  /** Maps each signal key to the table(s) it controls. */
@@ -3527,7 +3591,7 @@ const SIGNAL_TO_TABLES = {
3527
3591
  ],
3528
3592
  logs: [TABLE_LOG_EVENTS],
3529
3593
  metrics: [TABLE_METRIC_EVENTS],
3530
- scores: [TABLE_SCORE_EVENTS],
3594
+ scores: [TABLE_SCORE_EVENTS, TABLE_SCORE_EVENTS_CURRENT],
3531
3595
  feedback: [TABLE_FEEDBACK_EVENTS]
3532
3596
  };
3533
3597
  const RETENTION_MANAGED_TABLES = [...Object.keys(SIGNAL_TTL_COLUMNS), TABLE_DELETION_REQUESTS];
@@ -5947,6 +6011,36 @@ async function migrateSignalTables(client, logger) {
5947
6011
  }
5948
6012
  }
5949
6013
  //#endregion
6014
+ //#region src/storage/domains/observability/v-next/score-current.ts
6015
+ const SCORE_CURRENT_BACKFILL_MARKER = "v1";
6016
+ async function backfillCurrentScores(client) {
6017
+ const markerRows = await (await client.query({
6018
+ query: `SELECT count() AS count
6019
+ FROM ${TABLE_SCORE_EVENTS_CURRENT_BACKFILL} FINAL
6020
+ WHERE marker = {marker:String}`,
6021
+ query_params: { marker: SCORE_CURRENT_BACKFILL_MARKER },
6022
+ format: "JSONEachRow"
6023
+ })).json();
6024
+ if (Number(markerRows[0]?.count ?? 0) > 0) return;
6025
+ const columns = SCORE_EVENT_COLUMN_NAMES.join(", ");
6026
+ await client.command({
6027
+ query: `INSERT INTO ${TABLE_SCORE_EVENTS_CURRENT} (${columns})
6028
+ SELECT ${columns}
6029
+ FROM (
6030
+ SELECT *, cityHash64(tuple(*)) AS _currentScoreFingerprint
6031
+ FROM ${TABLE_SCORE_EVENTS} FINAL
6032
+ ORDER BY scoreId, writeVersion DESC, timestamp DESC, _currentScoreFingerprint DESC
6033
+ LIMIT 1 BY scoreId
6034
+ )`,
6035
+ clickhouse_settings: { max_bytes_ratio_before_external_sort: .5 }
6036
+ });
6037
+ await client.command({
6038
+ query: `INSERT INTO ${TABLE_SCORE_EVENTS_CURRENT_BACKFILL} (marker, completedAt)
6039
+ VALUES ({marker:String}, now64(3, 'UTC'))`,
6040
+ query_params: { marker: SCORE_CURRENT_BACKFILL_MARKER }
6041
+ });
6042
+ }
6043
+ //#endregion
5950
6044
  //#region src/storage/domains/observability/v-next/scores.ts
5951
6045
  const SCORE_TYPED_COLUMNS = /* @__PURE__ */ new Set([
5952
6046
  "timestamp",
@@ -6023,6 +6117,13 @@ function mergeFilters(...parts) {
6023
6117
  function toWhereClause(filter) {
6024
6118
  return filter.conditions.length ? `WHERE ${filter.conditions.join(" AND ")}` : "";
6025
6119
  }
6120
+ function currentScoresRelation(sourcePredicate) {
6121
+ return `(
6122
+ SELECT *
6123
+ FROM ${TABLE_SCORE_EVENTS_CURRENT} FINAL
6124
+ ${sourcePredicate ? `WHERE ${sourcePredicate}` : ""}
6125
+ )`;
6126
+ }
6026
6127
  function buildScoreIdentityFilter(args) {
6027
6128
  const conditions = ["scorerId = {olapScorerId:String}"];
6028
6129
  const params = { olapScorerId: args.scorerId };
@@ -6058,6 +6159,24 @@ async function queryJson(client, query, params) {
6058
6159
  clickhouse_settings: CH_SETTINGS
6059
6160
  })).json();
6060
6161
  }
6162
+ async function scoreRowsWithWriteVersions(client, scores) {
6163
+ const scoreIds = [...new Set(scores.flatMap((score) => score.scoreId ? [score.scoreId] : []))];
6164
+ const existingVersions = [];
6165
+ const lookupBatchSize = 1e3;
6166
+ for (let offset = 0; offset < scoreIds.length; offset += lookupBatchSize) existingVersions.push(...await queryJson(client, `SELECT scoreId, toString(max(writeVersion)) AS writeVersion
6167
+ FROM ${TABLE_SCORE_EVENTS}
6168
+ WHERE scoreId IN ({scoreIds:Array(String)})
6169
+ GROUP BY scoreId`, { scoreIds: scoreIds.slice(offset, offset + lookupBatchSize) }));
6170
+ const versions = new Map(existingVersions.map((row) => [row.scoreId, BigInt(row.writeVersion)]));
6171
+ return scores.map((score) => {
6172
+ const writeVersion = score.scoreId ? (versions.get(score.scoreId) ?? 0n) + 1n : 1n;
6173
+ if (score.scoreId) versions.set(score.scoreId, writeVersion);
6174
+ return {
6175
+ ...scoreRecordToRow(score),
6176
+ writeVersion: writeVersion.toString()
6177
+ };
6178
+ });
6179
+ }
6061
6180
  async function createScore(client, args) {
6062
6181
  await batchCreateScores(client, { scores: [args.score] });
6063
6182
  }
@@ -6065,7 +6184,7 @@ async function batchCreateScores(client, args) {
6065
6184
  if (args.scores.length === 0) return;
6066
6185
  await client.insert({
6067
6186
  table: TABLE_SCORE_EVENTS,
6068
- values: args.scores.map(scoreRecordToRow),
6187
+ values: await scoreRowsWithWriteVersions(client, args.scores),
6069
6188
  format: "JSONEachRow",
6070
6189
  clickhouse_settings: CH_INSERT_SETTINGS
6071
6190
  });
@@ -6108,10 +6227,11 @@ async function deleteScores(client, args, replication) {
6108
6227
  conditions.push("resourceId = {delResourceId:String}");
6109
6228
  params.delResourceId = args.resourceId;
6110
6229
  }
6111
- await client.command({
6112
- query: `DELETE FROM ${TABLE_SCORE_EVENTS} WHERE ${conditions.join(" AND ")}`,
6230
+ const clickhouse_settings = { lightweight_deletes_sync: isReplicationConfigured(replication) ? "2" : "1" };
6231
+ for (const table of [TABLE_SCORE_EVENTS_CURRENT, TABLE_SCORE_EVENTS]) await client.command({
6232
+ query: `DELETE FROM ${table} WHERE ${conditions.join(" AND ")}`,
6113
6233
  query_params: params,
6114
- clickhouse_settings: { lightweight_deletes_sync: isReplicationConfigured(replication) ? "2" : "1" }
6234
+ clickhouse_settings
6115
6235
  });
6116
6236
  }
6117
6237
  async function listScores(client, args, strategy) {
@@ -6145,8 +6265,9 @@ async function listScores(client, args, strategy) {
6145
6265
  };
6146
6266
  }
6147
6267
  const currentDeltaCursor = deltaCursorEnabled ? await getDeltaCursor$2(client, whereClause, filter.params) : void 0;
6148
- const countResult = await queryJson(client, `SELECT count() AS total FROM ${TABLE_SCORE_EVENTS} AS s ${whereClause}`, filter.params);
6149
- const rows = await queryJson(client, `SELECT * FROM ${TABLE_SCORE_EVENTS} AS s ${whereClause} ORDER BY ${orderBy} LIMIT {limit:UInt32} OFFSET {offset:UInt32}`, {
6268
+ const scoresRelation = currentScoresRelation();
6269
+ const countResult = await queryJson(client, `SELECT count() AS total FROM ${scoresRelation} AS s ${whereClause}`, filter.params);
6270
+ const rows = await queryJson(client, `SELECT * FROM ${scoresRelation} AS s ${whereClause} ORDER BY ${orderBy} LIMIT {limit:UInt32} OFFSET {offset:UInt32}`, {
6150
6271
  ...filter.params,
6151
6272
  limit: pagination.limit,
6152
6273
  offset: pagination.offset
@@ -6205,14 +6326,15 @@ function buildScoresCursor(row) {
6205
6326
  return row.cursorId ?? "0";
6206
6327
  }
6207
6328
  async function getScoreById(client, scoreId) {
6208
- const rows = await queryJson(client, `SELECT * FROM ${TABLE_SCORE_EVENTS} WHERE scoreId = {scoreId:String} LIMIT 1`, { scoreId });
6329
+ const rows = await queryJson(client, `SELECT * FROM ${currentScoresRelation("scoreId = {scoreId:String}")} LIMIT 1`, { scoreId });
6209
6330
  return rows[0] ? rowToScoreRecord(rows[0]) : null;
6210
6331
  }
6211
6332
  async function getScoreAggregate(client, args) {
6212
6333
  const aggSql = getAggregationSql(args.aggregation);
6213
6334
  const identity = buildScoreIdentityFilter(args);
6214
6335
  const combined = mergeFilters(identity, buildScoresFilterConditions(args.filters));
6215
- const result = await queryJson(client, `SELECT ${aggSql} AS value FROM ${TABLE_SCORE_EVENTS} ${toWhereClause(combined)}`, combined.params);
6336
+ const whereClause = toWhereClause(combined);
6337
+ const result = await queryJson(client, `SELECT ${aggSql} AS value FROM ${currentScoresRelation()} ${whereClause}`, combined.params);
6216
6338
  const value = result[0]?.value == null ? null : Number(result[0]?.value);
6217
6339
  if (args.comparePeriod && args.filters?.timestamp) {
6218
6340
  const ts = args.filters.timestamp;
@@ -6246,7 +6368,8 @@ async function getScoreAggregate(client, args) {
6246
6368
  endExclusive: ts.endExclusive
6247
6369
  }
6248
6370
  }));
6249
- const prevResult = await queryJson(client, `SELECT ${aggSql} AS value FROM ${TABLE_SCORE_EVENTS} ${toWhereClause(prevCombined)}`, prevCombined.params);
6371
+ const prevWhereClause = toWhereClause(prevCombined);
6372
+ const prevResult = await queryJson(client, `SELECT ${aggSql} AS value FROM ${currentScoresRelation()} ${prevWhereClause}`, prevCombined.params);
6250
6373
  const previousValue = prevResult[0]?.value == null ? null : Number(prevResult[0]?.value);
6251
6374
  let changePercent = null;
6252
6375
  if (previousValue !== null && previousValue !== 0 && value !== null) changePercent = (value - previousValue) / Math.abs(previousValue) * 100;
@@ -6264,7 +6387,7 @@ async function getScoreBreakdown(client, args) {
6264
6387
  const combined = mergeFilters(buildScoreIdentityFilter(args), buildScoresFilterConditions(args.filters));
6265
6388
  const whereClause = toWhereClause(combined);
6266
6389
  const resolved = resolveScoreGroupBy(args.groupBy);
6267
- return { groups: (await queryJson(client, `SELECT ${resolved.map((e) => e.selectSql).join(", ")}, ${aggSql} AS value FROM ${TABLE_SCORE_EVENTS} ${whereClause} GROUP BY ${resolved.map((e) => e.groupSql).join(", ")} ORDER BY value DESC`, combined.params)).map((row) => ({
6390
+ return { groups: (await queryJson(client, `SELECT ${resolved.map((e) => e.selectSql).join(", ")}, ${aggSql} AS value FROM ${currentScoresRelation()} ${whereClause} GROUP BY ${resolved.map((e) => e.groupSql).join(", ")} ORDER BY value DESC`, combined.params)).map((row) => ({
6268
6391
  dimensions: Object.fromEntries(resolved.map((entry, index) => {
6269
6392
  const v = row[`group_by_${index}`];
6270
6393
  return [entry.key, v == null ? null : String(v)];
@@ -6283,7 +6406,7 @@ async function getScoreTimeSeries(client, args) {
6283
6406
  SELECT toStartOfInterval(timestamp, ${intervalSql}) AS bucket,
6284
6407
  ${resolved.map((e) => e.selectSql).join(", ")},
6285
6408
  ${aggSql} AS value
6286
- FROM ${TABLE_SCORE_EVENTS} ${whereClause}
6409
+ FROM ${currentScoresRelation()} ${whereClause}
6287
6410
  GROUP BY bucket, ${resolved.map((e) => e.groupSql).join(", ")}
6288
6411
  ORDER BY bucket
6289
6412
  `, combined.params);
@@ -6305,7 +6428,7 @@ async function getScoreTimeSeries(client, args) {
6305
6428
  const rows = await queryJson(client, `
6306
6429
  SELECT toStartOfInterval(timestamp, ${intervalSql}) AS bucket,
6307
6430
  ${aggSql} AS value
6308
- FROM ${TABLE_SCORE_EVENTS} ${whereClause}
6431
+ FROM ${currentScoresRelation()} ${whereClause}
6309
6432
  GROUP BY bucket
6310
6433
  ORDER BY bucket
6311
6434
  `, combined.params);
@@ -6328,7 +6451,7 @@ async function getScorePercentiles(client, args) {
6328
6451
  const rows = await queryJson(client, `
6329
6452
  SELECT toStartOfInterval(timestamp, ${intervalSql}) AS bucket,
6330
6453
  quantile(${p})(score) AS pvalue
6331
- FROM ${TABLE_SCORE_EVENTS}
6454
+ FROM ${currentScoresRelation()}
6332
6455
  ${whereClause}
6333
6456
  GROUP BY bucket
6334
6457
  ORDER BY bucket
@@ -6697,11 +6820,9 @@ function compileClickHouseTraceScope(selection, relationCollections, parameters)
6697
6820
  entityVersionId,
6698
6821
  parentEntityVersionId,
6699
6822
  rootEntityVersionId
6700
- FROM ${TABLE_SCORE_EVENTS}
6701
- WHERE isNotNull(traceId)
6702
- AND traceId IN (SELECT traceId FROM root_scope)
6703
- ORDER BY scoreId, timestamp DESC
6704
- LIMIT 1 BY scoreId
6823
+ FROM ${currentScoresRelation()} AS current
6824
+ WHERE isNotNull(current.traceId)
6825
+ AND current.traceId IN (SELECT traceId FROM root_scope)
6705
6826
  )`);
6706
6827
  if (relationCollections.has("feedback")) ctes.push(`current_feedback AS (
6707
6828
  SELECT
@@ -6750,6 +6871,46 @@ LIMIT ${parameters.add(plan.limit + 1, "UInt64")}`,
6750
6871
  };
6751
6872
  const orderField = resolveOrderField(plan.orderBy.field);
6752
6873
  const direction = plan.orderBy.direction === "asc" ? "ASC" : "DESC";
6874
+ if (plan.paginationMode === "page") return {
6875
+ query: `${candidates},
6876
+ page_rows AS (
6877
+ SELECT *, row_number() OVER (ORDER BY ${orderField} ${direction}, traceId ASC) AS __row_position
6878
+ FROM candidates
6879
+ ORDER BY ${orderField} ${direction}, traceId ASC
6880
+ LIMIT ${parameters.add(plan.perPage, "UInt64")} OFFSET ${parameters.add(plan.page * plan.perPage, "UInt64")}
6881
+ ),
6882
+ page_total AS (
6883
+ SELECT count() AS total
6884
+ FROM candidates
6885
+ )
6886
+ SELECT page_rows.*, page_total.total, 0 AS __metadata
6887
+ FROM page_rows
6888
+ CROSS JOIN page_total
6889
+ UNION ALL
6890
+ SELECT
6891
+ '' AS traceId,
6892
+ '' AS rootSpanId,
6893
+ '' AS name,
6894
+ CAST(NULL, 'Nullable(String)') AS entityId,
6895
+ CAST(NULL, 'Nullable(String)') AS parentSpanId,
6896
+ CAST(NULL, 'Nullable(String)') AS metadata,
6897
+ CAST(NULL, 'Nullable(String)') AS input,
6898
+ CAST(NULL, 'Nullable(String)') AS threadId,
6899
+ CAST(NULL, 'Nullable(String)') AS resourceId,
6900
+ toDateTime64(0, 3, 'UTC') AS startedAt,
6901
+ toDateTime64(0, 3, 'UTC') AS endedAt,
6902
+ CAST(NULL, 'Nullable(String)') AS entityName,
6903
+ CAST(NULL, 'Nullable(String)') AS entityType,
6904
+ CAST(NULL, 'Nullable(String)') AS environment,
6905
+ '' AS status,
6906
+ 0 AS __row_position,
6907
+ page_total.total AS total,
6908
+ 1 AS __metadata
6909
+ FROM page_total
6910
+ ORDER BY __metadata ASC, __row_position ASC`,
6911
+ query_params: parameters.params,
6912
+ sharedSnapshot: true
6913
+ };
6753
6914
  let pageCondition = "";
6754
6915
  if (plan.cursor) {
6755
6916
  const comparison = plan.orderBy.direction === "asc" ? ">" : "<";
@@ -6896,7 +7057,8 @@ async function runWithClickHouseTraceQueryTimeout(client, limits, compiled, quer
6896
7057
  clickhouse_settings: {
6897
7058
  ...CH_SETTINGS,
6898
7059
  max_execution_time: resolvedTimeoutMs / 1e3,
6899
- ...limits.memoryLimitBytes === void 0 ? {} : { max_memory_usage: String(limits.memoryLimitBytes) }
7060
+ ...limits.memoryLimitBytes === void 0 ? {} : { max_memory_usage: String(limits.memoryLimitBytes) },
7061
+ ...compiled.sharedSnapshot ? { enable_shared_storage_snapshot_in_query: 1 } : {}
6900
7062
  }
6901
7063
  })).json();
6902
7064
  } catch (error) {
@@ -6927,6 +7089,37 @@ async function getTraceQueryValues(client, plan, limits) {
6927
7089
  });
6928
7090
  }
6929
7091
  async function queryTraces(client, plan, timeoutMs) {
7092
+ if (plan.paginationMode === "page") {
7093
+ const rows = await runWithClickHouseTraceQueryTimeout(client, { timeoutMs }, compileClickHouseTraceQuery(plan));
7094
+ const total = Number(rows.at(-1)?.total ?? 0);
7095
+ const traces = rows.filter((row) => Number(row.__metadata) === 0).map((row) => ({
7096
+ traceId: String(row.traceId),
7097
+ rootSpanId: String(row.rootSpanId),
7098
+ name: row.name,
7099
+ entityId: row.entityId ?? null,
7100
+ parentSpanId: row.parentSpanId ?? null,
7101
+ createdAt: asIsoTimestamp(row.startedAt),
7102
+ metadata: parseJson(row.metadata) ?? null,
7103
+ inputPreview: coreStorage.buildInputPreview(row.input) ?? null,
7104
+ threadId: row.threadId == null ? null : String(row.threadId),
7105
+ resourceId: row.resourceId == null ? null : String(row.resourceId),
7106
+ startedAt: asIsoTimestamp(row.startedAt),
7107
+ endedAt: asIsoTimestamp(row.endedAt),
7108
+ entityName: row.entityName == null ? null : String(row.entityName),
7109
+ entityType: row.entityType == null ? null : String(row.entityType),
7110
+ environment: row.environment == null ? null : String(row.environment),
7111
+ status: row.status
7112
+ }));
7113
+ return coreStorage.traceQueryResponseSchema.parse({
7114
+ traces,
7115
+ pagination: {
7116
+ total,
7117
+ page: plan.page,
7118
+ perPage: plan.perPage,
7119
+ hasMore: (plan.page + 1) * plan.perPage < total
7120
+ }
7121
+ });
7122
+ }
6930
7123
  const rows = await runWithClickHouseTraceQueryTimeout(client, { timeoutMs }, compileClickHouseTraceQuery(plan));
6931
7124
  const visibleRows = rows.slice(0, plan.limit);
6932
7125
  if (plan.result === "groups") {
@@ -7432,6 +7625,7 @@ async function batchDeleteTraces(client, args, replication) {
7432
7625
  TABLE_METRIC_EVENTS,
7433
7626
  TABLE_LOG_EVENTS,
7434
7627
  TABLE_SCORE_EVENTS,
7628
+ TABLE_SCORE_EVENTS_CURRENT,
7435
7629
  TABLE_FEEDBACK_EVENTS
7436
7630
  ];
7437
7631
  await Promise.all([...tracingTables.map((table) => client.command({
@@ -8111,10 +8305,13 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
8111
8305
  else if (existingStrategy) this.#deltaCursorStrategy = existingStrategy;
8112
8306
  else this.#deltaCursorStrategy = await detectDeltaCursorStrategy(this.#client, void 0, existingStrategy);
8113
8307
  await reconcileDiscoveryTables(this.#client, this.#replication);
8114
- const coreDdl = this.#deltaCursorStrategy === null ? [...BASE_TABLE_DDL, ...BASE_MV_DDL] : [...buildAllTableDDL(), ...buildAllMvDDL(this.#deltaCursorStrategy)];
8115
- for (const ddl of coreDdl) await this.#client.command({ query: applyReplicationToDDL(ddl, this.#replication) });
8308
+ const coreTableDdl = this.#deltaCursorStrategy === null ? BASE_TABLE_DDL : buildAllTableDDL();
8309
+ for (const ddl of coreTableDdl) await this.#client.command({ query: applyReplicationToDDL(ddl, this.#replication) });
8116
8310
  const pendingMigrations = await filterAppliedMigrations(this.#client, ALL_MIGRATIONS);
8117
8311
  for (const migration of pendingMigrations) await this.#client.command({ query: addOnClusterToDDL(migration.sql, this.#replication) });
8312
+ const coreMvDdl = this.#deltaCursorStrategy === null ? BASE_MV_DDL : buildAllMvDDL(this.#deltaCursorStrategy);
8313
+ for (const ddl of coreMvDdl) await this.#client.command({ query: applyReplicationToDDL(ddl, this.#replication) });
8314
+ await backfillCurrentScores(this.#client);
8118
8315
  if (this.#retention) await this.applyRetention();
8119
8316
  if (this.#deltaCursorStrategy === "serial") for (const counterName of DELTA_CURSOR_COUNTER_NAMES) await this.#client.query({
8120
8317
  query: `SELECT generateSerialID({counterName:String}) AS cursorId`,