Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions api/src/services/cdp/instrumentation/storage/duckdb-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ export interface DuckDBStorageOptions {
writeBufferFlushInterval?: number;
}

/**
* Maximum number of rows inserted per INSERT statement. A single INSERT that
* spans the entire write buffer builds a statement with ~7 bound parameters per
* row; spreading hundreds of thousands of params into `db.run(sql, ...params)`
* overflows the V8 call stack ("Maximum call stack size exceeded"). Capping the
* rows per statement keeps each flush bounded regardless of buffer size.
*/
const WRITE_CHUNK_SIZE = 1000;

export class DuckDBStorage implements LogStorage {
private db: Database | null = null;
private dbPath: string;
Expand Down Expand Up @@ -142,8 +151,11 @@ export class DuckDBStorage implements LogStorage {
try {
await this.writeBatchInternal(toFlush);
} catch (err) {
// Put events back on failure (at the front)
this.writeBuffer.unshift(...toFlush);
// Put events back on failure (at the front). Use concat rather than
// `unshift(...toFlush)`: spreading a large array as call arguments throws
// "Maximum call stack size exceeded" once the buffer is big enough, which
// would mask the original failure and lose the buffered events.
this.writeBuffer = toFlush.concat(this.writeBuffer);
throw err;
} finally {
this.isFlushing = false;
Expand Down Expand Up @@ -219,6 +231,19 @@ export class DuckDBStorage implements LogStorage {
): Promise<void> {
if (!this.db || events.length === 0) return;

// Insert in fixed-size chunks so a large buffer never produces a single
// statement with hundreds of thousands of bound parameters (see
// WRITE_CHUNK_SIZE).
for (let i = 0; i < events.length; i += WRITE_CHUNK_SIZE) {
await this.writeChunk(events.slice(i, i + WRITE_CHUNK_SIZE));
}
}

private async writeChunk(
events: Array<{ event: BrowserEventUnion; context: Record<string, any> }>,
): Promise<void> {
if (!this.db || events.length === 0) return;

const values: string[] = [];
const params: any[] = [];

Expand Down
Loading