-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathconnection-pool.js
More file actions
121 lines (102 loc) · 3.97 KB
/
Copy pathconnection-pool.js
File metadata and controls
121 lines (102 loc) · 3.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/**
* Connection pool example for MotherDuck using DuckDB Neo driver.
*
* Uses generic-pool to manage multiple connections for concurrent queries.
* Connections are recycled via validate() to ensure freshness.
*
* Run: npm run pool
*/
import "dotenv/config";
import { DuckDBInstance } from "@duckdb/node-api";
import { createPool } from "generic-pool";
const token = process.env.MOTHERDUCK_TOKEN;
const database = process.env.MOTHERDUCK_DATABASE || "my_db";
if (!token) {
console.error("Error: MOTHERDUCK_TOKEN environment variable is not set");
console.error("Copy .env.template to .env and add your token");
process.exit(1);
}
/**
* Factory for creating MotherDuck connections.
*
* Resources are wrapped as { connection, createdAt } so the pool's validate()
* can recycle stale connections without touching pool internals.
*/
class MDConnectionFactory {
constructor(opts) {
this.opts = opts;
this.recycleTimeoutMillis = opts.recycleTimeoutMillis || 300000;
}
async create() {
console.log("Creating new connection...");
// Use fromCache() so all pooled connections share the same cached instance,
// avoiding repeated MotherDuck extension reloads and catalog re-fetches.
const instance = await DuckDBInstance.fromCache(`md:${this.opts.database}`, {
motherduck_token: this.opts.token,
});
const connection = await instance.connect();
// Limit threads per connection so pooled connections don't compete for CPU
await connection.run("SET THREADS='1';");
return { connection, createdAt: Date.now() };
}
async destroy(resource) {
console.log("Destroying connection...");
resource.connection.closeSync();
}
async validate(resource) {
// Return false for stale connections — the pool will destroy and replace them
return Date.now() - resource.createdAt < this.recycleTimeoutMillis;
}
}
async function main() {
// Pool configuration
const poolConfig = {
// Connection settings
token: token,
database: database,
// Pool sizing
min: 2, // Minimum connections to keep ready
max: 5, // Maximum concurrent connections
// Eviction settings (idle connection cleanup)
evictionRunIntervalMillis: 30000, // Check every 30s
softIdleTimeoutMillis: 60000, // Soft-close after 1 min idle
idleTimeoutMillis: 120000, // Hard-close after 2 min idle
// Recycling (replace connections periodically for freshness)
recycleTimeoutMillis: 300000, // Recycle after 5 min
// Validate connections before handing them out (triggers validate())
testOnBorrow: true,
};
const factory = new MDConnectionFactory(poolConfig);
const pool = createPool(factory, poolConfig);
console.log("\n--- Running concurrent queries ---\n");
// Simulate concurrent queries
const queries = [
"SELECT 'Query 1' AS name, COUNT(*) AS cnt FROM sample_data.nyc.taxi",
"SELECT 'Query 2' AS name, AVG(trip_distance) AS avg_dist FROM sample_data.nyc.taxi",
"SELECT 'Query 3' AS name, MAX(total_amount) AS max_amount FROM sample_data.nyc.taxi",
"SELECT 'Query 4' AS name, MIN(trip_distance) AS min_dist FROM sample_data.nyc.taxi",
];
// Execute all queries concurrently
const promises = queries.map(async (query, index) => {
const resource = await pool.acquire();
console.log(`[Query ${index + 1}] Acquired connection, executing...`);
try {
const reader = await resource.connection.runAndReadAll(query);
const row = reader.getRowObjects()[0];
const metricKey = reader.columnNames().find((n) => n !== "name");
console.log(`[Query ${index + 1}] Result: ${row.name} = ${row[metricKey]}`);
return row;
} finally {
await pool.release(resource);
console.log(`[Query ${index + 1}] Released connection`);
}
});
const results = await Promise.all(promises);
console.log("\n--- All queries completed ---");
console.table(results);
// Cleanup
await pool.drain();
await pool.clear();
console.log("\nPool closed. Done!");
}
main().catch(console.error);