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

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
@@ -7,6 +7,7 @@ import { MastraBase } from "@mastra/core/base";
7
7
  import { randomUUID } from "crypto";
8
8
  import { parseFieldKey } from "@mastra/core/utils";
9
9
  import { coreFeatures } from "@mastra/core/features";
10
+ import { z } from "zod/v4";
10
11
  import { saveScorePayloadSchema } from "@mastra/core/evals";
11
12
  //#region src/storage/db/replication.ts
12
13
  const DEFAULT_ZOOKEEPER_PATH = "/clickhouse/tables/{shard}/{database}/{table}";
@@ -2546,6 +2547,8 @@ const TABLE_TRACE_BRANCHES_DELTA = "mastra_trace_branches_delta";
2546
2547
  const TABLE_METRIC_EVENTS = "mastra_metric_events";
2547
2548
  const TABLE_LOG_EVENTS = "mastra_log_events";
2548
2549
  const TABLE_SCORE_EVENTS = "mastra_score_events";
2550
+ const TABLE_SCORE_EVENTS_CURRENT = "mastra_score_events_current";
2551
+ const TABLE_SCORE_EVENTS_CURRENT_BACKFILL = "mastra_score_events_current_backfill";
2549
2552
  const TABLE_FEEDBACK_EVENTS = "mastra_feedback_events";
2550
2553
  const TABLE_DELETION_REQUESTS = "mastra_deletion_requests";
2551
2554
  const TABLE_METRIC_EVENTS_DELTA = "mastra_metric_events_delta";
@@ -2561,6 +2564,7 @@ const MV_TRACE_BRANCHES_DELTA = "mastra_mv_trace_branches_delta";
2561
2564
  const MV_METRIC_EVENTS_DELTA = "mastra_mv_metric_events_delta";
2562
2565
  const MV_LOG_EVENTS_DELTA = "mastra_mv_log_events_delta";
2563
2566
  const MV_SCORE_EVENTS_DELTA = "mastra_mv_score_events_delta";
2567
+ const MV_SCORE_EVENTS_CURRENT = "mastra_mv_score_events_current";
2564
2568
  const MV_FEEDBACK_EVENTS_DELTA = "mastra_mv_feedback_events_delta";
2565
2569
  const MV_DISCOVERY_VALUES = "mastra_mv_discovery_values";
2566
2570
  const MV_DISCOVERY_PAIRS = "mastra_mv_discovery_pairs";
@@ -3078,19 +3082,53 @@ FROM (
3078
3082
  )
3079
3083
  `;
3080
3084
  }
3081
- const SCORE_EVENTS_DDL = `
3082
- CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS} (
3083
- -- Timestamp
3085
+ const SCORE_EVENT_COLUMN_NAMES = [
3086
+ "timestamp",
3087
+ "scoreId",
3088
+ "writeVersion",
3089
+ "traceId",
3090
+ "spanId",
3091
+ "experimentId",
3092
+ "scoreTraceId",
3093
+ "entityType",
3094
+ "entityId",
3095
+ "entityName",
3096
+ "entityVersionId",
3097
+ "parentEntityVersionId",
3098
+ "parentEntityType",
3099
+ "parentEntityId",
3100
+ "parentEntityName",
3101
+ "rootEntityVersionId",
3102
+ "rootEntityType",
3103
+ "rootEntityId",
3104
+ "rootEntityName",
3105
+ "userId",
3106
+ "organizationId",
3107
+ "resourceId",
3108
+ "runId",
3109
+ "sessionId",
3110
+ "threadId",
3111
+ "requestId",
3112
+ "environment",
3113
+ "executionSource",
3114
+ "serviceName",
3115
+ "scorerId",
3116
+ "scorerVersion",
3117
+ "scoreSource",
3118
+ "score",
3119
+ "reason",
3120
+ "tags",
3121
+ "metadata",
3122
+ "scope"
3123
+ ];
3124
+ const SCORE_EVENT_COLUMNS_DDL = `
3084
3125
  timestamp DateTime64(3, 'UTC'),
3085
-
3086
- -- IDs
3087
3126
  scoreId String,
3127
+ writeVersion UInt64 DEFAULT 0,
3088
3128
  traceId Nullable(String),
3089
3129
  spanId Nullable(String),
3090
3130
  experimentId Nullable(String),
3091
3131
  scoreTraceId Nullable(String),
3092
-
3093
- -- Entity hierarchy
3094
3132
  entityType LowCardinality(Nullable(String)),
3095
3133
  entityId Nullable(String),
3096
3134
  entityName Nullable(String),
@@ -3103,8 +3141,6 @@ CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS} (
3103
3141
  rootEntityType LowCardinality(Nullable(String)),
3104
3142
  rootEntityId Nullable(String),
3105
3143
  rootEntityName Nullable(String),
3106
-
3107
- -- Context
3108
3144
  userId Nullable(String),
3109
3145
  organizationId Nullable(String),
3110
3146
  resourceId Nullable(String),
@@ -3115,30 +3151,48 @@ CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS} (
3115
3151
  environment LowCardinality(Nullable(String)),
3116
3152
  executionSource LowCardinality(Nullable(String)),
3117
3153
  serviceName LowCardinality(Nullable(String)),
3118
-
3119
- -- Scorer identity
3120
3154
  scorerId LowCardinality(String),
3121
3155
  scorerVersion LowCardinality(Nullable(String)),
3122
3156
  scoreSource LowCardinality(Nullable(String)),
3123
-
3124
- -- Score value
3125
3157
  score Float64,
3126
-
3127
- -- Information-only
3128
3158
  reason Nullable(String),
3129
-
3130
- -- Query-relevant flexible fields
3131
3159
  tags Array(LowCardinality(String)) DEFAULT [],
3132
-
3133
- -- Information-only JSON payloads
3134
3160
  metadata Nullable(String),
3135
3161
  scope Nullable(String)
3162
+ `;
3163
+ const SCORE_EVENTS_DDL = `
3164
+ CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS} (
3165
+ ${SCORE_EVENT_COLUMNS_DDL},
3166
+ INDEX idx_scoreId scoreId TYPE bloom_filter(0.01) GRANULARITY 1
3136
3167
  )
3137
3168
  ENGINE = ReplacingMergeTree
3138
3169
  PARTITION BY toDate(timestamp)
3139
3170
  ORDER BY (traceId, timestamp, scoreId)
3140
3171
  SETTINGS allow_nullable_key = 1
3141
3172
  `;
3173
+ const SCORE_EVENTS_CURRENT_DDL = `
3174
+ CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS_CURRENT} (
3175
+ ${SCORE_EVENT_COLUMNS_DDL}
3176
+ )
3177
+ ENGINE = ReplacingMergeTree(writeVersion)
3178
+ PARTITION BY cityHash64(scoreId) % 64
3179
+ ORDER BY scoreId
3180
+ `;
3181
+ const SCORE_EVENTS_CURRENT_BACKFILL_DDL = `
3182
+ CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS_CURRENT_BACKFILL} (
3183
+ marker String,
3184
+ completedAt DateTime64(3, 'UTC')
3185
+ )
3186
+ ENGINE = ReplacingMergeTree(completedAt)
3187
+ ORDER BY marker
3188
+ `;
3189
+ const SCORE_EVENTS_CURRENT_MV_DDL = `
3190
+ CREATE MATERIALIZED VIEW IF NOT EXISTS ${MV_SCORE_EVENTS_CURRENT}
3191
+ TO ${TABLE_SCORE_EVENTS_CURRENT}
3192
+ AS
3193
+ SELECT ${SCORE_EVENT_COLUMN_NAMES.join(", ")}
3194
+ FROM ${TABLE_SCORE_EVENTS}
3195
+ `;
3142
3196
  function buildScoreEventsDeltaDDL() {
3143
3197
  return `
3144
3198
  CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS_DELTA} (
@@ -3412,6 +3466,8 @@ const BASE_TABLE_DDL = [
3412
3466
  METRIC_EVENTS_DDL,
3413
3467
  LOG_EVENTS_DDL,
3414
3468
  SCORE_EVENTS_DDL,
3469
+ SCORE_EVENTS_CURRENT_DDL,
3470
+ SCORE_EVENTS_CURRENT_BACKFILL_DDL,
3415
3471
  FEEDBACK_EVENTS_DDL,
3416
3472
  DELETION_REQUESTS_DDL,
3417
3473
  DISCOVERY_VALUES_DDL,
@@ -3430,7 +3486,11 @@ function buildDeltaTableDDL() {
3430
3486
  function buildAllTableDDL() {
3431
3487
  return [...BASE_TABLE_DDL, ...buildDeltaTableDDL()];
3432
3488
  }
3433
- const BASE_MV_DDL = [TRACE_ROOTS_MV_DDL, TRACE_BRANCHES_MV_DDL];
3489
+ const BASE_MV_DDL = [
3490
+ TRACE_ROOTS_MV_DDL,
3491
+ TRACE_BRANCHES_MV_DDL,
3492
+ SCORE_EVENTS_CURRENT_MV_DDL
3493
+ ];
3434
3494
  function buildDeltaMvDDL(strategy) {
3435
3495
  return [
3436
3496
  buildTraceRootsDeltaMvDDL(strategy),
@@ -3452,11 +3512,11 @@ const addColumn = (table, name, type) => ({
3452
3512
  name,
3453
3513
  sql: `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${name} ${type}`
3454
3514
  });
3455
- const addBloomIndex = (table, name, column) => ({
3515
+ const addBloomIndex = (table, name, column, granularity = 2) => ({
3456
3516
  kind: "index",
3457
3517
  table,
3458
3518
  name,
3459
- sql: `ALTER TABLE ${table} ADD INDEX IF NOT EXISTS ${name} ${column} TYPE bloom_filter(0.01) GRANULARITY 2`
3519
+ sql: `ALTER TABLE ${table} ADD INDEX IF NOT EXISTS ${name} ${column} TYPE bloom_filter(0.01) GRANULARITY ${granularity}`
3460
3520
  });
3461
3521
  const ALL_MIGRATIONS = [
3462
3522
  addColumn(TABLE_SPAN_EVENTS, "entityVersionId", "Nullable(String)"),
@@ -3471,9 +3531,11 @@ const ALL_MIGRATIONS = [
3471
3531
  addColumn(TABLE_LOG_EVENTS, "entityVersionId", "Nullable(String)"),
3472
3532
  addColumn(TABLE_LOG_EVENTS, "parentEntityVersionId", "Nullable(String)"),
3473
3533
  addColumn(TABLE_LOG_EVENTS, "rootEntityVersionId", "Nullable(String)"),
3534
+ addColumn(TABLE_SCORE_EVENTS, "writeVersion", "UInt64 DEFAULT 0"),
3474
3535
  addColumn(TABLE_SCORE_EVENTS, "entityVersionId", "Nullable(String)"),
3475
3536
  addColumn(TABLE_SCORE_EVENTS, "parentEntityVersionId", "Nullable(String)"),
3476
3537
  addColumn(TABLE_SCORE_EVENTS, "rootEntityVersionId", "Nullable(String)"),
3538
+ addBloomIndex(TABLE_SCORE_EVENTS, "idx_scoreId", "scoreId", 1),
3477
3539
  addColumn(TABLE_FEEDBACK_EVENTS, "writeVersion", "UInt64 DEFAULT 0"),
3478
3540
  addColumn(TABLE_FEEDBACK_EVENTS, "entityVersionId", "Nullable(String)"),
3479
3541
  addColumn(TABLE_FEEDBACK_EVENTS, "reviewStatus", "LowCardinality(String) DEFAULT 'needs-review'"),
@@ -3499,6 +3561,8 @@ const ALL_TABLE_NAMES = [
3499
3561
  TABLE_METRIC_EVENTS,
3500
3562
  TABLE_LOG_EVENTS,
3501
3563
  TABLE_SCORE_EVENTS,
3564
+ TABLE_SCORE_EVENTS_CURRENT,
3565
+ TABLE_SCORE_EVENTS_CURRENT_BACKFILL,
3502
3566
  TABLE_FEEDBACK_EVENTS,
3503
3567
  TABLE_DELETION_REQUESTS,
3504
3568
  TABLE_METRIC_EVENTS_DELTA,
@@ -3516,6 +3580,7 @@ const SIGNAL_TTL_COLUMNS = {
3516
3580
  [TABLE_METRIC_EVENTS]: "timestamp",
3517
3581
  [TABLE_LOG_EVENTS]: "timestamp",
3518
3582
  [TABLE_SCORE_EVENTS]: "timestamp",
3583
+ [TABLE_SCORE_EVENTS_CURRENT]: "timestamp",
3519
3584
  [TABLE_FEEDBACK_EVENTS]: "timestamp"
3520
3585
  };
3521
3586
  /** Maps each signal key to the table(s) it controls. */
@@ -3527,7 +3592,7 @@ const SIGNAL_TO_TABLES = {
3527
3592
  ],
3528
3593
  logs: [TABLE_LOG_EVENTS],
3529
3594
  metrics: [TABLE_METRIC_EVENTS],
3530
- scores: [TABLE_SCORE_EVENTS],
3595
+ scores: [TABLE_SCORE_EVENTS, TABLE_SCORE_EVENTS_CURRENT],
3531
3596
  feedback: [TABLE_FEEDBACK_EVENTS]
3532
3597
  };
3533
3598
  const RETENTION_MANAGED_TABLES = [...Object.keys(SIGNAL_TTL_COLUMNS), TABLE_DELETION_REQUESTS];
@@ -5947,6 +6012,36 @@ async function migrateSignalTables(client, logger) {
5947
6012
  }
5948
6013
  }
5949
6014
  //#endregion
6015
+ //#region src/storage/domains/observability/v-next/score-current.ts
6016
+ const SCORE_CURRENT_BACKFILL_MARKER = "v1";
6017
+ async function backfillCurrentScores(client) {
6018
+ const markerRows = await (await client.query({
6019
+ query: `SELECT count() AS count
6020
+ FROM ${TABLE_SCORE_EVENTS_CURRENT_BACKFILL} FINAL
6021
+ WHERE marker = {marker:String}`,
6022
+ query_params: { marker: SCORE_CURRENT_BACKFILL_MARKER },
6023
+ format: "JSONEachRow"
6024
+ })).json();
6025
+ if (Number(markerRows[0]?.count ?? 0) > 0) return;
6026
+ const columns = SCORE_EVENT_COLUMN_NAMES.join(", ");
6027
+ await client.command({
6028
+ query: `INSERT INTO ${TABLE_SCORE_EVENTS_CURRENT} (${columns})
6029
+ SELECT ${columns}
6030
+ FROM (
6031
+ SELECT *, cityHash64(tuple(*)) AS _currentScoreFingerprint
6032
+ FROM ${TABLE_SCORE_EVENTS} FINAL
6033
+ ORDER BY scoreId, writeVersion DESC, timestamp DESC, _currentScoreFingerprint DESC
6034
+ LIMIT 1 BY scoreId
6035
+ )`,
6036
+ clickhouse_settings: { max_bytes_ratio_before_external_sort: .5 }
6037
+ });
6038
+ await client.command({
6039
+ query: `INSERT INTO ${TABLE_SCORE_EVENTS_CURRENT_BACKFILL} (marker, completedAt)
6040
+ VALUES ({marker:String}, now64(3, 'UTC'))`,
6041
+ query_params: { marker: SCORE_CURRENT_BACKFILL_MARKER }
6042
+ });
6043
+ }
6044
+ //#endregion
5950
6045
  //#region src/storage/domains/observability/v-next/scores.ts
5951
6046
  const SCORE_TYPED_COLUMNS = /* @__PURE__ */ new Set([
5952
6047
  "timestamp",
@@ -6023,6 +6118,13 @@ function mergeFilters(...parts) {
6023
6118
  function toWhereClause(filter) {
6024
6119
  return filter.conditions.length ? `WHERE ${filter.conditions.join(" AND ")}` : "";
6025
6120
  }
6121
+ function currentScoresRelation(sourcePredicate) {
6122
+ return `(
6123
+ SELECT *
6124
+ FROM ${TABLE_SCORE_EVENTS_CURRENT} FINAL
6125
+ ${sourcePredicate ? `WHERE ${sourcePredicate}` : ""}
6126
+ )`;
6127
+ }
6026
6128
  function buildScoreIdentityFilter(args) {
6027
6129
  const conditions = ["scorerId = {olapScorerId:String}"];
6028
6130
  const params = { olapScorerId: args.scorerId };
@@ -6058,6 +6160,24 @@ async function queryJson(client, query, params) {
6058
6160
  clickhouse_settings: CH_SETTINGS
6059
6161
  })).json();
6060
6162
  }
6163
+ async function scoreRowsWithWriteVersions(client, scores) {
6164
+ const scoreIds = [...new Set(scores.flatMap((score) => score.scoreId ? [score.scoreId] : []))];
6165
+ const existingVersions = [];
6166
+ const lookupBatchSize = 1e3;
6167
+ for (let offset = 0; offset < scoreIds.length; offset += lookupBatchSize) existingVersions.push(...await queryJson(client, `SELECT scoreId, toString(max(writeVersion)) AS writeVersion
6168
+ FROM ${TABLE_SCORE_EVENTS}
6169
+ WHERE scoreId IN ({scoreIds:Array(String)})
6170
+ GROUP BY scoreId`, { scoreIds: scoreIds.slice(offset, offset + lookupBatchSize) }));
6171
+ const versions = new Map(existingVersions.map((row) => [row.scoreId, BigInt(row.writeVersion)]));
6172
+ return scores.map((score) => {
6173
+ const writeVersion = score.scoreId ? (versions.get(score.scoreId) ?? 0n) + 1n : 1n;
6174
+ if (score.scoreId) versions.set(score.scoreId, writeVersion);
6175
+ return {
6176
+ ...scoreRecordToRow(score),
6177
+ writeVersion: writeVersion.toString()
6178
+ };
6179
+ });
6180
+ }
6061
6181
  async function createScore(client, args) {
6062
6182
  await batchCreateScores(client, { scores: [args.score] });
6063
6183
  }
@@ -6065,7 +6185,7 @@ async function batchCreateScores(client, args) {
6065
6185
  if (args.scores.length === 0) return;
6066
6186
  await client.insert({
6067
6187
  table: TABLE_SCORE_EVENTS,
6068
- values: args.scores.map(scoreRecordToRow),
6188
+ values: await scoreRowsWithWriteVersions(client, args.scores),
6069
6189
  format: "JSONEachRow",
6070
6190
  clickhouse_settings: CH_INSERT_SETTINGS
6071
6191
  });
@@ -6108,10 +6228,11 @@ async function deleteScores(client, args, replication) {
6108
6228
  conditions.push("resourceId = {delResourceId:String}");
6109
6229
  params.delResourceId = args.resourceId;
6110
6230
  }
6111
- await client.command({
6112
- query: `DELETE FROM ${TABLE_SCORE_EVENTS} WHERE ${conditions.join(" AND ")}`,
6231
+ const clickhouse_settings = { lightweight_deletes_sync: isReplicationConfigured(replication) ? "2" : "1" };
6232
+ for (const table of [TABLE_SCORE_EVENTS_CURRENT, TABLE_SCORE_EVENTS]) await client.command({
6233
+ query: `DELETE FROM ${table} WHERE ${conditions.join(" AND ")}`,
6113
6234
  query_params: params,
6114
- clickhouse_settings: { lightweight_deletes_sync: isReplicationConfigured(replication) ? "2" : "1" }
6235
+ clickhouse_settings
6115
6236
  });
6116
6237
  }
6117
6238
  async function listScores(client, args, strategy) {
@@ -6145,8 +6266,9 @@ async function listScores(client, args, strategy) {
6145
6266
  };
6146
6267
  }
6147
6268
  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}`, {
6269
+ const scoresRelation = currentScoresRelation();
6270
+ const countResult = await queryJson(client, `SELECT count() AS total FROM ${scoresRelation} AS s ${whereClause}`, filter.params);
6271
+ const rows = await queryJson(client, `SELECT * FROM ${scoresRelation} AS s ${whereClause} ORDER BY ${orderBy} LIMIT {limit:UInt32} OFFSET {offset:UInt32}`, {
6150
6272
  ...filter.params,
6151
6273
  limit: pagination.limit,
6152
6274
  offset: pagination.offset
@@ -6205,14 +6327,15 @@ function buildScoresCursor(row) {
6205
6327
  return row.cursorId ?? "0";
6206
6328
  }
6207
6329
  async function getScoreById(client, scoreId) {
6208
- const rows = await queryJson(client, `SELECT * FROM ${TABLE_SCORE_EVENTS} WHERE scoreId = {scoreId:String} LIMIT 1`, { scoreId });
6330
+ const rows = await queryJson(client, `SELECT * FROM ${currentScoresRelation("scoreId = {scoreId:String}")} LIMIT 1`, { scoreId });
6209
6331
  return rows[0] ? rowToScoreRecord(rows[0]) : null;
6210
6332
  }
6211
6333
  async function getScoreAggregate(client, args) {
6212
6334
  const aggSql = getAggregationSql(args.aggregation);
6213
6335
  const identity = buildScoreIdentityFilter(args);
6214
6336
  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);
6337
+ const whereClause = toWhereClause(combined);
6338
+ const result = await queryJson(client, `SELECT ${aggSql} AS value FROM ${currentScoresRelation()} ${whereClause}`, combined.params);
6216
6339
  const value = result[0]?.value == null ? null : Number(result[0]?.value);
6217
6340
  if (args.comparePeriod && args.filters?.timestamp) {
6218
6341
  const ts = args.filters.timestamp;
@@ -6246,7 +6369,8 @@ async function getScoreAggregate(client, args) {
6246
6369
  endExclusive: ts.endExclusive
6247
6370
  }
6248
6371
  }));
6249
- const prevResult = await queryJson(client, `SELECT ${aggSql} AS value FROM ${TABLE_SCORE_EVENTS} ${toWhereClause(prevCombined)}`, prevCombined.params);
6372
+ const prevWhereClause = toWhereClause(prevCombined);
6373
+ const prevResult = await queryJson(client, `SELECT ${aggSql} AS value FROM ${currentScoresRelation()} ${prevWhereClause}`, prevCombined.params);
6250
6374
  const previousValue = prevResult[0]?.value == null ? null : Number(prevResult[0]?.value);
6251
6375
  let changePercent = null;
6252
6376
  if (previousValue !== null && previousValue !== 0 && value !== null) changePercent = (value - previousValue) / Math.abs(previousValue) * 100;
@@ -6264,7 +6388,7 @@ async function getScoreBreakdown(client, args) {
6264
6388
  const combined = mergeFilters(buildScoreIdentityFilter(args), buildScoresFilterConditions(args.filters));
6265
6389
  const whereClause = toWhereClause(combined);
6266
6390
  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) => ({
6391
+ 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
6392
  dimensions: Object.fromEntries(resolved.map((entry, index) => {
6269
6393
  const v = row[`group_by_${index}`];
6270
6394
  return [entry.key, v == null ? null : String(v)];
@@ -6283,7 +6407,7 @@ async function getScoreTimeSeries(client, args) {
6283
6407
  SELECT toStartOfInterval(timestamp, ${intervalSql}) AS bucket,
6284
6408
  ${resolved.map((e) => e.selectSql).join(", ")},
6285
6409
  ${aggSql} AS value
6286
- FROM ${TABLE_SCORE_EVENTS} ${whereClause}
6410
+ FROM ${currentScoresRelation()} ${whereClause}
6287
6411
  GROUP BY bucket, ${resolved.map((e) => e.groupSql).join(", ")}
6288
6412
  ORDER BY bucket
6289
6413
  `, combined.params);
@@ -6305,7 +6429,7 @@ async function getScoreTimeSeries(client, args) {
6305
6429
  const rows = await queryJson(client, `
6306
6430
  SELECT toStartOfInterval(timestamp, ${intervalSql}) AS bucket,
6307
6431
  ${aggSql} AS value
6308
- FROM ${TABLE_SCORE_EVENTS} ${whereClause}
6432
+ FROM ${currentScoresRelation()} ${whereClause}
6309
6433
  GROUP BY bucket
6310
6434
  ORDER BY bucket
6311
6435
  `, combined.params);
@@ -6328,7 +6452,7 @@ async function getScorePercentiles(client, args) {
6328
6452
  const rows = await queryJson(client, `
6329
6453
  SELECT toStartOfInterval(timestamp, ${intervalSql}) AS bucket,
6330
6454
  quantile(${p})(score) AS pvalue
6331
- FROM ${TABLE_SCORE_EVENTS}
6455
+ FROM ${currentScoresRelation()}
6332
6456
  ${whereClause}
6333
6457
  GROUP BY bucket
6334
6458
  ORDER BY bucket
@@ -6697,11 +6821,9 @@ function compileClickHouseTraceScope(selection, relationCollections, parameters)
6697
6821
  entityVersionId,
6698
6822
  parentEntityVersionId,
6699
6823
  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
6824
+ FROM ${currentScoresRelation()} AS current
6825
+ WHERE isNotNull(current.traceId)
6826
+ AND current.traceId IN (SELECT traceId FROM root_scope)
6705
6827
  )`);
6706
6828
  if (relationCollections.has("feedback")) ctes.push(`current_feedback AS (
6707
6829
  SELECT
@@ -6728,7 +6850,18 @@ function compileClickHouseTraceScope(selection, relationCollections, parameters)
6728
6850
  )`);
6729
6851
  return ctes;
6730
6852
  }
6731
- function compileClickHouseTraceQuery(plan) {
6853
+ const deltaWatermarkSchema = z.object({
6854
+ cursorId: z.string().regex(/^\d+$/).refine((value) => BigInt(value) <= 18446744073709551615n),
6855
+ traceId: z.string()
6856
+ }).strict();
6857
+ function parseDeltaWatermark(value) {
6858
+ try {
6859
+ return deltaWatermarkSchema.parse(JSON.parse(value));
6860
+ } catch {
6861
+ throw new coreStorage.TraceQueryCursorError("TRACE_QUERY_CURSOR_MALFORMED");
6862
+ }
6863
+ }
6864
+ function compileClickHouseTraceQuery(plan, deltaHead) {
6732
6865
  const parameters = new ParameterBuilder();
6733
6866
  const ctes = compileClickHouseTraceScope(plan, collectRelationCollections(plan.where), parameters);
6734
6867
  const predicate = plan.where ? compilePredicate(plan.where, parameters) : "1";
@@ -6748,8 +6881,70 @@ ORDER BY threadId ASC
6748
6881
  LIMIT ${parameters.add(plan.limit + 1, "UInt64")}`,
6749
6882
  query_params: parameters.params
6750
6883
  };
6884
+ if (plan.paginationMode === "delta") {
6885
+ const watermark = coreStorage.getTraceQueryDeltaWatermark(plan, "clickhouse");
6886
+ const after = watermark ? parseDeltaWatermark(watermark) : {
6887
+ cursorId: "0",
6888
+ traceId: ""
6889
+ };
6890
+ return {
6891
+ query: `${candidates}, delta_candidates AS (
6892
+ SELECT traceId, max(cursorId) AS latestCursorId
6893
+ FROM ${TABLE_TRACE_ROOTS_DELTA}
6894
+ WHERE tuple(cursorId, traceId) > ${`tuple(${parameters.add(after.cursorId, "UInt64")}, ${parameters.add(after.traceId, "String")})`} ${deltaHead ? `AND tuple(cursorId, traceId) <= tuple(${parameters.add(deltaHead.cursorId, "UInt64")}, ${parameters.add(deltaHead.traceId, "String")})` : ""}
6895
+ GROUP BY traceId
6896
+ )
6897
+ SELECT c.*, toString(d.latestCursorId) AS __delta_cursor
6898
+ FROM candidates c
6899
+ INNER JOIN delta_candidates d ON c.traceId = d.traceId
6900
+ ORDER BY d.latestCursorId ASC, c.traceId ASC
6901
+ LIMIT ${parameters.add(plan.limit + 1, "UInt64")}`,
6902
+ query_params: parameters.params,
6903
+ sharedSnapshot: true
6904
+ };
6905
+ }
6751
6906
  const orderField = resolveOrderField(plan.orderBy.field);
6752
6907
  const direction = plan.orderBy.direction === "asc" ? "ASC" : "DESC";
6908
+ if (plan.paginationMode === "page") return {
6909
+ query: `${candidates},
6910
+ page_rows AS (
6911
+ SELECT *, row_number() OVER (ORDER BY ${orderField} ${direction}, traceId ASC) AS __row_position
6912
+ FROM candidates
6913
+ ORDER BY ${orderField} ${direction}, traceId ASC
6914
+ LIMIT ${parameters.add(plan.perPage, "UInt64")} OFFSET ${parameters.add(plan.page * plan.perPage, "UInt64")}
6915
+ ),
6916
+ page_total AS (
6917
+ SELECT count() AS total
6918
+ FROM candidates
6919
+ )
6920
+ SELECT page_rows.*, page_total.total, 0 AS __metadata
6921
+ FROM page_rows
6922
+ CROSS JOIN page_total
6923
+ UNION ALL
6924
+ SELECT
6925
+ '' AS traceId,
6926
+ '' AS rootSpanId,
6927
+ '' AS name,
6928
+ CAST(NULL, 'Nullable(String)') AS entityId,
6929
+ CAST(NULL, 'Nullable(String)') AS parentSpanId,
6930
+ CAST(NULL, 'Nullable(String)') AS metadata,
6931
+ CAST(NULL, 'Nullable(String)') AS input,
6932
+ CAST(NULL, 'Nullable(String)') AS threadId,
6933
+ CAST(NULL, 'Nullable(String)') AS resourceId,
6934
+ toDateTime64(0, 3, 'UTC') AS startedAt,
6935
+ toDateTime64(0, 3, 'UTC') AS endedAt,
6936
+ CAST(NULL, 'Nullable(String)') AS entityName,
6937
+ CAST(NULL, 'Nullable(String)') AS entityType,
6938
+ CAST(NULL, 'Nullable(String)') AS environment,
6939
+ '' AS status,
6940
+ 0 AS __row_position,
6941
+ page_total.total AS total,
6942
+ 1 AS __metadata
6943
+ FROM page_total
6944
+ ORDER BY __metadata ASC, __row_position ASC`,
6945
+ query_params: parameters.params,
6946
+ sharedSnapshot: true
6947
+ };
6753
6948
  let pageCondition = "";
6754
6949
  if (plan.cursor) {
6755
6950
  const comparison = plan.orderBy.direction === "asc" ? ">" : "<";
@@ -6896,7 +7091,8 @@ async function runWithClickHouseTraceQueryTimeout(client, limits, compiled, quer
6896
7091
  clickhouse_settings: {
6897
7092
  ...CH_SETTINGS,
6898
7093
  max_execution_time: resolvedTimeoutMs / 1e3,
6899
- ...limits.memoryLimitBytes === void 0 ? {} : { max_memory_usage: String(limits.memoryLimitBytes) }
7094
+ ...limits.memoryLimitBytes === void 0 ? {} : { max_memory_usage: String(limits.memoryLimitBytes) },
7095
+ ...compiled.sharedSnapshot ? { enable_shared_storage_snapshot_in_query: 1 } : {}
6900
7096
  }
6901
7097
  })).json();
6902
7098
  } catch (error) {
@@ -6926,8 +7122,70 @@ async function getTraceQueryValues(client, plan, limits) {
6926
7122
  valuesTruncated: rows.length > plan.limit
6927
7123
  });
6928
7124
  }
6929
- async function queryTraces(client, plan, timeoutMs) {
6930
- const rows = await runWithClickHouseTraceQueryTimeout(client, { timeoutMs }, compileClickHouseTraceQuery(plan));
7125
+ async function queryTraces(client, plan, timeoutMs, strategy = null) {
7126
+ const deadline = performance.now() + coreStorage.resolveTraceQueryTimeoutMs(timeoutMs);
7127
+ const remaining = () => {
7128
+ const value = Math.floor(deadline - performance.now());
7129
+ if (value <= 0) throw new coreStorage.TraceQueryExecutionError();
7130
+ return value;
7131
+ };
7132
+ let deltaHead;
7133
+ if (plan.paginationMode === "delta") {
7134
+ assertDeltaPollingSupported(strategy);
7135
+ const watermark = coreStorage.getTraceQueryDeltaWatermark(plan, "clickhouse");
7136
+ if (watermark) parseDeltaWatermark(watermark);
7137
+ }
7138
+ if (plan.paginationMode === "delta" || plan.paginationMode === "page" && deltaPollingSupported(strategy) && typeof coreStorage.encodeTraceQueryDeltaCursor === "function") {
7139
+ const head = await runWithClickHouseTraceQueryTimeout(client, { timeoutMs: remaining() }, {
7140
+ query: `SELECT toString(cursorId) AS cursorId, traceId FROM ${TABLE_TRACE_ROOTS_DELTA} ORDER BY cursorId DESC, traceId DESC LIMIT 1`,
7141
+ query_params: {}
7142
+ });
7143
+ deltaHead = head[0] ? parseDeltaWatermark(JSON.stringify(head[0])) : {
7144
+ cursorId: "0",
7145
+ traceId: ""
7146
+ };
7147
+ if (plan.paginationMode === "delta" && !plan.deltaCursor) return coreStorage.traceQueryResponseSchema.parse({
7148
+ traces: [],
7149
+ delta: {
7150
+ limit: plan.limit,
7151
+ hasMore: false
7152
+ },
7153
+ deltaCursor: coreStorage.encodeTraceQueryDeltaCursor(plan, "clickhouse", JSON.stringify(deltaHead))
7154
+ });
7155
+ }
7156
+ if (plan.paginationMode === "page") {
7157
+ const rows = await runWithClickHouseTraceQueryTimeout(client, { timeoutMs: deltaHead ? remaining() : timeoutMs }, compileClickHouseTraceQuery(plan, deltaHead));
7158
+ const total = Number(rows.at(-1)?.total ?? 0);
7159
+ const traces = rows.filter((row) => Number(row.__metadata) === 0).map((row) => ({
7160
+ traceId: String(row.traceId),
7161
+ rootSpanId: String(row.rootSpanId),
7162
+ name: row.name,
7163
+ entityId: row.entityId ?? null,
7164
+ parentSpanId: row.parentSpanId ?? null,
7165
+ createdAt: asIsoTimestamp(row.startedAt),
7166
+ metadata: parseJson(row.metadata) ?? null,
7167
+ inputPreview: coreStorage.buildInputPreview(row.input) ?? null,
7168
+ threadId: row.threadId == null ? null : String(row.threadId),
7169
+ resourceId: row.resourceId == null ? null : String(row.resourceId),
7170
+ startedAt: asIsoTimestamp(row.startedAt),
7171
+ endedAt: asIsoTimestamp(row.endedAt),
7172
+ entityName: row.entityName == null ? null : String(row.entityName),
7173
+ entityType: row.entityType == null ? null : String(row.entityType),
7174
+ environment: row.environment == null ? null : String(row.environment),
7175
+ status: row.status
7176
+ }));
7177
+ return coreStorage.traceQueryResponseSchema.parse({
7178
+ traces,
7179
+ ...deltaHead ? { deltaCursor: coreStorage.encodeTraceQueryDeltaCursor(plan, "clickhouse", JSON.stringify(deltaHead)) } : {},
7180
+ pagination: {
7181
+ total,
7182
+ page: plan.page,
7183
+ perPage: plan.perPage,
7184
+ hasMore: (plan.page + 1) * plan.perPage < total
7185
+ }
7186
+ });
7187
+ }
7188
+ const rows = await runWithClickHouseTraceQueryTimeout(client, { timeoutMs: deltaHead ? remaining() : timeoutMs }, compileClickHouseTraceQuery(plan, deltaHead));
6931
7189
  const visibleRows = rows.slice(0, plan.limit);
6932
7190
  if (plan.result === "groups") {
6933
7191
  const groups = visibleRows.map((row) => ({ threadId: String(row.threadId) }));
@@ -6959,6 +7217,23 @@ async function queryTraces(client, plan, timeoutMs) {
6959
7217
  status: row.status
6960
7218
  }));
6961
7219
  const last = traces.at(-1);
7220
+ if (plan.paginationMode === "delta") {
7221
+ const lastRow = visibleRows.at(-1);
7222
+ let watermark = lastRow ? {
7223
+ cursorId: String(lastRow.__delta_cursor),
7224
+ traceId: String(lastRow.traceId)
7225
+ } : deltaHead;
7226
+ const previous = parseDeltaWatermark(coreStorage.getTraceQueryDeltaWatermark(plan, "clickhouse"));
7227
+ if (BigInt(previous.cursorId) > BigInt(watermark.cursorId) || previous.cursorId === watermark.cursorId && previous.traceId > watermark.traceId) watermark = previous;
7228
+ return coreStorage.traceQueryResponseSchema.parse({
7229
+ traces,
7230
+ delta: {
7231
+ limit: plan.limit,
7232
+ hasMore: rows.length > plan.limit
7233
+ },
7234
+ deltaCursor: coreStorage.encodeTraceQueryDeltaCursor(plan, "clickhouse", JSON.stringify(watermark))
7235
+ });
7236
+ }
6962
7237
  return coreStorage.traceQueryResponseSchema.parse({
6963
7238
  traces,
6964
7239
  page: { next: rows.length > plan.limit && last ? coreStorage.encodeTraceQueryCursor(plan, {
@@ -7432,6 +7707,7 @@ async function batchDeleteTraces(client, args, replication) {
7432
7707
  TABLE_METRIC_EVENTS,
7433
7708
  TABLE_LOG_EVENTS,
7434
7709
  TABLE_SCORE_EVENTS,
7710
+ TABLE_SCORE_EVENTS_CURRENT,
7435
7711
  TABLE_FEEDBACK_EVENTS
7436
7712
  ];
7437
7713
  await Promise.all([...tracingTables.map((table) => client.command({
@@ -8111,10 +8387,13 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
8111
8387
  else if (existingStrategy) this.#deltaCursorStrategy = existingStrategy;
8112
8388
  else this.#deltaCursorStrategy = await detectDeltaCursorStrategy(this.#client, void 0, existingStrategy);
8113
8389
  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) });
8390
+ const coreTableDdl = this.#deltaCursorStrategy === null ? BASE_TABLE_DDL : buildAllTableDDL();
8391
+ for (const ddl of coreTableDdl) await this.#client.command({ query: applyReplicationToDDL(ddl, this.#replication) });
8116
8392
  const pendingMigrations = await filterAppliedMigrations(this.#client, ALL_MIGRATIONS);
8117
8393
  for (const migration of pendingMigrations) await this.#client.command({ query: addOnClusterToDDL(migration.sql, this.#replication) });
8394
+ const coreMvDdl = this.#deltaCursorStrategy === null ? BASE_MV_DDL : buildAllMvDDL(this.#deltaCursorStrategy);
8395
+ for (const ddl of coreMvDdl) await this.#client.command({ query: applyReplicationToDDL(ddl, this.#replication) });
8396
+ await backfillCurrentScores(this.#client);
8118
8397
  if (this.#retention) await this.applyRetention();
8119
8398
  if (this.#deltaCursorStrategy === "serial") for (const counterName of DELTA_CURSOR_COUNTER_NAMES) await this.#client.query({
8120
8399
  query: `SELECT generateSerialID({counterName:String}) AS cursorId`,
@@ -8313,9 +8592,9 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
8313
8592
  }
8314
8593
  async queryTraces(plan) {
8315
8594
  try {
8316
- return await queryTraces(this.#client, plan, this.#traceQueryTimeoutMs);
8595
+ return await queryTraces(this.#client, plan, this.#traceQueryTimeoutMs, this.#deltaCursorStrategy);
8317
8596
  } catch (error) {
8318
- if (error instanceof MastraError || error instanceof coreStorage.TraceQueryExecutionError || error instanceof coreStorage.TraceQueryResourceLimitError) throw error;
8597
+ if (error instanceof MastraError || error instanceof coreStorage.TraceQueryExecutionError || error instanceof coreStorage.TraceQueryCursorError || error instanceof coreStorage.TraceQueryResourceLimitError) throw error;
8319
8598
  throw new MastraError({
8320
8599
  id: createStorageErrorId("CLICKHOUSE", "QUERY_TRACES", "FAILED"),
8321
8600
  domain: ErrorDomain.STORAGE,