Skip to content

Commit 5e7e509

Browse files
authored
feat: Adding duckdb instance shutdown during inactivity (#32)
* Adding instance manager * Adding shutdown DB test * Adding missed changes
1 parent a613e8a commit 5e7e509

File tree

12 files changed

+306
-37
lines changed

12 files changed

+306
-37
lines changed

benchmarking/benchmarking-app/src/app/dbm-context/indexed-dbm-context.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ import log from 'loglevel';
33
import React, { useState } from 'react';
44
import { DBMContext } from '../hooks/dbm-context';
55
import { useClassicEffect } from '../hooks/use-classic-effect';
6+
import { InstanceManager } from './instance-manager';
67
import { useAsyncDuckDB } from './use-async-duckdb';
78

89
export const IndexedDBMProvider = ({ children }: { children: JSX.Element }) => {
910
const fileManagerRef = React.useRef<IndexedDBFileManager | null>(null);
1011
const [dbm, setdbm] = useState<DBM | null>(null);
12+
const instanceManagerRef = React.useRef<InstanceManager>(
13+
new InstanceManager()
14+
);
1115

1216
const dbState = useAsyncDuckDB();
1317

@@ -16,7 +20,7 @@ export const IndexedDBMProvider = ({ children }: { children: JSX.Element }) => {
1620
return;
1721
}
1822
fileManagerRef.current = new IndexedDBFileManager({
19-
db: dbState,
23+
instanceManager: instanceManagerRef.current,
2024
fetchTableFileBuffers: async (table) => {
2125
return [];
2226
},
@@ -25,12 +29,15 @@ export const IndexedDBMProvider = ({ children }: { children: JSX.Element }) => {
2529
fileManagerRef.current.initializeDB();
2630

2731
const dbm = new DBM({
28-
db: dbState,
32+
instanceManager: instanceManagerRef.current,
2933
fileManager: fileManagerRef.current,
3034
onEvent: (event) => {
3135
console.info(event);
3236
},
3337
logger: log,
38+
options: {
39+
shutdownInactiveTime: 1000,
40+
},
3441
});
3542

3643
setdbm(dbm);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { InstanceManagerType } from '@devrev/meerkat-dbm';
2+
import * as duckdb from '@duckdb/duckdb-wasm';
3+
import { AsyncDuckDB, LogEntryVariant } from '@duckdb/duckdb-wasm';
4+
const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles();
5+
6+
export class InstanceManager implements InstanceManagerType {
7+
private db: AsyncDuckDB | null = null;
8+
9+
private async initDB() {
10+
const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES);
11+
12+
const worker_url = URL.createObjectURL(
13+
new Blob([`importScripts("${bundle.mainWorker!}");`], {
14+
type: 'text/javascript',
15+
})
16+
);
17+
18+
// Instantiate the asynchronus version of DuckDB-wasm
19+
const worker = new Worker(worker_url);
20+
const logger = {
21+
log: (msg: LogEntryVariant) => console.log(msg),
22+
};
23+
const db = new duckdb.AsyncDuckDB(logger, worker);
24+
await db.instantiate(bundle.mainModule, bundle.pthreadWorker);
25+
URL.revokeObjectURL(worker_url);
26+
return db;
27+
}
28+
29+
async getDB() {
30+
if (!this.db) {
31+
console.info('Creating new DB');
32+
this.db = await this.initDB();
33+
}
34+
return this.db;
35+
}
36+
37+
async terminateDB() {
38+
console.info('terminateDB');
39+
await this.db?.terminate();
40+
this.db = null;
41+
}
42+
}

benchmarking/benchmarking-app/src/app/dbm-context/memory-dbm-context.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ import log from 'loglevel';
33
import React, { useState } from 'react';
44
import { DBMContext } from '../hooks/dbm-context';
55
import { useClassicEffect } from '../hooks/use-classic-effect';
6+
import { InstanceManager } from './instance-manager';
67
import { useAsyncDuckDB } from './use-async-duckdb';
78

89
export const MemoryDBMProvider = ({ children }: { children: JSX.Element }) => {
910
const fileManagerRef = React.useRef<FileManagerType | null>(null);
1011
const [dbm, setdbm] = useState<DBM | null>(null);
12+
const instanceManagerRef = React.useRef<InstanceManager>(
13+
new InstanceManager()
14+
);
1115

1216
const dbState = useAsyncDuckDB();
1317

@@ -16,14 +20,14 @@ export const MemoryDBMProvider = ({ children }: { children: JSX.Element }) => {
1620
return;
1721
}
1822
fileManagerRef.current = new MemoryDBFileManager({
19-
db: dbState,
23+
instanceManager: instanceManagerRef.current,
2024
fetchTableFileBuffers: async (table) => {
2125
return [];
2226
},
2327
});
2428
log.setLevel('DEBUG');
2529
const dbm = new DBM({
26-
db: dbState,
30+
instanceManager: instanceManagerRef.current,
2731
fileManager: fileManagerRef.current,
2832
logger: log,
2933
onEvent: (event) => {

benchmarking/benchmarking-app/src/app/dbm-context/raw-dbm-context.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ import log from 'loglevel';
33
import React, { useState } from 'react';
44
import { DBMContext } from '../hooks/dbm-context';
55
import { useClassicEffect } from '../hooks/use-classic-effect';
6+
import { InstanceManager } from './instance-manager';
67
import { useAsyncDuckDB } from './use-async-duckdb';
78

89
export const RawDBMProvider = ({ children }: { children: JSX.Element }) => {
910
const fileManagerRef = React.useRef<FileManagerType | null>(null);
1011
const [dbm, setdbm] = useState<DBM | null>(null);
12+
const instanceManagerRef = React.useRef<InstanceManager>(
13+
new InstanceManager()
14+
);
1115

1216
const dbState = useAsyncDuckDB();
1317

@@ -16,13 +20,13 @@ export const RawDBMProvider = ({ children }: { children: JSX.Element }) => {
1620
return;
1721
}
1822
fileManagerRef.current = new MemoryDBFileManager({
19-
db: dbState,
23+
instanceManager: instanceManagerRef.current,
2024
fetchTableFileBuffers: async (table) => {
2125
return [];
2226
},
2327
});
2428
const dbm = new DBM({
25-
db: dbState,
29+
instanceManager: instanceManagerRef.current,
2630
fileManager: fileManagerRef.current,
2731
logger: log,
2832
onEvent: (event) => {

meerkat-dbm/src/dbm/dbm.spec.ts

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
FileManagerType,
66
} from '../file-manager/file-manager-type';
77
import { DBM, DBMConstructorOptions } from './dbm';
8+
import { InstanceManagerType } from './instance-manager';
89

910
export class MockFileManager implements FileManagerType {
1011
private fileBufferStore: Record<string, FileBufferStore> = {};
@@ -64,21 +65,34 @@ const mockDB = {
6465
},
6566
};
6667

68+
export class InstanceManager implements InstanceManagerType {
69+
async getDB() {
70+
return mockDB as AsyncDuckDB;
71+
}
72+
73+
async terminateDB() {
74+
// do nothing
75+
}
76+
}
77+
6778
describe('DBM', () => {
6879
let db: AsyncDuckDB;
6980
let fileManager: FileManagerType;
7081
let dbm: DBM;
82+
let instanceManager;
7183

7284
beforeAll(async () => {
7385
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
7486
//@ts-ignore
7587
db = mockDB;
7688
fileManager = new MockFileManager();
89+
instanceManager = new InstanceManager();
7790
});
7891

7992
beforeEach(() => {
93+
const instanceManager = new InstanceManager();
8094
const options: DBMConstructorOptions = {
81-
db,
95+
instanceManager: instanceManager,
8296
fileManager,
8397
logger: log,
8498
onEvent: (event) => {
@@ -149,4 +163,113 @@ describe('DBM', () => {
149163
expect(result3).toEqual(['SELECT * FROM table3']);
150164
});
151165
});
166+
167+
describe('shutdown the db test', () => {
168+
it('should shutdown the db if there are no queries in the queue', async () => {
169+
const instanceManager = new InstanceManager();
170+
// If instanceManager.terminateDB is a method
171+
jest.spyOn(instanceManager, 'terminateDB');
172+
173+
// If instanceManager.terminateDB is a function
174+
instanceManager.terminateDB = jest.fn();
175+
const options: DBMConstructorOptions = {
176+
instanceManager: instanceManager,
177+
fileManager,
178+
logger: log,
179+
onEvent: (event) => {
180+
console.log(event);
181+
},
182+
options: {
183+
shutdownInactiveTime: 100,
184+
},
185+
};
186+
const dbm = new DBM(options);
187+
188+
/**
189+
* Execute a query
190+
*/
191+
const promise1 = dbm.queryWithTableNames('SELECT * FROM table1', [
192+
'table1',
193+
]);
194+
195+
/**
196+
* Execute another query
197+
*/
198+
const promise2 = dbm.queryWithTableNames('SELECT * FROM table2', [
199+
'table1',
200+
]);
201+
202+
/**
203+
* Wait for the queries to complete
204+
*/
205+
await Promise.all([promise1, promise2]);
206+
207+
await new Promise((resolve) => setTimeout(resolve, 10));
208+
/**
209+
* Expect instanceManager.terminateDB to not be called
210+
*/
211+
expect(instanceManager.terminateDB).not.toBeCalled();
212+
213+
/**
214+
* wait for 200ms
215+
*/
216+
await new Promise((resolve) => setTimeout(resolve, 200));
217+
/**
218+
* Expect instanceManager.terminateDB to be called
219+
*/
220+
expect(instanceManager.terminateDB).toBeCalled();
221+
});
222+
223+
it('should not shutdown the db if option is not set', async () => {
224+
const instanceManager = new InstanceManager();
225+
// If instanceManager.terminateDB is a method
226+
jest.spyOn(instanceManager, 'terminateDB');
227+
228+
// If instanceManager.terminateDB is a function
229+
instanceManager.terminateDB = jest.fn();
230+
const options: DBMConstructorOptions = {
231+
instanceManager: instanceManager,
232+
fileManager,
233+
logger: log,
234+
onEvent: (event) => {
235+
console.log(event);
236+
},
237+
};
238+
const dbm = new DBM(options);
239+
240+
/**
241+
* Execute a query
242+
*/
243+
const promise1 = dbm.queryWithTableNames('SELECT * FROM table1', [
244+
'table1',
245+
]);
246+
247+
/**
248+
* Execute another query
249+
*/
250+
const promise2 = dbm.queryWithTableNames('SELECT * FROM table2', [
251+
'table1',
252+
]);
253+
254+
/**
255+
* Wait for the queries to complete
256+
*/
257+
await Promise.all([promise1, promise2]);
258+
259+
await new Promise((resolve) => setTimeout(resolve, 10));
260+
/**
261+
* Expect instanceManager.terminateDB to not be called
262+
*/
263+
expect(instanceManager.terminateDB).not.toBeCalled();
264+
265+
/**
266+
* wait for 200ms
267+
*/
268+
await new Promise((resolve) => setTimeout(resolve, 200));
269+
/**
270+
* Expect instanceManager.terminateDB to be called
271+
*/
272+
expect(instanceManager.terminateDB).not.toBeCalled();
273+
});
274+
});
152275
});

0 commit comments

Comments
 (0)