diff --git a/packages/compiler/src/writers.ts b/packages/compiler/src/writers.ts index 469049c..a2a60f5 100644 --- a/packages/compiler/src/writers.ts +++ b/packages/compiler/src/writers.ts @@ -32,7 +32,14 @@ const DUCKDB_TEMP_DIRECTORY = join(tmpdir(), "openislands-duckdb"); * /app owned by root in the Docker image: "IO Error: Failed to create directory .tmp"). */ export function createInMemoryDuckDB(): Promise> { - return DuckDBInstance.create(":memory:", { temp_directory: DUCKDB_TEMP_DIRECTORY }); + return DuckDBInstance.create(":memory:", { + temp_directory: DUCKDB_TEMP_DIRECTORY, + // ponytail: DuckDB caps memory_limit to the cgroup (~80%) but scales threads to HOST cores, + // ignoring the CPU cgroup — 12 threads on an 819 MiB cap OOM at boot ("failed to pin block") + // at ~68 MiB/thread. 4 keeps >125 MiB/thread (DuckDB's floor) and clamps to core count on + // smaller boxes. Raise only for a big-data deployment that also has the RAM for it. + threads: "4", + }); } /** Where a dataset's rows physically live: its content source, plus the table name when that source is a SQLite database. */ diff --git a/packages/compiler/test/writers.test.ts b/packages/compiler/test/writers.test.ts index 867ac76..1a2b8b6 100644 --- a/packages/compiler/test/writers.test.ts +++ b/packages/compiler/test/writers.test.ts @@ -3,7 +3,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; import { LocalContentStore } from "@openislands/storage"; -import { resolveWriter } from "../src/writers.js"; +import { createInMemoryDuckDB, resolveWriter } from "../src/writers.js"; function tempDir(): string { const dir = mkdtempSync(join(tmpdir(), "oi-writers-")); @@ -44,3 +44,23 @@ describe("appendLines EOL preservation", () => { expect(content).not.toContain("\r"); }); }); + +describe("createInMemoryDuckDB resource bounds", () => { + // Guards the container OOM fix: an unbounded thread count over a cgroup-capped memory_limit + // fails to pin buffers at boot, and a default temp_directory spills to a non-writable cwd. + it("caps threads and sets a writable temp_directory", async () => { + const instance = await createInMemoryDuckDB(); + const conn = await instance.connect(); + try { + const reader = await conn.runAndReadAll( + "SELECT current_setting('threads') AS threads, current_setting('temp_directory') AS temp_directory", + ); + const [row] = reader.getRowObjects(); + expect(Number(row.threads)).toBe(4); + expect(String(row.temp_directory)).toContain("openislands-duckdb"); + } finally { + conn.closeSync(); + instance.closeSync(); + } + }); +});