@lightdash/warehouses 0.2603.2 → 0.2604.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,6 +20,7 @@ export type DuckdbWarehouseClientArgs = {
20
20
  databasePath?: string;
21
21
  s3Config?: DuckdbS3SessionConfig;
22
22
  resourceLimits?: DuckdbResourceLimits;
23
+ bufferPoolSize?: string;
23
24
  logger?: DuckdbLogger;
24
25
  };
25
26
  export declare const mapFieldTypeFromTypeId: (typeId: number) => DimensionType;
@@ -29,13 +30,18 @@ export declare class DuckdbSqlBuilder extends WarehouseBaseSqlBuilder {
29
30
  getMetricSql(sql: string, metric: Metric): string;
30
31
  concatString(...args: string[]): string;
31
32
  }
33
+ /** Reset shared state without closing — for use in tests with mocked instances. */
34
+ export declare function resetSharedDuckdbStateForTesting(): void;
32
35
  export declare class DuckdbWarehouseClient extends WarehouseBaseClient<CreatePostgresCredentials> {
33
36
  private readonly databasePath;
34
37
  private readonly s3Config?;
35
38
  private readonly resourceLimits?;
39
+ private readonly bufferPoolSize?;
36
40
  private readonly logger?;
37
41
  constructor(args?: DuckdbWarehouseClientArgs);
42
+ close(): Promise<void>;
38
43
  private getSQLWithMetadata;
44
+ private connectWithRetry;
39
45
  private withSession;
40
46
  private bootstrapSession;
41
47
  private getBindValues;
@@ -1 +1 @@
1
- {"version":3,"file":"DuckdbWarehouseClient.d.ts","sourceRoot":"","sources":["../../src/warehouseClients/DuckdbWarehouseClient.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DuckdbWarehouseClient.d.ts","sourceRoot":"","sources":["../../src/warehouseClients/DuckdbWarehouseClient.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.DuckdbWarehouseClient = exports.DuckdbSqlBuilder = exports.mapFieldTypeFromTypeId = void 0;
7
+ exports.resetSharedDuckdbStateForTesting = resetSharedDuckdbStateForTesting;
7
8
  const node_api_1 = require("@duckdb/node-api");
8
9
  const common_1 = require("@lightdash/common");
9
10
  const promises_1 = __importDefault(require("fs/promises"));
@@ -75,24 +76,78 @@ class DuckdbSqlBuilder extends WarehouseBaseSqlBuilder_1.default {
75
76
  }
76
77
  }
77
78
  exports.DuckdbSqlBuilder = DuckdbSqlBuilder;
79
+ /**
80
+ * Shared DuckDB instance — one per worker process.
81
+ * All DuckdbWarehouseClient instances share the same underlying DuckDB instance
82
+ * to maximize cache hits (parquet metadata, HTTP metadata, buffer pool).
83
+ */
84
+ let sharedInstance = null;
85
+ let httpfsInstalled = false;
86
+ let cachesConfigured = false;
87
+ async function getOrCreateSharedInstance(databasePath, logger) {
88
+ if (!sharedInstance) {
89
+ const t0 = performance.now();
90
+ sharedInstance = (await node_api_1.DuckDBInstance.create(databasePath));
91
+ const createMs = performance.now() - t0;
92
+ httpfsInstalled = false;
93
+ cachesConfigured = false;
94
+ logger?.info(`DuckDB shared instance created: path=${databasePath} createMs=${Math.round(createMs)}ms`);
95
+ }
96
+ return sharedInstance;
97
+ }
98
+ function clearSharedInstance(logger) {
99
+ if (sharedInstance) {
100
+ try {
101
+ sharedInstance.closeSync?.();
102
+ }
103
+ catch {
104
+ // best-effort cleanup
105
+ }
106
+ sharedInstance = null;
107
+ httpfsInstalled = false;
108
+ cachesConfigured = false;
109
+ logger?.info('DuckDB shared instance cleared');
110
+ }
111
+ }
112
+ /** Reset shared state without closing — for use in tests with mocked instances. */
113
+ function resetSharedDuckdbStateForTesting() {
114
+ sharedInstance = null;
115
+ httpfsInstalled = false;
116
+ cachesConfigured = false;
117
+ }
78
118
  class DuckdbWarehouseClient extends WarehouseBaseClient_1.default {
79
119
  constructor(args = {}) {
80
120
  super(DUCKDB_INTERNAL_CREDENTIALS, new DuckdbSqlBuilder());
81
121
  this.databasePath = args.databasePath ?? ':memory:';
82
122
  this.s3Config = args.s3Config;
83
123
  this.resourceLimits = args.resourceLimits;
124
+ this.bufferPoolSize = args.bufferPoolSize;
84
125
  this.logger = args.logger;
85
126
  }
127
+ async close() {
128
+ clearSharedInstance(this.logger);
129
+ }
86
130
  getSQLWithMetadata(sql, tags) {
87
131
  if (!tags) {
88
132
  return sql;
89
133
  }
90
134
  return `${sql}\n-- ${JSON.stringify(tags)}`;
91
135
  }
136
+ async connectWithRetry() {
137
+ const instance = await getOrCreateSharedInstance(this.databasePath, this.logger);
138
+ try {
139
+ return await instance.connect();
140
+ }
141
+ catch (firstError) {
142
+ this.logger?.info(`DuckDB connect failed, retrying with fresh instance: ${firstError}`);
143
+ clearSharedInstance(this.logger);
144
+ const freshInstance = await getOrCreateSharedInstance(this.databasePath, this.logger);
145
+ return freshInstance.connect();
146
+ }
147
+ }
92
148
  async withSession(callback) {
93
149
  const sessionStart = performance.now();
94
- const instance = (await node_api_1.DuckDBInstance.create(this.databasePath));
95
- const connection = await instance.connect();
150
+ const connection = await this.connectWithRetry();
96
151
  const connectMs = performance.now() - sessionStart;
97
152
  // Only create a temp dir when resource limits are set (spill to disk).
98
153
  const tempDir = this.resourceLimits
@@ -112,19 +167,36 @@ class DuckdbWarehouseClient extends WarehouseBaseClient_1.default {
112
167
  finally {
113
168
  connection.closeSync?.();
114
169
  connection.disconnectSync?.();
115
- instance.closeSync?.();
170
+ // Note: we do NOT close the instance — it's shared across queries
116
171
  if (tempDir) {
117
172
  await promises_1.default.rm(tempDir, { recursive: true, force: true }).catch(() => { });
118
173
  }
119
174
  }
120
175
  }
121
176
  async bootstrapSession(db, tempDir) {
122
- const t0 = performance.now();
123
- await db.run('INSTALL httpfs;');
124
- const installMs = performance.now() - t0;
177
+ let installMs = 0;
178
+ if (!httpfsInstalled) {
179
+ const t0 = performance.now();
180
+ await db.run('INSTALL httpfs;');
181
+ installMs = performance.now() - t0;
182
+ httpfsInstalled = true;
183
+ this.logger?.info(`DuckDB httpfs installed (first use): ${Math.round(installMs)}ms`);
184
+ }
125
185
  const t1 = performance.now();
126
186
  await db.run('LOAD httpfs;');
127
187
  const loadMs = performance.now() - t1;
188
+ // Enable built-in caches — these are GLOBAL settings (instance-level, not
189
+ // connection-level), so they only need to be set once per shared instance.
190
+ if (!cachesConfigured) {
191
+ await db.run('SET enable_http_metadata_cache = true;');
192
+ await db.run('SET enable_external_file_cache = true;');
193
+ await db.run('SET parquet_metadata_cache = true;');
194
+ cachesConfigured = true;
195
+ if (this.bufferPoolSize) {
196
+ await db.run(`SET buffer_pool_size = '${this.bufferPoolSize}';`);
197
+ }
198
+ this.logger?.info(`DuckDB caches enabled: http_metadata=true external_file=true parquet_metadata=true buffer_pool_size=${this.bufferPoolSize ?? 'default'}`);
199
+ }
128
200
  if (this.resourceLimits && tempDir) {
129
201
  await db.run(`SET memory_limit = '${this.resourceLimits.memoryLimit}';`);
130
202
  await db.run(`SET temp_directory = '${tempDir}';`);
@@ -138,6 +138,7 @@ describe('mapFieldTypeFromTypeId', () => {
138
138
  describe('DuckdbWarehouseClient', () => {
139
139
  beforeEach(() => {
140
140
  jest.clearAllMocks();
141
+ (0, DuckdbWarehouseClient_1.resetSharedDuckdbStateForTesting)();
141
142
  });
142
143
  it('should return query rows and mapped fields', async () => {
143
144
  const rows = [
@@ -213,7 +214,11 @@ describe('DuckdbWarehouseClient', () => {
213
214
  });
214
215
  await client.runQuery('SELECT 1 AS val', undefined, 'UTC');
215
216
  const runCalls = runMock.mock.calls.map((call) => call[0]);
217
+ expect(runCalls).toContain('INSTALL httpfs;');
216
218
  expect(runCalls).toContain('LOAD httpfs;');
219
+ expect(runCalls).toContain('SET enable_http_metadata_cache = true;');
220
+ expect(runCalls).toContain('SET enable_external_file_cache = true;');
221
+ expect(runCalls).toContain('SET parquet_metadata_cache = true;');
217
222
  expect(runCalls).toContain("SET s3_endpoint = 'localhost:9000';");
218
223
  expect(runCalls).toContain("SET s3_region = 'us-east-1';");
219
224
  expect(runCalls).toContain("SET TimeZone = 'UTC';");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightdash/warehouses",
3
- "version": "0.2603.2",
3
+ "version": "0.2604.1",
4
4
  "description": "Warehouse connectors for Lightdash",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -26,7 +26,7 @@
26
26
  "snowflake-sdk": "~2.3.4",
27
27
  "ssh2": "^1.14.0",
28
28
  "trino-client": "0.2.9",
29
- "@lightdash/common": "0.2603.2"
29
+ "@lightdash/common": "0.2604.1"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/node-fetch": "^2.6.13",