Skip to content

Commit e6f1300

Browse files
committed
perf(clickhouse): stop storing native event attributes
1 parent d6b02fa commit e6f1300

2 files changed

Lines changed: 150 additions & 0 deletions

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
-- +goose Up
2+
3+
-- Full-text search is served outside the source event table. Keeping these
4+
-- indexes here adds work to every event insert and merge without serving reads.
5+
ALTER TABLE trigger_dev.task_events_v2
6+
DROP INDEX IF EXISTS idx_attributes_text_search;
7+
8+
ALTER TABLE trigger_dev.task_events_v2
9+
DROP INDEX IF EXISTS idx_message_text_search;
10+
11+
-- attributes remains an insert input for attributes_text, but is no longer
12+
-- stored. Writers must include attributes in an explicit insert column list
13+
-- because implicit INSERT column lists exclude EPHEMERAL columns.
14+
ALTER TABLE trigger_dev.task_events_v2
15+
MODIFY COLUMN attributes JSON EPHEMERAL;
16+
17+
-- +goose Down
18+
19+
-- ClickHouse 26.2 preserves EPHEMERAL when only the type or codec is modified.
20+
-- Replace the input column while keeping the stored attributes_text values.
21+
ALTER TABLE trigger_dev.task_events_v2
22+
RENAME COLUMN attributes TO attributes_ephemeral,
23+
ADD COLUMN attributes JSON CODEC(ZSTD(1)) AFTER status,
24+
MODIFY COLUMN attributes_text String MATERIALIZED toJSONString(attributes),
25+
DROP COLUMN attributes_ephemeral;
26+
27+
ALTER TABLE trigger_dev.task_events_v2
28+
ADD INDEX IF NOT EXISTS idx_attributes_text_search lower(attributes_text)
29+
TYPE ngrambf_v1(3, 32768, 2, 0)
30+
GRANULARITY 1;
31+
32+
ALTER TABLE trigger_dev.task_events_v2
33+
ADD INDEX IF NOT EXISTS idx_message_text_search lower(message)
34+
TYPE ngrambf_v1(3, 32768, 2, 0)
35+
GRANULARITY 1;

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

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,121 @@ describe("task events v2", () => {
6262
},
6363
]);
6464

65+
const readColumnKinds = ch.reader.query({
66+
name: "read-task-event-attribute-column-kinds",
67+
query: `SELECT name, default_kind, default_expression
68+
FROM system.columns
69+
WHERE database = 'trigger_dev'
70+
AND table = 'task_events_v2'
71+
AND name IN ('attributes', 'attributes_text')
72+
ORDER BY name`,
73+
schema: z.object({
74+
name: z.string(),
75+
default_kind: z.string(),
76+
default_expression: z.string(),
77+
}),
78+
});
79+
const [columnError, columns] = await readColumnKinds({});
80+
expect(columnError).toBeNull();
81+
expect(columns).toEqual([
82+
{
83+
name: "attributes",
84+
default_kind: "EPHEMERAL",
85+
default_expression: "defaultValueOfTypeName('JSON')",
86+
},
87+
{
88+
name: "attributes_text",
89+
default_kind: "MATERIALIZED",
90+
default_expression: "toJSONString(attributes)",
91+
},
92+
]);
93+
94+
const readRemovedIndexes = ch.reader.query({
95+
name: "read-removed-task-event-text-indexes",
96+
query: `SELECT name
97+
FROM system.data_skipping_indices
98+
WHERE database = 'trigger_dev'
99+
AND table = 'task_events_v2'
100+
AND name IN ('idx_attributes_text_search', 'idx_message_text_search')
101+
ORDER BY name`,
102+
schema: z.object({ name: z.string() }),
103+
});
104+
const [indexError, indexes] = await readRemovedIndexes({});
105+
expect(indexError).toBeNull();
106+
expect(indexes).toEqual([]);
107+
}
108+
);
109+
110+
clickhouseTest(
111+
"restores stored attributes for implicit inserts on rollback",
112+
async ({ clickhouseClient }) => {
113+
const table = "trigger_dev.task_events_ephemeral_rollback_test";
114+
await clickhouseClient.command({
115+
query: `CREATE TABLE ${table}
116+
(
117+
id UInt64,
118+
attributes JSON EPHEMERAL,
119+
attributes_text String MATERIALIZED toJSONString(attributes)
120+
)
121+
ENGINE = MergeTree
122+
ORDER BY id`,
123+
});
124+
125+
try {
126+
await clickhouseClient.insert({
127+
table,
128+
columns: ["id", "attributes"],
129+
format: "JSONEachRow",
130+
values: [{ id: 1, attributes: { before: "rollback" } }],
131+
});
132+
133+
await clickhouseClient.command({
134+
query: `ALTER TABLE ${table}
135+
RENAME COLUMN attributes TO attributes_ephemeral,
136+
ADD COLUMN attributes JSON CODEC(ZSTD(1)) AFTER id,
137+
MODIFY COLUMN attributes_text String MATERIALIZED toJSONString(attributes),
138+
DROP COLUMN attributes_ephemeral`,
139+
});
140+
141+
await clickhouseClient.insert({
142+
table,
143+
format: "JSONEachRow",
144+
values: [{ id: 2, attributes: { after: "rollback" } }],
145+
});
146+
147+
const columnsResult = await clickhouseClient.query({
148+
query: `SELECT default_kind, compression_codec
149+
FROM system.columns
150+
WHERE database = 'trigger_dev'
151+
AND table = 'task_events_ephemeral_rollback_test'
152+
AND name = 'attributes'`,
153+
format: "JSONEachRow",
154+
});
155+
expect(await columnsResult.json()).toEqual([
156+
{ default_kind: "", compression_codec: "CODEC(ZSTD(1))" },
157+
]);
158+
159+
const rowsResult = await clickhouseClient.query({
160+
query: `SELECT id, attributes_text, toJSONString(attributes) AS attributes_json
161+
FROM ${table}
162+
ORDER BY id`,
163+
format: "JSONEachRow",
164+
});
165+
expect(await rowsResult.json()).toEqual([
166+
{
167+
id: 1,
168+
attributes_text: '{"before":"rollback"}',
169+
attributes_json: "{}",
170+
},
171+
{
172+
id: 2,
173+
attributes_text: '{"after":"rollback"}',
174+
attributes_json: '{"after":"rollback"}',
175+
},
176+
]);
177+
} finally {
178+
await clickhouseClient.command({ query: `DROP TABLE IF EXISTS ${table}` });
179+
}
65180
}
66181
);
67182
});

0 commit comments

Comments
 (0)