Skip to content

Commit c1dfce7

Browse files
carderneTrigger.dev RepoOps
authored andcommitted
feat(webapp): split logs search ClickHouse URL into reader and writer
Mono-RevId: 65c2b9b96bc9a32a351ab93a70eb65773b6ede6e
1 parent 175f143 commit c1dfce7

3 files changed

Lines changed: 61 additions & 10 deletions

File tree

apps/webapp/app/env.server.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2226,8 +2226,8 @@ const EnvironmentSchema = z
22262226
.nonnegative()
22272227
.optional(),
22282228

2229-
// Scheduled logs-search projection. Disabled by default. LOGS_CLICKHOUSE_URL, or the
2230-
// CLICKHOUSE_URL fallback, must reach both source and destination tables and allow writes.
2229+
// Scheduled logs-search projection. Disabled by default. LOGS_SEARCH_WRITER_CLICKHOUSE_URL,
2230+
// or the CLICKHOUSE_URL fallback, must reach both source and destination tables and allow writes.
22312231
LOGS_SEARCH_PROJECTOR_ENABLED: BoolEnv.default(false),
22322232
LOGS_SEARCH_PROJECTOR_PREVIEW_ENABLED: BoolEnv.default(false),
22332233
LOGS_SEARCH_PROJECTOR_MAX_WINDOWS_PER_TICK: z.coerce.number().int().min(1).max(20).default(5),
@@ -2249,6 +2249,11 @@ const EnvironmentSchema = z
22492249
LOGS_SEARCH_DUAL_WRITE_ORGANIZATION_IDS: z.string().default(""),
22502250
LOGS_SEARCH_DUAL_WRITE_MAX_CONCURRENCY: z.coerce.number().int().min(1).max(10).default(2),
22512251
LOGS_SEARCH_DUAL_WRITE_MAX_PENDING: z.coerce.number().int().min(1).max(100).default(4),
2252+
// Logs-search write side (dual writer, projector). Must share storage with
2253+
// EVENTS_CLICKHOUSE_URL and allow writes. Unset: the dual writer uses the events client.
2254+
LOGS_SEARCH_WRITER_CLICKHOUSE_URL: z.string().optional(),
2255+
// Logs-search read side (logs page). Falls back to CLICKHOUSE_READER_URL, then CLICKHOUSE_URL.
2256+
LOGS_SEARCH_READER_CLICKHOUSE_URL: z.string().optional(),
22522257

22532258
// Logs list pagination tuning.
22542259
LOGS_LIST_DEFAULT_PAGE_SIZE: z.coerce.number().int().positive().default(50),
@@ -2260,9 +2265,6 @@ const EnvironmentSchema = z
22602265
// AI features (Prompts, Models, AI Metrics sidebar section)
22612266
AI_FEATURES_ENABLED: z.string().default("0"),
22622267

2263-
// Logs page ClickHouse URL (for logs queries)
2264-
LOGS_CLICKHOUSE_URL: z.string().optional(),
2265-
22662268
// Query page ClickHouse limits (for TSQL queries)
22672269
QUERY_CLICKHOUSE_URL: z
22682270
.string()

apps/webapp/app/services/clickhouse/clickhouseFactory.server.ts

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const defaultLogsClickhouseClient = singleton(
3838
);
3939

4040
function initializeLogsSearchProjectorClickhouseClient() {
41-
const url = new URL(env.LOGS_CLICKHOUSE_URL ?? env.CLICKHOUSE_URL);
41+
const url = new URL(env.LOGS_SEARCH_WRITER_CLICKHOUSE_URL ?? env.CLICKHOUSE_URL);
4242
url.searchParams.delete("secure");
4343

4444
return new ClickHouse({
@@ -80,7 +80,9 @@ function getLogsListClickhouseSettings() {
8080
}
8181

8282
function initializeLogsClickhouseClient() {
83-
const url = new URL(env.LOGS_CLICKHOUSE_URL ?? env.CLICKHOUSE_READER_URL ?? env.CLICKHOUSE_URL);
83+
const url = new URL(
84+
env.LOGS_SEARCH_READER_CLICKHOUSE_URL ?? env.CLICKHOUSE_READER_URL ?? env.CLICKHOUSE_URL
85+
);
8486
url.searchParams.delete("secure");
8587

8688
return new ClickHouse({
@@ -466,6 +468,39 @@ function initializeEventsClickhouseClient(): ClickHouse {
466468
});
467469
}
468470

471+
/**
472+
* Logs-search dual-write destination (`LOGS_SEARCH_WRITER_CLICKHOUSE_URL`). Shares storage with the
473+
* events warehouse; a separate service keeps the extra insert stream off the events writer.
474+
* Undefined when unset, in which case the dual writer uses the events client.
475+
*/
476+
const defaultLogsSearchClickhouseClient = singleton(
477+
"logsSearchClickhouseClient",
478+
initializeLogsSearchClickhouseClient
479+
);
480+
481+
function initializeLogsSearchClickhouseClient(): ClickHouse | undefined {
482+
if (!env.LOGS_SEARCH_WRITER_CLICKHOUSE_URL) {
483+
return undefined;
484+
}
485+
486+
const url = new URL(env.LOGS_SEARCH_WRITER_CLICKHOUSE_URL);
487+
url.searchParams.delete("secure");
488+
489+
return new ClickHouse({
490+
name: "logs-search-writer",
491+
url: url.toString(),
492+
keepAlive: {
493+
enabled: env.EVENTS_CLICKHOUSE_KEEP_ALIVE_ENABLED === "1",
494+
idleSocketTtl: env.EVENTS_CLICKHOUSE_KEEP_ALIVE_IDLE_SOCKET_TTL_MS,
495+
},
496+
logLevel: env.EVENTS_CLICKHOUSE_LOG_LEVEL,
497+
compression: {
498+
request: env.EVENTS_CLICKHOUSE_COMPRESSION_REQUEST === "1",
499+
},
500+
maxOpenConnections: env.LOGS_SEARCH_DUAL_WRITE_MAX_CONCURRENCY,
501+
});
502+
}
503+
469504
// ---------------------------------------------------------------------------
470505
// Helpers
471506
// ---------------------------------------------------------------------------
@@ -732,7 +767,11 @@ export class ClickhouseFactory {
732767
let defaultRepo = this._eventRepositoryCache.get(defaultKey);
733768
if (!defaultRepo) {
734769
const eventsClickhouse = getEventsClickhouseClient();
735-
defaultRepo = buildEventRepository(store, eventsClickhouse);
770+
defaultRepo = buildEventRepository(
771+
store,
772+
eventsClickhouse,
773+
defaultLogsSearchClickhouseClient
774+
);
736775
this._eventRepositoryCache.set(defaultKey, defaultRepo);
737776
}
738777
return { key: defaultKey, repository: defaultRepo };
@@ -786,11 +825,16 @@ const logsSearchDualWriteOrganizationIds = new Set(
786825
.filter(Boolean)
787826
);
788827

789-
function buildEventRepository(store: string, clickhouse: ClickHouse): ClickhouseEventRepository {
828+
function buildEventRepository(
829+
store: string,
830+
clickhouse: ClickHouse,
831+
logsSearchClickhouse?: ClickHouse
832+
): ClickhouseEventRepository {
790833
switch (store) {
791834
case "clickhouse": {
792835
return new ClickhouseEventRepository({
793836
clickhouse,
837+
logsSearchClickhouse,
794838
batchSize: env.EVENTS_CLICKHOUSE_BATCH_SIZE,
795839
flushInterval: env.EVENTS_CLICKHOUSE_FLUSH_INTERVAL_MS,
796840
maximumTraceSummaryViewCount: clampToEmergencySpanCap(
@@ -822,6 +866,7 @@ function buildEventRepository(store: string, clickhouse: ClickHouse): Clickhouse
822866
case "clickhouse_v2": {
823867
return new ClickhouseEventRepository({
824868
clickhouse: clickhouse,
869+
logsSearchClickhouse,
825870
batchSize: env.EVENTS_CLICKHOUSE_BATCH_SIZE,
826871
flushInterval: env.EVENTS_CLICKHOUSE_FLUSH_INTERVAL_MS,
827872
maximumTraceSummaryViewCount: clampToEmergencySpanCap(

apps/webapp/app/v3/eventRepository/clickhouseEventRepository.server.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ export function logsSearchRolloutSelectedRowCount(
100100

101101
export type ClickhouseEventRepositoryConfig = {
102102
clickhouse: ClickHouse;
103+
/** Destination for logs-search dual writes; defaults to `clickhouse`. */
104+
logsSearchClickhouse?: ClickHouse;
103105
batchSize?: number;
104106
flushInterval?: number;
105107
insertStrategy?: "insert" | "insert_async";
@@ -145,6 +147,7 @@ export type ClickhouseEventRepositoryConfig = {
145147
*/
146148
export class ClickhouseEventRepository implements IEventRepository {
147149
private _clickhouse: ClickHouse;
150+
private _logsSearchClickhouse: ClickHouse;
148151
private _config: ClickhouseEventRepositoryConfig;
149152
private readonly _flushScheduler: DynamicFlushScheduler<TaskEventV1Input | TaskEventV2Input>;
150153
private readonly _llmMetricsFlushScheduler: DynamicFlushScheduler<LlmMetricsV1Input>;
@@ -194,6 +197,7 @@ export class ClickhouseEventRepository implements IEventRepository {
194197

195198
constructor(config: ClickhouseEventRepositoryConfig) {
196199
this._clickhouse = config.clickhouse;
200+
this._logsSearchClickhouse = config.logsSearchClickhouse ?? config.clickhouse;
197201
this._config = config;
198202
this._tracer = config.tracer ?? trace.getTracer("clickhouseEventRepo", "0.0.1");
199203
this._version = config.version ?? "v1";
@@ -574,7 +578,7 @@ export class ClickhouseEventRepository implements IEventRepository {
574578
let lastError: { clickhouseErrorType?: string } | undefined;
575579

576580
for (let attempt = 1; attempt <= 2; attempt++) {
577-
const [error] = await this._clickhouse.taskEventsSearch.insert(rows, {
581+
const [error] = await this._logsSearchClickhouse.taskEventsSearch.insert(rows, {
578582
params: {
579583
clickhouse_settings: {
580584
async_insert: 0,

0 commit comments

Comments
 (0)