Skip to content

Commit 478422f

Browse files
carderneTrigger.dev RepoOps
authored andcommitted
perf(clickhouse): serialize task event attributes_text in the writer
Span and log attributes are now serialized by the server before being stored, so the attribute text shown in the dashboard is exactly what your task sent. Mono-RevId: b789b40bd993d5096f0e10ca57554ecb0bfd12e9
1 parent 701374e commit 478422f

2 files changed

Lines changed: 28 additions & 21 deletions

File tree

‎internal-packages/clickhouse/src/taskEvents.test.ts‎

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,10 @@ function readColumnKinds(ch: ClickHouse) {
6363

6464
describe("task events v2", () => {
6565
clickhouseTest(
66-
"computes attributes_text when the writer omits it",
66+
"stores attributes_text serialized by the writer",
6767
async ({ clickhouseContainer }) => {
6868
const ch = new ClickHouse({ url: clickhouseContainer.getConnectionUrl(), name: "test" });
69-
const spanId = "span_computed_attributes";
69+
const spanId = "span_serialized_attributes";
7070

7171
const [insertError] = await ch.taskEventsV2.insert([
7272
{
@@ -87,23 +87,23 @@ describe("task events v2", () => {
8787
expect(readError).toBeNull();
8888
expect(rows).toEqual([
8989
{
90-
attributes_text: '{"a":"hello","nested":{"enabled":true},"z":1}',
90+
attributes_text: '{"z":1,"a":"hello","nested":{"enabled":true}}',
9191
has_inserted_at: 1,
9292
},
9393
]);
9494
}
9595
);
9696

9797
clickhouseTest(
98-
"accepts attributes_text supplied by the writer",
98+
"computes attributes_text for a writer that omits it",
9999
async ({ clickhouseContainer }) => {
100100
const ch = new ClickHouse({ url: clickhouseContainer.getConnectionUrl(), name: "test" });
101-
const spanId = "span_supplied_attributes";
101+
const spanId = "span_computed_attributes";
102102

103-
// A writer that serializes attributes itself can send the column directly.
104-
// The DEFAULT expression only applies when the column is omitted.
103+
// An older writer that only sends the JSON column still gets attributes_text
104+
// from the DEFAULT expression, so both writer generations can coexist.
105105
const insert = ch.writer.insertUnsafe<Record<string, unknown>>({
106-
name: "insertTaskEventsV2WithAttributesText",
106+
name: "insertTaskEventsV2WithoutAttributesText",
107107
table: "trigger_dev.task_events_v2",
108108
columns: [
109109
"environment_id",
@@ -120,7 +120,6 @@ describe("task events v2", () => {
120120
"kind",
121121
"status",
122122
"attributes",
123-
"attributes_text",
124123
"metadata",
125124
"expires_at",
126125
],
@@ -130,8 +129,7 @@ describe("task events v2", () => {
130129
const [insertError] = await insert([
131130
{
132131
...baseEvent(spanId),
133-
attributes: { supplied: false },
134-
attributes_text: '{"supplied":true}',
132+
attributes: { z: 1, a: "hello" },
135133
},
136134
]);
137135
expect(insertError).toBeNull();
@@ -141,7 +139,7 @@ describe("task events v2", () => {
141139
spanId,
142140
});
143141
expect(readError).toBeNull();
144-
expect(rows).toEqual([{ attributes_text: '{"supplied":true}', has_inserted_at: 1 }]);
142+
expect(rows).toEqual([{ attributes_text: '{"a":"hello","z":1}', has_inserted_at: 1 }]);
145143
}
146144
);
147145

‎internal-packages/clickhouse/src/taskEvents.ts‎

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ const TASK_EVENT_V2_INSERT_COLUMNS = [
195195
"kind",
196196
"status",
197197
"attributes",
198-
"attributes_input",
198+
"attributes_text",
199199
"metadata",
200200
"expires_at",
201201
"machine_id",
@@ -226,22 +226,33 @@ export const TaskEventV2Input = z.object({
226226

227227
export type TaskEventV2Input = z.input<typeof TaskEventV2Input>;
228228

229-
type TaskEventV2DualAttributesInput = TaskEventV2Input & {
230-
attributes_input: unknown;
229+
type TaskEventV2Row = TaskEventV2Input & {
230+
attributes_text: string;
231231
};
232232

233-
function withAttributesInput(event: TaskEventV2Input): TaskEventV2DualAttributesInput {
233+
// attributes_text is serialized by the writer rather than computed by ClickHouse,
234+
// so the stored text is exactly what was sent and the table does not have to
235+
// parse and re-encode the attributes on every insert.
236+
export function serializeTaskEventAttributes(attributes: unknown): string {
237+
if (attributes === null || attributes === undefined) {
238+
return "{}";
239+
}
240+
241+
return JSON.stringify(attributes) ?? "{}";
242+
}
243+
244+
function toTaskEventV2Row(event: TaskEventV2Input): TaskEventV2Row {
234245
return {
235246
...event,
236-
attributes_input: event.attributes,
247+
attributes_text: serializeTaskEventAttributes(event.attributes),
237248
};
238249
}
239250

240251
export function insertTaskEventsV2(
241252
ch: ClickhouseWriter,
242253
settings?: ClickHouseSettings
243254
): ClickhouseInsertFunction<TaskEventV2Input> {
244-
const insert = ch.insertUnsafe<TaskEventV2DualAttributesInput>({
255+
const insert = ch.insertUnsafe<TaskEventV2Row>({
245256
name: "insertTaskEventsV2",
246257
table: "trigger_dev.task_events_v2",
247258
columns: TASK_EVENT_V2_INSERT_COLUMNS,
@@ -256,9 +267,7 @@ export function insertTaskEventsV2(
256267
});
257268

258269
return (events, options) => {
259-
const values = Array.isArray(events)
260-
? events.map(withAttributesInput)
261-
: withAttributesInput(events);
270+
const values = Array.isArray(events) ? events.map(toTaskEventV2Row) : toTaskEventV2Row(events);
262271

263272
return insert(values, options);
264273
};

0 commit comments

Comments
 (0)