@mastra/clickhouse 1.26.0-alpha.5 → 1.27.0-alpha.0
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/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/index.cjs +260 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +259 -51
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/observability/v-next/ddl.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/index.d.ts +3 -2
- package/dist/storage/domains/observability/v-next/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/span-query.d.ts +34 -1
- package/dist/storage/domains/observability/v-next/span-query.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/trace-aggregate.d.ts +7 -1
- package/dist/storage/domains/observability/v-next/trace-aggregate.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/trace-query.d.ts +1 -1
- package/dist/storage/domains/observability/v-next/trace-query.d.ts.map +1 -1
- package/package.json +7 -7
package/dist/docs/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: mastra-clickhouse
|
|
|
3
3
|
description: Documentation for @mastra/clickhouse. Use when working with @mastra/clickhouse APIs, configuration, or implementation.
|
|
4
4
|
metadata:
|
|
5
5
|
package: "@mastra/clickhouse"
|
|
6
|
-
version: "1.
|
|
6
|
+
version: "1.27.0-alpha.0"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
## When to use
|
package/dist/index.cjs
CHANGED
|
@@ -16,7 +16,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
16
16
|
}
|
|
17
17
|
return to;
|
|
18
18
|
};
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
20
20
|
value: mod,
|
|
21
21
|
enumerable: true
|
|
22
22
|
}) : target, mod));
|
|
@@ -1610,6 +1610,7 @@ var MemoryStorageClickhouse = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1610
1610
|
metadataIndex++;
|
|
1611
1611
|
}
|
|
1612
1612
|
}
|
|
1613
|
+
const idScope = filter?.resourceId ? `WHERE id IN (SELECT id FROM ${_mastra_core_storage.TABLE_THREADS} WHERE resourceId = {resourceId:String})` : "";
|
|
1613
1614
|
const latestThreads = `
|
|
1614
1615
|
latest_threads AS (
|
|
1615
1616
|
SELECT
|
|
@@ -1620,7 +1621,7 @@ var MemoryStorageClickhouse = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1620
1621
|
toDateTime64(createdAt, 3) as createdAt,
|
|
1621
1622
|
toDateTime64(updatedAt, 3) as updatedAt
|
|
1622
1623
|
FROM ${_mastra_core_storage.TABLE_THREADS}
|
|
1623
|
-
${
|
|
1624
|
+
${idScope}
|
|
1624
1625
|
ORDER BY updatedAt DESC
|
|
1625
1626
|
LIMIT 1 BY id
|
|
1626
1627
|
)`;
|
|
@@ -2441,9 +2442,7 @@ var ObservabilityStorageClickhouse = class extends _mastra_core_storage.Observab
|
|
|
2441
2442
|
case _mastra_core_storage.TraceStatus.RUNNING:
|
|
2442
2443
|
conditions.push(`endedAt IS NULL AND (error IS NULL OR error = '')`);
|
|
2443
2444
|
break;
|
|
2444
|
-
case _mastra_core_storage.TraceStatus.SUCCESS:
|
|
2445
|
-
conditions.push(`endedAt IS NOT NULL AND (error IS NULL OR error = '')`);
|
|
2446
|
-
break;
|
|
2445
|
+
case _mastra_core_storage.TraceStatus.SUCCESS: conditions.push(`endedAt IS NOT NULL AND (error IS NULL OR error = '')`);
|
|
2447
2446
|
}
|
|
2448
2447
|
if (filters.hasChildError !== void 0) {
|
|
2449
2448
|
const finalClause = (TABLE_ENGINES[_mastra_core_storage.TABLE_SPANS] ?? "MergeTree()").startsWith("ReplacingMergeTree") ? "FINAL" : "";
|
|
@@ -3672,6 +3671,9 @@ const ALL_MIGRATIONS = [
|
|
|
3672
3671
|
addColumn(TABLE_SPAN_EVENTS, "inputPreview", "Nullable(String)"),
|
|
3673
3672
|
addColumn(TABLE_SPAN_EVENTS, "outputPreview", "Nullable(String)"),
|
|
3674
3673
|
addColumn(TABLE_SPAN_EVENTS, "errorPreview", "Nullable(String)"),
|
|
3674
|
+
addColumn(TABLE_TRACE_BRANCHES, "entityVersionId", "Nullable(String)"),
|
|
3675
|
+
addColumn(TABLE_TRACE_BRANCHES, "parentEntityVersionId", "Nullable(String)"),
|
|
3676
|
+
addColumn(TABLE_TRACE_BRANCHES, "rootEntityVersionId", "Nullable(String)"),
|
|
3675
3677
|
addColumn(TABLE_SPAN_EVENTS, "entityVersionId", "Nullable(String)"),
|
|
3676
3678
|
addColumn(TABLE_SPAN_EVENTS, "parentEntityVersionId", "Nullable(String)"),
|
|
3677
3679
|
addColumn(TABLE_SPAN_EVENTS, "rootEntityVersionId", "Nullable(String)"),
|
|
@@ -5284,7 +5286,8 @@ async function getFeedbackAggregate(client, args) {
|
|
|
5284
5286
|
const aggSql = getAggregationSql$2(args.aggregation);
|
|
5285
5287
|
const identity = buildFeedbackIdentityFilter(args);
|
|
5286
5288
|
const combined = mergeFilters$2(identity, buildFeedbackFilterConditions(args.filters));
|
|
5287
|
-
const
|
|
5289
|
+
const whereClause = toWhereClause$2(combined);
|
|
5290
|
+
const result = await queryJson$2(client, `SELECT ${aggSql} AS value FROM ${TABLE_FEEDBACK_EVENTS} FINAL ${whereClause}`, combined.params);
|
|
5288
5291
|
const value = result[0]?.value == null ? null : Number(result[0]?.value);
|
|
5289
5292
|
if (args.comparePeriod && args.filters?.timestamp) {
|
|
5290
5293
|
const ts = args.filters.timestamp;
|
|
@@ -5318,7 +5321,8 @@ async function getFeedbackAggregate(client, args) {
|
|
|
5318
5321
|
endExclusive: ts.endExclusive
|
|
5319
5322
|
}
|
|
5320
5323
|
}));
|
|
5321
|
-
const
|
|
5324
|
+
const prevWhereClause = toWhereClause$2(prevCombined);
|
|
5325
|
+
const prevResult = await queryJson$2(client, `SELECT ${aggSql} AS value FROM ${TABLE_FEEDBACK_EVENTS} FINAL ${prevWhereClause}`, prevCombined.params);
|
|
5322
5326
|
const previousValue = prevResult[0]?.value == null ? null : Number(prevResult[0]?.value);
|
|
5323
5327
|
let changePercent = null;
|
|
5324
5328
|
if (previousValue !== null && previousValue !== 0 && value !== null) changePercent = (value - previousValue) / Math.abs(previousValue) * 100;
|
|
@@ -6225,8 +6229,10 @@ function buildLegacySpanSelectExprs(legacyColumnTypes) {
|
|
|
6225
6229
|
const promotedIn = PROMOTED_METADATA_KEYS.map((k) => `'${k}'`).join(", ");
|
|
6226
6230
|
const metadataSearchExpr = has("metadata") ? `CAST(arrayFilter(x -> x.1 NOT IN (${promotedIn}) AND trim(BOTH ' ' FROM x.2) != '', arrayMap(x -> (x.1, trim(BOTH ' ' FROM x.2)), JSONExtractKeysAndValues(COALESCE("metadata", '{}'), 'String'))), 'Map(String, String)')` : `CAST(map(), 'Map(String, String)')`;
|
|
6227
6231
|
let tagsExpr = "[]";
|
|
6228
|
-
if (has("tags"))
|
|
6229
|
-
|
|
6232
|
+
if (has("tags")) {
|
|
6233
|
+
if (legacyColumnTypes.get("tags").includes("Array")) tagsExpr = `arrayFilter(x -> x != '', arrayDistinct(arrayMap(x -> trim(BOTH ' ' FROM x), "tags")))`;
|
|
6234
|
+
else tagsExpr = `arrayFilter(x -> x != '', arrayDistinct(arrayMap(x -> trim(BOTH ' ' FROM x), JSONExtract(COALESCE("tags", '[]'), 'Array(String)'))))`;
|
|
6235
|
+
}
|
|
6230
6236
|
const exprs = {
|
|
6231
6237
|
dedupeKey: `concat("traceId", ':', "spanId")`,
|
|
6232
6238
|
traceId: `"traceId"`,
|
|
@@ -6473,10 +6479,11 @@ function toWhereClause(filter) {
|
|
|
6473
6479
|
return filter.conditions.length ? `WHERE ${filter.conditions.join(" AND ")}` : "";
|
|
6474
6480
|
}
|
|
6475
6481
|
function currentScoresRelation(sourcePredicate) {
|
|
6482
|
+
const whereClause = sourcePredicate ? `WHERE ${sourcePredicate}` : "";
|
|
6476
6483
|
return `(
|
|
6477
6484
|
SELECT *
|
|
6478
6485
|
FROM ${TABLE_SCORE_EVENTS_CURRENT} FINAL
|
|
6479
|
-
${
|
|
6486
|
+
${whereClause}
|
|
6480
6487
|
)`;
|
|
6481
6488
|
}
|
|
6482
6489
|
/**
|
|
@@ -8008,8 +8015,7 @@ async function queryThreads(client, plan, timeoutMs) {
|
|
|
8008
8015
|
});
|
|
8009
8016
|
}
|
|
8010
8017
|
/** Compile a direct span-query filter using its dedicated field mapping. */
|
|
8011
|
-
function compileSpanQueryPredicate(predicate) {
|
|
8012
|
-
const parameters = new ParameterBuilder();
|
|
8018
|
+
function compileSpanQueryPredicate(predicate, parameters = new ParameterBuilder()) {
|
|
8013
8019
|
return {
|
|
8014
8020
|
sql: compileScalarPredicate(predicate, SPAN_QUERY_FIELDS, parameters),
|
|
8015
8021
|
params: parameters.params
|
|
@@ -8170,6 +8176,131 @@ function compileClickHouseSpanQuery(plan) {
|
|
|
8170
8176
|
}
|
|
8171
8177
|
};
|
|
8172
8178
|
}
|
|
8179
|
+
const branchSpanTypes = new Set(BRANCH_SPAN_TYPE_VALUES);
|
|
8180
|
+
const sameColumn = (field) => [field];
|
|
8181
|
+
/**
|
|
8182
|
+
* Span fields `trace_branches` can serve, as the `trace_branches` columns each one reads. The two
|
|
8183
|
+
* tables are expected to diverge, so this list is explicit: a selection whose fields are not all
|
|
8184
|
+
* listed reads `span_events`. A new span-query field has to be classified here deliberately, and a
|
|
8185
|
+
* missing column then costs speed, not correctness.
|
|
8186
|
+
*/
|
|
8187
|
+
const TRACE_BRANCHES_SPAN_FIELD_COLUMNS = {
|
|
8188
|
+
...Object.fromEntries([
|
|
8189
|
+
"organizationId",
|
|
8190
|
+
"resourceId",
|
|
8191
|
+
"traceId",
|
|
8192
|
+
"spanId",
|
|
8193
|
+
"parentSpanId",
|
|
8194
|
+
"name",
|
|
8195
|
+
"spanType",
|
|
8196
|
+
"startedAt",
|
|
8197
|
+
"endedAt",
|
|
8198
|
+
"timeToFirstChunkMs",
|
|
8199
|
+
"threadId",
|
|
8200
|
+
"runId",
|
|
8201
|
+
"sessionId",
|
|
8202
|
+
"userId",
|
|
8203
|
+
"requestId",
|
|
8204
|
+
"serviceName",
|
|
8205
|
+
"experimentId",
|
|
8206
|
+
"environment",
|
|
8207
|
+
"executionSource",
|
|
8208
|
+
"entityType",
|
|
8209
|
+
"entityId",
|
|
8210
|
+
"entityName",
|
|
8211
|
+
"entityVersionId",
|
|
8212
|
+
"parentEntityType",
|
|
8213
|
+
"parentEntityId",
|
|
8214
|
+
"parentEntityName",
|
|
8215
|
+
"parentEntityVersionId",
|
|
8216
|
+
"rootEntityType",
|
|
8217
|
+
"rootEntityId",
|
|
8218
|
+
"rootEntityName",
|
|
8219
|
+
"rootEntityVersionId"
|
|
8220
|
+
].map((field) => [field, sameColumn(field)])),
|
|
8221
|
+
status: ["error"],
|
|
8222
|
+
error: ["error"],
|
|
8223
|
+
durationMs: ["startedAt", "endedAt"]
|
|
8224
|
+
};
|
|
8225
|
+
/**
|
|
8226
|
+
* Whether a span selection reads `trace_branches`: `where` must restrict `spanType` to branch
|
|
8227
|
+
* types, and `trace_branches` must serve every selected field (`served`, for tests).
|
|
8228
|
+
*/
|
|
8229
|
+
function readsTraceBranches(where, fields, served = TRACE_BRANCHES_SPAN_FIELD_COLUMNS) {
|
|
8230
|
+
return positiveSpanConjuncts(where).some(({ field, values }) => field === "spanType" && values.length > 0 && values.every((value) => branchSpanTypes.has(String(value)))) && [...fields].every((field) => Object.hasOwn(served, field));
|
|
8231
|
+
}
|
|
8232
|
+
/**
|
|
8233
|
+
* Top-level `spanType` / `name` equality or `in` conditions. Every span the predicate can match
|
|
8234
|
+
* satisfies them, and copies of a span never differ in either field, so they can be applied
|
|
8235
|
+
* before collapsing copies.
|
|
8236
|
+
*/
|
|
8237
|
+
function positiveSpanConjuncts(where) {
|
|
8238
|
+
return (where?.type === "boolean" && where.operator === "and" ? where.args : where ? [where] : []).flatMap((conjunct) => {
|
|
8239
|
+
if (conjunct.type !== "comparison" && conjunct.type !== "membership") return [];
|
|
8240
|
+
if (conjunct.field !== "spanType" && conjunct.field !== "name") return [];
|
|
8241
|
+
if (conjunct.type === "comparison" && conjunct.operator === "eq") return [{
|
|
8242
|
+
field: conjunct.field,
|
|
8243
|
+
values: [conjunct.value]
|
|
8244
|
+
}];
|
|
8245
|
+
if (conjunct.type === "membership" && conjunct.operator === "in") return [{
|
|
8246
|
+
field: conjunct.field,
|
|
8247
|
+
values: conjunct.values
|
|
8248
|
+
}];
|
|
8249
|
+
return [];
|
|
8250
|
+
});
|
|
8251
|
+
}
|
|
8252
|
+
/**
|
|
8253
|
+
* The spans `querySpans()` selects, as CTEs ending in `selected` (alias each row as you like):
|
|
8254
|
+
* one row per current completed span with `startedAt` in the window, matching `where`, carrying
|
|
8255
|
+
* the identity, `startedAt`, `endedAt`, `status`, and `fields`.
|
|
8256
|
+
*
|
|
8257
|
+
* Copies of a span collapse to the copy with the latest `endedAt`. When `where` requires branch
|
|
8258
|
+
* span types (agent and workflow runs, tool calls, …) and `trace_branches` serves every selected
|
|
8259
|
+
* field, the spans are read from `trace_branches`, whose sort key starts with `spanType`, instead of
|
|
8260
|
+
* every span in the window. That path can differ from `querySpans()` in two ways:
|
|
8261
|
+
* - `trace_branches` is filled only by its materialized view, with no backfill, so an install that
|
|
8262
|
+
* upgraded from @mastra/clickhouse 1.4–1.6 has no branch rows for spans written before then.
|
|
8263
|
+
* - It is a `ReplacingMergeTree` without a version column, so once a background merge collapses a
|
|
8264
|
+
* retried span's copies it keeps the last-inserted copy, not the one with the latest `endedAt`.
|
|
8265
|
+
* Counts are unaffected; `status` and duration of an out-of-order retry can differ.
|
|
8266
|
+
*
|
|
8267
|
+
* Like `querySpans()`, the read has no upper bound on `endedAt` (`PREWHERE endedAt >= from` only,
|
|
8268
|
+
* since a span that starts in the window can end any time later), so a historical window reads
|
|
8269
|
+
* every span that ended since `from`.
|
|
8270
|
+
*/
|
|
8271
|
+
function compileClickHouseSpanSelection(plan, fields, parameters) {
|
|
8272
|
+
const from = parameters.add(plan.timeRange.from, "DateTime64(3, 'UTC')");
|
|
8273
|
+
const to = parameters.add(plan.timeRange.to, "DateTime64(3, 'UTC')");
|
|
8274
|
+
const prefilter = positiveSpanConjuncts(plan.where).map(({ field, values }) => values.length ? `\n AND e.${field} IN (${values.map((value) => parameters.add(String(value), "String")).join(", ")})` : "\n AND 0").join("");
|
|
8275
|
+
const predicate = plan.where ? compileSpanQueryPredicate(plan.where, parameters).sql : "1";
|
|
8276
|
+
let tenant = "";
|
|
8277
|
+
if (plan.scope) {
|
|
8278
|
+
tenant += `\n AND e.organizationId = ${parameters.add(plan.scope.organizationId, "String")}`;
|
|
8279
|
+
if (plan.scope.resourceId !== void 0) tenant += `\n AND e.resourceId = ${parameters.add(plan.scope.resourceId, "String")}`;
|
|
8280
|
+
}
|
|
8281
|
+
const selectionFields = [.../* @__PURE__ */ new Set([
|
|
8282
|
+
"startedAt",
|
|
8283
|
+
"endedAt",
|
|
8284
|
+
"status",
|
|
8285
|
+
...fields,
|
|
8286
|
+
...predicateFields$1(plan.where)
|
|
8287
|
+
])].filter((field) => !new Set(identityFields).has(field));
|
|
8288
|
+
const branches = readsTraceBranches(plan.where, selectionFields);
|
|
8289
|
+
const scan = `FROM ${branches ? TABLE_TRACE_BRANCHES : TABLE_SPAN_EVENTS} e
|
|
8290
|
+
PREWHERE e.endedAt >= ${from}
|
|
8291
|
+
WHERE e.startedAt >= ${from} AND e.startedAt < ${to}${tenant}${prefilter}`;
|
|
8292
|
+
return [`span_candidates AS (
|
|
8293
|
+
${branches ? `SELECT ${identityFields.map((field) => `e.${field} AS ${field}`).join(", ")}, ${selectionFields.map((field) => field === "endedAt" ? "max(e.endedAt) AS endedAt" : `argMax(tuple(${fieldExpression(field)}), e.endedAt).1 AS ${field}`).join(", ")}
|
|
8294
|
+
${scan}
|
|
8295
|
+
GROUP BY ${identityFields.map((field) => `e.${field}`).join(", ")}` : `SELECT ${identities}, ${selectionFields.map((field) => `${fieldExpression(field)} AS ${field}`).join(", ")}
|
|
8296
|
+
${scan}
|
|
8297
|
+
ORDER BY ${identities}, e.endedAt DESC
|
|
8298
|
+
LIMIT 1 BY ${identities}`}
|
|
8299
|
+
)`, `selected AS (
|
|
8300
|
+
SELECT * FROM span_candidates s
|
|
8301
|
+
WHERE s.startedAt >= ${from} AND s.startedAt < ${to} AND (${predicate})
|
|
8302
|
+
)`];
|
|
8303
|
+
}
|
|
8173
8304
|
const LEGACY_PAYLOAD_BATCH_ROWS = 32;
|
|
8174
8305
|
function iso$1(value) {
|
|
8175
8306
|
return new Date(value.includes("T") ? value : `${value.replace(" ", "T")}Z`).toISOString();
|
|
@@ -8181,7 +8312,7 @@ async function querySpans(client, plan, timeoutMs) {
|
|
|
8181
8312
|
if (remaining <= 0) throw new _mastra_core_storage.TraceQueryExecutionError();
|
|
8182
8313
|
return await runWithClickHouseTraceQueryTimeout(client, {
|
|
8183
8314
|
timeoutMs: remaining,
|
|
8184
|
-
memoryLimitBytes:
|
|
8315
|
+
memoryLimitBytes: 536870912
|
|
8185
8316
|
}, compiled);
|
|
8186
8317
|
};
|
|
8187
8318
|
const selected = (await run(compileClickHouseSpanQuery(plan))).map((row) => ({
|
|
@@ -8298,14 +8429,14 @@ function afterCursor(plan, parameters) {
|
|
|
8298
8429
|
return `(${terms.join(" OR ")})`;
|
|
8299
8430
|
}
|
|
8300
8431
|
const THREAD_QUERY_EXECUTION_LIMITS = {
|
|
8301
|
-
memoryLimitBytes:
|
|
8432
|
+
memoryLimitBytes: 536870912,
|
|
8302
8433
|
maxThreads: 2,
|
|
8303
8434
|
maxBlockSize: 4096,
|
|
8304
|
-
spillThresholdBytes:
|
|
8435
|
+
spillThresholdBytes: 33554432
|
|
8305
8436
|
};
|
|
8306
8437
|
const MAX_CANDIDATE_THREADS = 16384;
|
|
8307
8438
|
const MAX_CANDIDATE_TRACES = 1e5;
|
|
8308
|
-
const MAX_CANDIDATE_ID_BYTES =
|
|
8439
|
+
const MAX_CANDIDATE_ID_BYTES = 4194304;
|
|
8309
8440
|
const MAX_FULL_HISTORY_ROOTS = 25e4;
|
|
8310
8441
|
function canUseThreadCandidates(probe) {
|
|
8311
8442
|
return Number(probe.threadCount) <= MAX_CANDIDATE_THREADS && Number(probe.traceCount) <= MAX_CANDIDATE_TRACES && Number(probe.threadBytes) + Number(probe.traceBytes) <= MAX_CANDIDATE_ID_BYTES;
|
|
@@ -8415,7 +8546,7 @@ function* batches(rows, maxRows = 256) {
|
|
|
8415
8546
|
let bytes = 0;
|
|
8416
8547
|
for (const row of rows) {
|
|
8417
8548
|
const size = Buffer.byteLength(JSON.stringify(row), "utf8") * 2;
|
|
8418
|
-
if (batch.length && (batch.length >= maxRows || bytes + size >
|
|
8549
|
+
if (batch.length && (batch.length >= maxRows || bytes + size > 49152)) {
|
|
8419
8550
|
yield batch;
|
|
8420
8551
|
batch = [];
|
|
8421
8552
|
bytes = 0;
|
|
@@ -8573,6 +8704,28 @@ const DIMENSION_SQL = {
|
|
|
8573
8704
|
organizationId: "r.organizationId",
|
|
8574
8705
|
experimentId: "r.experimentId"
|
|
8575
8706
|
};
|
|
8707
|
+
/** Span column expressions (alias `r`, a `selected` span row) for each groupable span dimension. */
|
|
8708
|
+
const SPAN_DIMENSION_SQL = Object.fromEntries([
|
|
8709
|
+
"name",
|
|
8710
|
+
"spanType",
|
|
8711
|
+
"entityType",
|
|
8712
|
+
"entityId",
|
|
8713
|
+
"entityName",
|
|
8714
|
+
"parentEntityType",
|
|
8715
|
+
"parentEntityName",
|
|
8716
|
+
"rootEntityType",
|
|
8717
|
+
"rootEntityName",
|
|
8718
|
+
"environment",
|
|
8719
|
+
"status",
|
|
8720
|
+
"serviceName",
|
|
8721
|
+
"executionSource",
|
|
8722
|
+
"threadId",
|
|
8723
|
+
"resourceId",
|
|
8724
|
+
"userId",
|
|
8725
|
+
"sessionId",
|
|
8726
|
+
"organizationId",
|
|
8727
|
+
"experimentId"
|
|
8728
|
+
].map((field) => [field, `r.${field}`]));
|
|
8576
8729
|
const PERCENTILES = {
|
|
8577
8730
|
"duration.p50": .5,
|
|
8578
8731
|
"duration.p90": .9,
|
|
@@ -8599,13 +8752,41 @@ const COMPARISON_SQL = {
|
|
|
8599
8752
|
*/
|
|
8600
8753
|
function compileClickHouseTraceAggregate(plan) {
|
|
8601
8754
|
const parameters = new ParameterBuilder();
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8755
|
+
return compileClickHouseAggregate(plan, parameters, {
|
|
8756
|
+
ctes: compileClickHouseTraceCandidates(plan, "*", parameters, { oneRootPerTrace: true }),
|
|
8757
|
+
from: "candidates",
|
|
8758
|
+
dimensionSql: (field) => {
|
|
8759
|
+
if (field.startsWith("metadata.")) return traceMetadataValueSql(parameters.add(field.slice(9), "String"));
|
|
8760
|
+
return lookupDimensionSql(DIMENSION_SQL, field);
|
|
8761
|
+
},
|
|
8762
|
+
isErrorSql: "isNotNull(r.error)",
|
|
8763
|
+
usageKey: ["traceId"],
|
|
8764
|
+
usageBound: "traceId IN (SELECT traceId FROM candidates)"
|
|
8765
|
+
});
|
|
8766
|
+
}
|
|
8767
|
+
/**
|
|
8768
|
+
* Compiles a trusted span aggregate plan over the spans `querySpans()` selects. Usage is
|
|
8769
|
+
* attributed by `(traceId, spanId)`, so only the span that recorded a metric row carries it.
|
|
8770
|
+
*/
|
|
8771
|
+
function compileClickHouseSpanAggregate(plan) {
|
|
8772
|
+
const parameters = new ParameterBuilder();
|
|
8773
|
+
return compileClickHouseAggregate(plan, parameters, {
|
|
8774
|
+
ctes: compileClickHouseSpanSelection(plan, [...plan.dimensions, ...plan.measures.flatMap((measure) => measure.type === "countDistinct" && measure.field !== "traceId" ? [measure.field] : [])], parameters),
|
|
8775
|
+
from: "selected",
|
|
8776
|
+
dimensionSql: (field) => lookupDimensionSql(SPAN_DIMENSION_SQL, field),
|
|
8777
|
+
isErrorSql: "r.status = 'error'",
|
|
8778
|
+
usageKey: ["traceId", "spanId"],
|
|
8779
|
+
usageBound: "isNotNull(spanId)"
|
|
8780
|
+
});
|
|
8781
|
+
}
|
|
8782
|
+
function lookupDimensionSql(columns, field) {
|
|
8783
|
+
const sql = Object.hasOwn(columns, field) ? columns[field] : void 0;
|
|
8784
|
+
if (sql === void 0) throw new Error(`Unsupported trusted aggregate field: ${field}`);
|
|
8785
|
+
return sql;
|
|
8786
|
+
}
|
|
8787
|
+
function compileClickHouseAggregate(plan, parameters, source) {
|
|
8788
|
+
const { ctes } = source;
|
|
8789
|
+
const dimensionSql = source.dimensionSql;
|
|
8609
8790
|
const distinctSql = (field) => field === "traceId" ? "r.traceId" : dimensionSql(field);
|
|
8610
8791
|
const dimensionColumns = plan.dimensions.map((_, index) => `d${index}`);
|
|
8611
8792
|
const distinctColumns = /* @__PURE__ */ new Map();
|
|
@@ -8616,7 +8797,7 @@ function compileClickHouseTraceAggregate(plan) {
|
|
|
8616
8797
|
distinctColumns.set(measure.field, column);
|
|
8617
8798
|
factColumns.push(`CAST(${distinctSql(measure.field)} AS Nullable(String)) AS ${column}`);
|
|
8618
8799
|
}
|
|
8619
|
-
factColumns.push(`toFloat64(${durationMsSql("r.startedAt", "r.endedAt")}) AS durationMs`,
|
|
8800
|
+
factColumns.push(`toFloat64(${durationMsSql("r.startedAt", "r.endedAt")}) AS durationMs`, `${source.isErrorSql} AS isError`, `cityHash64(${source.usageKey.map((column) => `r.${column}`).join(", ")}) AS traceSeed`);
|
|
8620
8801
|
if (plan.interval !== void 0) {
|
|
8621
8802
|
const intervalMs = _mastra_core_storage.TRACE_AGGREGATE_INTERVAL_MS[plan.interval];
|
|
8622
8803
|
if (intervalMs === void 0) throw new Error(`Unsupported trusted trace-aggregate interval: ${plan.interval}`);
|
|
@@ -8626,14 +8807,14 @@ function compileClickHouseTraceAggregate(plan) {
|
|
|
8626
8807
|
const hasCost = measureKinds.has("cost");
|
|
8627
8808
|
let usageJoin = "";
|
|
8628
8809
|
if (hasCost || measureKinds.has("tokens")) {
|
|
8629
|
-
ctes.push(compileUsageCte(plan, parameters));
|
|
8630
|
-
usageJoin =
|
|
8810
|
+
ctes.push(compileUsageCte(plan, parameters, source));
|
|
8811
|
+
usageJoin = `\n LEFT JOIN usage u ON ${source.usageKey.map((column) => `u.${column} = r.${column}`).join(" AND ")}`;
|
|
8631
8812
|
USAGE_METRIC_NAMES.forEach((_, index) => factColumns.push(`if(u.hasUsage = 1, u.t${index}, NULL) AS t${index}`));
|
|
8632
8813
|
factColumns.push(`u.hasUsage = 1 AS usageBearing`, `if(u.hasUsage = 1 AND u.priced = 1, u.cost, NULL) AS traceCost`, `u.hasUsage = 1 AND u.priced = 1 AND u.pricingFailure = 0 AS covered`, `if(u.hasUsage = 1 AND u.priced = 1, u.unitMin, NULL) AS unitMin`, `if(u.hasUsage = 1 AND u.priced = 1, u.unitMax, NULL) AS unitMax`);
|
|
8633
8814
|
}
|
|
8634
8815
|
ctes.push(`facts AS (
|
|
8635
8816
|
SELECT ${factColumns.join(",\n ")}
|
|
8636
|
-
FROM
|
|
8817
|
+
FROM ${source.from} r${usageJoin}
|
|
8637
8818
|
)`);
|
|
8638
8819
|
const mixedUnitsSql = "minOrNull(unitMin) != maxOrNull(unitMax)";
|
|
8639
8820
|
const measureSql = (name) => {
|
|
@@ -8748,26 +8929,28 @@ function usageMetricIndex(name) {
|
|
|
8748
8929
|
return index;
|
|
8749
8930
|
}
|
|
8750
8931
|
/**
|
|
8751
|
-
* Builds `usage`: one row per
|
|
8752
|
-
* per-name token sums (`t<i>`, indexed by `TRACE_AGGREGATE_USAGE_METRIC_NAMES`) and
|
|
8753
|
-
* pricing state. Metric rows are matched by
|
|
8754
|
-
* `timestamp >= from` only: a trace that starts in the window can emit
|
|
8755
|
-
* the
|
|
8932
|
+
* Builds `usage`: one row per usage key (a trace, or a span) that has at least one token metric
|
|
8933
|
+
* row, holding per-name token sums (`t<i>`, indexed by `TRACE_AGGREGATE_USAGE_METRIC_NAMES`) and
|
|
8934
|
+
* its pricing state. Metric rows are matched by the source's usage bound, usage metric name,
|
|
8935
|
+
* tenant scope, and `timestamp >= from` only: a trace or span that starts in the window can emit
|
|
8936
|
+
* metrics after `to`. For spans that makes the read cost grow with the age of `from` (see
|
|
8937
|
+
* `usageBound`).
|
|
8756
8938
|
*
|
|
8757
8939
|
* Rows are deduplicated with `LIMIT 1 BY metricId` at query time. `ReplacingMergeTree` only
|
|
8758
8940
|
* collapses rows with equal sort keys `(name, timestamp, metricId)`, so retried copies with
|
|
8759
8941
|
* different timestamps never merge, and copies with equal keys stay separate until a background
|
|
8760
8942
|
* merge runs.
|
|
8761
8943
|
*/
|
|
8762
|
-
function compileUsageCte(plan, parameters) {
|
|
8944
|
+
function compileUsageCte(plan, parameters, source) {
|
|
8945
|
+
const key = source.usageKey.join(", ");
|
|
8763
8946
|
const names = USAGE_METRIC_NAMES.map((name) => parameters.add(name, "String"));
|
|
8764
8947
|
const costRow = `name IN (${_mastra_core_storage.TRACE_AGGREGATE_COST_METRIC_NAMES.map((name) => names[usageMetricIndex(name)]).join(", ")})`;
|
|
8765
8948
|
const from = parameters.add(plan.timeRange.from, "DateTime64(3, 'UTC')");
|
|
8766
8949
|
const tenant = compileTenantScope(plan.scope, parameters);
|
|
8767
|
-
const priced = `${costRow} AND isNotNull(estimatedCost) AND isNotNull(costUnit) AND NOT hasError`;
|
|
8950
|
+
const priced = `${costRow} AND isNotNull(estimatedCost) AND isNotNull(costUnit) AND (NOT hasError OR partialCost)`;
|
|
8768
8951
|
const failed = `${costRow} AND (hasError OR (isNotNull(estimatedCost) AND isNull(costUnit)))`;
|
|
8769
8952
|
return `usage AS (
|
|
8770
|
-
SELECT
|
|
8953
|
+
SELECT ${key},
|
|
8771
8954
|
toUInt8(1) AS hasUsage,
|
|
8772
8955
|
${names.map((name, index) => `sumIf(value, name = ${name}) AS t${index}`).join(",\n ")},
|
|
8773
8956
|
sumIf(assumeNotNull(estimatedCost), ${priced}) AS cost,
|
|
@@ -8776,34 +8959,42 @@ function compileUsageCte(plan, parameters) {
|
|
|
8776
8959
|
minIf(assumeNotNull(costUnit), ${priced}) AS unitMin,
|
|
8777
8960
|
maxIf(assumeNotNull(costUnit), ${priced}) AS unitMax
|
|
8778
8961
|
FROM (
|
|
8779
|
-
SELECT
|
|
8962
|
+
SELECT ${key},
|
|
8780
8963
|
latest.1 AS name,
|
|
8781
8964
|
latest.2 AS value,
|
|
8782
8965
|
latest.3 AS estimatedCost,
|
|
8783
8966
|
latest.4 AS costUnit,
|
|
8784
|
-
latest.5 AS hasError
|
|
8967
|
+
latest.5 AS hasError,
|
|
8968
|
+
latest.6 AS partialCost
|
|
8785
8969
|
FROM (
|
|
8786
|
-
SELECT
|
|
8787
|
-
argMax(tuple(name, value, estimatedCost, costUnit, hasError), timestamp) AS latest
|
|
8970
|
+
SELECT ${key},
|
|
8971
|
+
argMax(tuple(name, value, estimatedCost, costUnit, hasError, partialCost), timestamp) AS latest
|
|
8788
8972
|
FROM (
|
|
8789
|
-
SELECT
|
|
8790
|
-
if(${costRow}, ifNull(JSONHas(costMetadata, 'error') AND JSONType(costMetadata, 'error') != 'Null', 0), 0) AS hasError
|
|
8973
|
+
SELECT ${key}, metricId, timestamp, name, value, estimatedCost, costUnit,
|
|
8974
|
+
if(${costRow}, ifNull(JSONHas(costMetadata, 'error') AND JSONType(costMetadata, 'error') != 'Null', 0), 0) AS hasError,
|
|
8975
|
+
if(${costRow}, ifNull(JSONExtractString(costMetadata, 'error') = 'partial_cost', 0), 0) AS partialCost
|
|
8791
8976
|
FROM ${TABLE_METRIC_EVENTS}
|
|
8792
|
-
WHERE
|
|
8977
|
+
WHERE ${source.usageBound}
|
|
8793
8978
|
AND name IN (${names.join(", ")})
|
|
8794
8979
|
AND timestamp >= ${from}${tenant}
|
|
8795
8980
|
)
|
|
8796
|
-
GROUP BY
|
|
8981
|
+
GROUP BY ${key}, metricId
|
|
8797
8982
|
)
|
|
8798
8983
|
)
|
|
8799
|
-
GROUP BY
|
|
8984
|
+
GROUP BY ${key}
|
|
8800
8985
|
)`;
|
|
8801
8986
|
}
|
|
8802
8987
|
function nullableNumber(value) {
|
|
8803
8988
|
return value === null || value === void 0 ? null : Number(value);
|
|
8804
8989
|
}
|
|
8805
|
-
|
|
8806
|
-
|
|
8990
|
+
function aggregateTraces(client, plan, timeoutMs) {
|
|
8991
|
+
return runAggregate(client, plan, compileClickHouseTraceAggregate(plan), timeoutMs);
|
|
8992
|
+
}
|
|
8993
|
+
async function aggregateSpans(client, plan, timeoutMs) {
|
|
8994
|
+
return await runAggregate(client, plan, compileClickHouseSpanAggregate(plan), timeoutMs);
|
|
8995
|
+
}
|
|
8996
|
+
async function runAggregate(client, plan, compiled, timeoutMs) {
|
|
8997
|
+
const result = await runWithClickHouseTraceQueryTimeout(client, { timeoutMs }, compiled);
|
|
8807
8998
|
let truncated;
|
|
8808
8999
|
let resultRows;
|
|
8809
9000
|
if (plan.interval === void 0) {
|
|
@@ -9699,6 +9890,7 @@ function buildBranchCursor(row) {
|
|
|
9699
9890
|
//#endregion
|
|
9700
9891
|
//#region src/storage/domains/observability/v-next/index.ts
|
|
9701
9892
|
const spanQueryFeatures = typeof _mastra_core_storage.planSpanQuery === "function" ? ["span-query", "span-query-context-fields"] : [];
|
|
9893
|
+
const spanAggregateFeatures = typeof _mastra_core_storage.planSpanAggregate === "function" ? ["span-aggregate"] : [];
|
|
9702
9894
|
const threadQueryFeatures = typeof _mastra_core_storage.planThreadRowsQuery === "function" ? ["thread-query-rows"] : [];
|
|
9703
9895
|
function buildSignalMigrationRequiredMessage(args) {
|
|
9704
9896
|
const tableList = args.tables.map((table) => ` - ${table.table} (${table.engine})`).join("\n");
|
|
@@ -9985,7 +10177,7 @@ async function detectExistingDeltaCursorStrategy(client) {
|
|
|
9985
10177
|
}
|
|
9986
10178
|
}
|
|
9987
10179
|
const TRACE_QUERY_DISCOVERY_DEFAULT_TIMEOUT_MS = 5e3;
|
|
9988
|
-
const TRACE_QUERY_DISCOVERY_DEFAULT_MEMORY_LIMIT_BYTES =
|
|
10180
|
+
const TRACE_QUERY_DISCOVERY_DEFAULT_MEMORY_LIMIT_BYTES = 268435456;
|
|
9989
10181
|
function resolveTraceQueryDiscoveryMemoryLimitBytes(memoryLimitBytes = TRACE_QUERY_DISCOVERY_DEFAULT_MEMORY_LIMIT_BYTES) {
|
|
9990
10182
|
if (!Number.isSafeInteger(memoryLimitBytes) || memoryLimitBytes <= 0) throw new RangeError("traceQuery.discovery.memoryLimitBytes must be a positive safe integer");
|
|
9991
10183
|
return memoryLimitBytes;
|
|
@@ -10142,6 +10334,7 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
|
|
|
10142
10334
|
"trace-query-root-entity-id",
|
|
10143
10335
|
"trace-aggregate",
|
|
10144
10336
|
...spanQueryFeatures,
|
|
10337
|
+
...spanAggregateFeatures,
|
|
10145
10338
|
...threadQueryFeatures
|
|
10146
10339
|
];
|
|
10147
10340
|
return [
|
|
@@ -10166,6 +10359,7 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
|
|
|
10166
10359
|
"trace-query-root-entity-id",
|
|
10167
10360
|
"trace-aggregate",
|
|
10168
10361
|
...spanQueryFeatures,
|
|
10362
|
+
...spanAggregateFeatures,
|
|
10169
10363
|
...threadQueryFeatures
|
|
10170
10364
|
];
|
|
10171
10365
|
}
|
|
@@ -10317,6 +10511,18 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
|
|
|
10317
10511
|
}, error);
|
|
10318
10512
|
}
|
|
10319
10513
|
}
|
|
10514
|
+
async aggregateSpans(plan) {
|
|
10515
|
+
try {
|
|
10516
|
+
return await aggregateSpans(this.#client, plan, this.#traceQueryTimeoutMs);
|
|
10517
|
+
} catch (error) {
|
|
10518
|
+
if (error instanceof _mastra_core_error.MastraError || error instanceof _mastra_core_storage.TraceQueryExecutionError || error instanceof _mastra_core_storage.TraceQueryResourceLimitError) throw error;
|
|
10519
|
+
throw new _mastra_core_error.MastraError({
|
|
10520
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("CLICKHOUSE", "AGGREGATE_SPANS", "FAILED"),
|
|
10521
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
10522
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY
|
|
10523
|
+
}, error);
|
|
10524
|
+
}
|
|
10525
|
+
}
|
|
10320
10526
|
async getTraceQueryObservedFields(plan) {
|
|
10321
10527
|
try {
|
|
10322
10528
|
return await getTraceQueryObservedFields(this.#client, plan, this.#traceQueryDiscoveryLimits);
|
|
@@ -11378,10 +11584,12 @@ var WorkflowsStorageClickhouse = class extends _mastra_core_storage.WorkflowsSto
|
|
|
11378
11584
|
conditions.push(`JSONExtractString(snapshot, 'status') = {var_status:String}`);
|
|
11379
11585
|
values.var_status = status;
|
|
11380
11586
|
}
|
|
11381
|
-
if (resourceId)
|
|
11382
|
-
|
|
11383
|
-
|
|
11384
|
-
|
|
11587
|
+
if (resourceId) {
|
|
11588
|
+
if (await this.#db.hasColumn(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "resourceId")) {
|
|
11589
|
+
conditions.push(`resourceId = {var_resourceId:String}`);
|
|
11590
|
+
values.var_resourceId = resourceId;
|
|
11591
|
+
} else this.logger.warn(`[${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
|
|
11592
|
+
}
|
|
11385
11593
|
if (fromDate) {
|
|
11386
11594
|
conditions.push(`createdAt >= {var_from_date:DateTime64(3)}`);
|
|
11387
11595
|
values.var_from_date = fromDate.getTime() / 1e3;
|