@digitalwalletcorp/redis-pooling 1.1.1 → 1.3.0
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.
- package/README.md +4 -0
- package/lib/redis-pooling.d.ts +17 -9
- package/lib/redis-pooling.js +106 -96
- package/lib/redis-pooling.js.map +1 -1
- package/package.json +13 -9
- package/src/redis-pooling.ts +110 -106
package/README.md
CHANGED
|
@@ -124,6 +124,7 @@ Creates a Redis connection pool.
|
|
|
124
124
|
| `max` | number | 10 | Maximum number of clients in the pool. |
|
|
125
125
|
| `min` | number | 0 | Minimum number of clients in the pool. |
|
|
126
126
|
| `connectTimeout` | number | 5000 | Connection timeout in milliseconds. |
|
|
127
|
+
| `acquireTimeout` | number | 10000 | Timeout in milliseconds to acquire a client from the pool. |
|
|
127
128
|
| `testOnBorrow` | boolean | true | Enable connection validate on borrow. |
|
|
128
129
|
| `enableTls` | boolean | false | Enable TLS for Redis connection. |
|
|
129
130
|
|
|
@@ -149,6 +150,9 @@ Extends the standard `ioredis` `Redis` client with additional helpers:
|
|
|
149
150
|
This library provides a Redis connection pool capable of managing Redis clients
|
|
150
151
|
connected to different database indexes (`SELECT db`).
|
|
151
152
|
|
|
153
|
+
All database indexes share a single pool, so `max` is the upper limit of connections to the Redis server.
|
|
154
|
+
The database index is selected every time a client is acquired.
|
|
155
|
+
|
|
152
156
|
**Calling `acquire(dbIndex)`:**
|
|
153
157
|
|
|
154
158
|
> When `dbIndex` is specified, the acquired client will be connected to the specified database index.
|
package/lib/redis-pooling.d.ts
CHANGED
|
@@ -3,30 +3,32 @@ export interface RedisConfig {
|
|
|
3
3
|
url: string;
|
|
4
4
|
dbIndex?: number;
|
|
5
5
|
connectTimeout?: number;
|
|
6
|
+
acquireTimeout?: number;
|
|
6
7
|
max?: number;
|
|
7
8
|
min?: number;
|
|
8
9
|
testOnBorrow?: boolean;
|
|
9
10
|
enableTls?: boolean;
|
|
10
11
|
}
|
|
12
|
+
export interface RedisPoolOptions {
|
|
13
|
+
debug?: boolean;
|
|
14
|
+
}
|
|
11
15
|
export interface RedisClient extends Redis {
|
|
12
|
-
getKeys(pattern: string): Promise<string[]>;
|
|
13
|
-
deleteKeys(pattern: string): Promise<PromiseSettledResult<number>[]>;
|
|
14
|
-
_originalDbIndex?: number;
|
|
16
|
+
getKeys(pattern: string, count?: number): Promise<string[]>;
|
|
17
|
+
deleteKeys(pattern: string, count?: number): Promise<PromiseSettledResult<number>[]>;
|
|
15
18
|
}
|
|
16
19
|
export declare class RedisPool {
|
|
17
20
|
private readonly url;
|
|
18
21
|
private readonly db;
|
|
19
22
|
private readonly connectTimeout;
|
|
23
|
+
private readonly acquireTimeout;
|
|
20
24
|
private readonly max;
|
|
21
25
|
private readonly min;
|
|
22
26
|
private readonly testOnBorrow;
|
|
23
27
|
private readonly tls?;
|
|
24
|
-
private
|
|
28
|
+
private pool?;
|
|
25
29
|
private initialized;
|
|
26
30
|
private readonly debug;
|
|
27
|
-
constructor(config: RedisConfig, options?:
|
|
28
|
-
debug?: boolean;
|
|
29
|
-
});
|
|
31
|
+
constructor(config: RedisConfig, options?: RedisPoolOptions);
|
|
30
32
|
acquire(dbIndex?: number): Promise<RedisClient>;
|
|
31
33
|
release(client?: RedisClient): Promise<void>;
|
|
32
34
|
destroy(timeoutMs?: number): Promise<void>;
|
|
@@ -36,8 +38,14 @@ export declare class RedisPool {
|
|
|
36
38
|
* @param {number} dbIndex
|
|
37
39
|
*/
|
|
38
40
|
private checkConnectivity;
|
|
41
|
+
/**
|
|
42
|
+
* Redisクライアントの接続が完了するまで待つ。接続に失敗した場合は、そのエラーで reject する
|
|
43
|
+
*
|
|
44
|
+
* @param {Redis} client
|
|
45
|
+
*/
|
|
46
|
+
private waitForReady;
|
|
39
47
|
private ping;
|
|
40
|
-
debugLog
|
|
48
|
+
private debugLog;
|
|
41
49
|
private getPool;
|
|
42
|
-
private
|
|
50
|
+
private createPool;
|
|
43
51
|
}
|
package/lib/redis-pooling.js
CHANGED
|
@@ -38,6 +38,7 @@ const ioredis_1 = require("ioredis");
|
|
|
38
38
|
const genericPool = __importStar(require("generic-pool"));
|
|
39
39
|
const logHeader = '[RedisPooling]';
|
|
40
40
|
const DEFAULT_CONNECT_TIMEOUT = 5000;
|
|
41
|
+
const DEFAULT_ACQUIRE_TIMEOUT = 10000;
|
|
41
42
|
const DEFAULT_MAX_POOLING_SIZE = 10;
|
|
42
43
|
const DEFAULT_MIN_POOLING_SIZE = 0;
|
|
43
44
|
const REDIS_PING_TIMEOUT_MS = 3000; // 3秒
|
|
@@ -45,11 +46,12 @@ class RedisPool {
|
|
|
45
46
|
url;
|
|
46
47
|
db;
|
|
47
48
|
connectTimeout;
|
|
49
|
+
acquireTimeout;
|
|
48
50
|
max;
|
|
49
51
|
min;
|
|
50
52
|
testOnBorrow;
|
|
51
53
|
tls;
|
|
52
|
-
|
|
54
|
+
pool;
|
|
53
55
|
initialized = false;
|
|
54
56
|
debug;
|
|
55
57
|
constructor(config, options) {
|
|
@@ -59,6 +61,7 @@ class RedisPool {
|
|
|
59
61
|
this.url = config.url;
|
|
60
62
|
this.db = config.dbIndex ?? 0;
|
|
61
63
|
this.connectTimeout = config.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT;
|
|
64
|
+
this.acquireTimeout = config.acquireTimeout ?? DEFAULT_ACQUIRE_TIMEOUT;
|
|
62
65
|
this.max = config.max ?? DEFAULT_MAX_POOLING_SIZE;
|
|
63
66
|
this.min = config.min ?? DEFAULT_MIN_POOLING_SIZE;
|
|
64
67
|
this.testOnBorrow = config.testOnBorrow ?? true;
|
|
@@ -74,69 +77,56 @@ class RedisPool {
|
|
|
74
77
|
this.initialized = true;
|
|
75
78
|
}
|
|
76
79
|
const index = dbIndex ?? this.db;
|
|
77
|
-
const pool = this.getPool(
|
|
80
|
+
const pool = this.getPool();
|
|
81
|
+
const client = await pool.acquire();
|
|
78
82
|
try {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return client;
|
|
83
|
+
// プールの接続は、前の利用者が選択したDBを保持したまま返却される。そのため貸し出すたびに対象のDBを選択する
|
|
84
|
+
await client.select(index);
|
|
82
85
|
}
|
|
83
86
|
catch (error) {
|
|
87
|
+
await pool.destroy(client);
|
|
84
88
|
throw error;
|
|
85
89
|
}
|
|
90
|
+
this.debugLog(logHeader, `Redis client ${index} has been acquired.`);
|
|
91
|
+
return client;
|
|
86
92
|
}
|
|
87
93
|
async release(client) {
|
|
88
|
-
if (client) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
needDestroy = true;
|
|
100
|
-
break;
|
|
101
|
-
case client.status === 'ready':
|
|
102
|
-
try {
|
|
103
|
-
// 元のDBインデックスに戻す
|
|
104
|
-
await client.select(dbIndex);
|
|
105
|
-
}
|
|
106
|
-
catch (error) {
|
|
107
|
-
// selectに失敗する→Redisクライアントが不正な状態にあると判断できるのでプールから破棄
|
|
108
|
-
needDestroy = true;
|
|
109
|
-
}
|
|
110
|
-
break;
|
|
111
|
-
default:
|
|
112
|
-
}
|
|
113
|
-
if (needDestroy) {
|
|
114
|
-
// Redisクライアントの破棄
|
|
115
|
-
await pool.destroy(client);
|
|
116
|
-
this.debugLog(logHeader, `Redis client ${dbIndex} destroyed due to invalid status.`);
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
// Redisクライアントの返却
|
|
120
|
-
await pool.release(client);
|
|
121
|
-
this.debugLog(logHeader, `Redis client ${dbIndex} released.`);
|
|
122
|
-
}
|
|
94
|
+
if (!client || !this.pool) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (client.status === 'end' || client.status === 'close') {
|
|
98
|
+
// 再利用できない状態のRedisクライアントはプールから破棄する
|
|
99
|
+
await this.pool.destroy(client);
|
|
100
|
+
this.debugLog(logHeader, 'Redis client destroyed due to invalid status.');
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
await this.pool.release(client);
|
|
104
|
+
this.debugLog(logHeader, 'Redis client released.');
|
|
123
105
|
}
|
|
124
106
|
}
|
|
125
107
|
async destroy(timeoutMs = 5000) {
|
|
126
|
-
|
|
127
|
-
|
|
108
|
+
const pool = this.pool;
|
|
109
|
+
if (!pool) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
this.debugLog(logHeader, 'Destroying Redis pool...');
|
|
113
|
+
let timer;
|
|
114
|
+
try {
|
|
128
115
|
await Promise.race([
|
|
129
116
|
(async () => {
|
|
130
117
|
await pool.drain();
|
|
131
118
|
await pool.clear();
|
|
132
119
|
})(),
|
|
133
120
|
new Promise((_, reject) => {
|
|
134
|
-
setTimeout(() => reject(new Error(`${logHeader} Timeout while draining Redis pool
|
|
121
|
+
timer = setTimeout(() => reject(new Error(`${logHeader} Timeout while draining Redis pool`)), timeoutMs);
|
|
135
122
|
})
|
|
136
123
|
]);
|
|
137
|
-
this.debugLog(logHeader, `Redis pool for DB index ${dbIndex} destroyed.`);
|
|
138
|
-
this.poolMap.delete(dbIndex);
|
|
139
124
|
}
|
|
125
|
+
finally {
|
|
126
|
+
clearTimeout(timer);
|
|
127
|
+
}
|
|
128
|
+
this.debugLog(logHeader, 'Redis pool destroyed.');
|
|
129
|
+
this.pool = undefined;
|
|
140
130
|
}
|
|
141
131
|
/**
|
|
142
132
|
* 初めてacquireが呼ばれた時に、指定された接続情報で接続できるかチェックする
|
|
@@ -151,18 +141,37 @@ class RedisPool {
|
|
|
151
141
|
connectTimeout: this.connectTimeout,
|
|
152
142
|
tls: this.tls,
|
|
153
143
|
});
|
|
144
|
+
try {
|
|
145
|
+
await this.waitForReady(client);
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
client.quit().catch(() => client.disconnect());
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Redisクライアントの接続が完了するまで待つ。接続に失敗した場合は、そのエラーで reject する
|
|
153
|
+
*
|
|
154
|
+
* @param {Redis} client
|
|
155
|
+
*/
|
|
156
|
+
async waitForReady(client) {
|
|
157
|
+
if (client.status === 'ready') {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
154
160
|
return new Promise((resolve, reject) => {
|
|
155
|
-
const
|
|
156
|
-
client.quit().catch(() => client.disconnect());
|
|
157
|
-
};
|
|
158
|
-
client.once('ready', () => {
|
|
161
|
+
const onReady = () => {
|
|
159
162
|
cleanup();
|
|
160
163
|
resolve();
|
|
161
|
-
}
|
|
162
|
-
|
|
164
|
+
};
|
|
165
|
+
const onError = (error) => {
|
|
163
166
|
cleanup();
|
|
164
167
|
reject(error);
|
|
165
|
-
}
|
|
168
|
+
};
|
|
169
|
+
const cleanup = () => {
|
|
170
|
+
client.off('ready', onReady);
|
|
171
|
+
client.off('error', onError);
|
|
172
|
+
};
|
|
173
|
+
client.once('ready', onReady);
|
|
174
|
+
client.once('error', onError);
|
|
166
175
|
});
|
|
167
176
|
}
|
|
168
177
|
async ping(client) {
|
|
@@ -188,19 +197,17 @@ class RedisPool {
|
|
|
188
197
|
console.debug(...args);
|
|
189
198
|
}
|
|
190
199
|
}
|
|
191
|
-
getPool(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
pool = this.createSingleDbPool(dbIndex);
|
|
195
|
-
this.poolMap.set(dbIndex, pool);
|
|
200
|
+
getPool() {
|
|
201
|
+
if (!this.pool) {
|
|
202
|
+
this.pool = this.createPool();
|
|
196
203
|
}
|
|
197
|
-
return pool;
|
|
204
|
+
return this.pool;
|
|
198
205
|
}
|
|
199
|
-
|
|
206
|
+
createPool() {
|
|
200
207
|
const factory = {
|
|
201
208
|
create: async () => {
|
|
202
209
|
const client = new ioredis_1.Redis(this.url, {
|
|
203
|
-
db:
|
|
210
|
+
db: this.db,
|
|
204
211
|
connectTimeout: this.connectTimeout,
|
|
205
212
|
keepAlive: 1,
|
|
206
213
|
enableOfflineQueue: true,
|
|
@@ -214,27 +221,26 @@ class RedisPool {
|
|
|
214
221
|
},
|
|
215
222
|
reconnectOnError: (error) => {
|
|
216
223
|
process.emitWarning(`${logHeader} detected error (on reconnectOnError). ${error.message}`);
|
|
217
|
-
|
|
224
|
+
// フェイルオーバー後にレプリカへ接続したままになっている場合だけ、再接続で解消できる
|
|
225
|
+
return error.message.startsWith('READONLY');
|
|
218
226
|
}
|
|
219
227
|
});
|
|
220
|
-
client._originalDbIndex = dbIndex;
|
|
221
228
|
client.on('error', error => {
|
|
222
229
|
process.emitWarning(`${logHeader} detected error (on error). ${error.message}`);
|
|
223
230
|
});
|
|
224
|
-
// カスタムメソッド内の処理でthis.debugLogなどが参照できなくなるため、poolのインスタンスを変数キャプチャする
|
|
225
|
-
const poolInstance = this;
|
|
226
231
|
// カスタムメソッド START
|
|
227
232
|
/**
|
|
228
233
|
* 指定されたパターンに一致するキーを Redis から全て取得する
|
|
229
234
|
*
|
|
230
235
|
* @param {string} pattern
|
|
236
|
+
* @param {number} [count] 1度にスキャンする件数 デフォルト:1000
|
|
231
237
|
* @returns {Promise<string[]>}
|
|
232
238
|
*/
|
|
233
|
-
client.getKeys = async function (pattern) {
|
|
239
|
+
client.getKeys = async function (pattern, count) {
|
|
234
240
|
const allKeys = [];
|
|
235
241
|
const stream = this.scanStream({
|
|
236
242
|
match: pattern,
|
|
237
|
-
count:
|
|
243
|
+
count: count ?? 1000
|
|
238
244
|
});
|
|
239
245
|
return new Promise((resolve, reject) => {
|
|
240
246
|
stream.on('data', (keys) => {
|
|
@@ -242,8 +248,8 @@ class RedisPool {
|
|
|
242
248
|
allKeys.push(...keys);
|
|
243
249
|
}
|
|
244
250
|
});
|
|
245
|
-
stream.
|
|
246
|
-
stream.
|
|
251
|
+
stream.once('end', () => resolve(allKeys));
|
|
252
|
+
stream.once('error', (err) => {
|
|
247
253
|
reject(err);
|
|
248
254
|
});
|
|
249
255
|
});
|
|
@@ -264,52 +270,55 @@ class RedisPool {
|
|
|
264
270
|
* .reduce((acc, cur) => acc + (cur as PromiseFulfilledResult<number>).value, 0);
|
|
265
271
|
*
|
|
266
272
|
* @param {string} pattern
|
|
273
|
+
* @param {number} [count] 1度にスキャンする件数 デフォルト:1000
|
|
267
274
|
* @returns {Promise<PromiseSettledResult<number>>}
|
|
268
275
|
*/
|
|
269
|
-
client.deleteKeys = async function (pattern) {
|
|
276
|
+
client.deleteKeys = async function (pattern, count) {
|
|
270
277
|
const stream = this.scanStream({
|
|
271
278
|
match: pattern,
|
|
272
|
-
count:
|
|
279
|
+
count: count ?? 1000
|
|
273
280
|
});
|
|
281
|
+
const results = [];
|
|
274
282
|
return new Promise((resolve, reject) => {
|
|
275
|
-
const tasks = [];
|
|
276
283
|
stream.on('data', async (keys) => {
|
|
277
284
|
if (keys.length) {
|
|
278
|
-
|
|
285
|
+
stream.pause();
|
|
286
|
+
try {
|
|
279
287
|
// UNLINK を使用し、現在のインスタンス (this) で実行
|
|
280
288
|
const unlinkResult = await this.unlink(...keys);
|
|
281
|
-
|
|
282
|
-
|
|
289
|
+
results.push({
|
|
290
|
+
status: 'fulfilled',
|
|
291
|
+
value: unlinkResult
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
catch (err) {
|
|
295
|
+
results.push({
|
|
296
|
+
status: 'rejected',
|
|
297
|
+
reason: err
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
finally {
|
|
301
|
+
stream.resume();
|
|
302
|
+
}
|
|
283
303
|
}
|
|
284
304
|
});
|
|
285
|
-
stream.
|
|
286
|
-
const results = await Promise.allSettled(tasks);
|
|
305
|
+
stream.once('end', async () => {
|
|
287
306
|
resolve(results);
|
|
288
307
|
});
|
|
289
|
-
stream.
|
|
308
|
+
stream.once('error', (err) => {
|
|
290
309
|
reject(err);
|
|
291
310
|
});
|
|
292
311
|
});
|
|
293
312
|
};
|
|
294
313
|
// カスタムメソッド END
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
// cleanup();
|
|
304
|
-
// reject(error);
|
|
305
|
-
// };
|
|
306
|
-
// const cleanup = () => {
|
|
307
|
-
// client.off('ready', onReady);
|
|
308
|
-
// client.off('error', onError);
|
|
309
|
-
// };
|
|
310
|
-
// client.once('ready', onReady);
|
|
311
|
-
// client.once('error', onError);
|
|
312
|
-
// });
|
|
314
|
+
try {
|
|
315
|
+
await this.waitForReady(client);
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
// 接続できなかったクライアントはプールに入れない。ioredisの再接続も止める
|
|
319
|
+
client.disconnect();
|
|
320
|
+
throw error;
|
|
321
|
+
}
|
|
313
322
|
return client;
|
|
314
323
|
},
|
|
315
324
|
destroy: async (client) => {
|
|
@@ -329,7 +338,8 @@ class RedisPool {
|
|
|
329
338
|
return genericPool.createPool(factory, {
|
|
330
339
|
max: this.max,
|
|
331
340
|
min: this.min,
|
|
332
|
-
testOnBorrow: this.testOnBorrow
|
|
341
|
+
testOnBorrow: this.testOnBorrow,
|
|
342
|
+
acquireTimeoutMillis: this.acquireTimeout
|
|
333
343
|
});
|
|
334
344
|
}
|
|
335
345
|
}
|
package/lib/redis-pooling.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis-pooling.js","sourceRoot":"","sources":["../src/redis-pooling.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,qCAAgC;AAChC,0DAA4C;
|
|
1
|
+
{"version":3,"file":"redis-pooling.js","sourceRoot":"","sources":["../src/redis-pooling.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@digitalwalletcorp/redis-pooling",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "This is a library for redis connection pooling",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc --project tsconfig.build.json",
|
|
9
|
+
"install:dev": "rm -rf lib dest && find node_modules -mindepth 1 -delete && yarn install",
|
|
9
10
|
"version:patch": "npm version patch",
|
|
10
11
|
"version:minor": "npm version minor",
|
|
11
12
|
"version:major": "npm version major",
|
|
@@ -32,17 +33,20 @@
|
|
|
32
33
|
"lib/**/*",
|
|
33
34
|
"src/**/*"
|
|
34
35
|
],
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"generic-pool": ">=3.9.0",
|
|
38
|
+
"ioredis": ">=5.0.0"
|
|
39
|
+
},
|
|
35
40
|
"devDependencies": {
|
|
36
41
|
"@types/ioredis-mock": "^8.2.6",
|
|
37
|
-
"@types/jest": "^
|
|
42
|
+
"@types/jest": "^30.0.0",
|
|
43
|
+
"generic-pool": "^3.9.0",
|
|
44
|
+
"ioredis": "^5.9.2",
|
|
38
45
|
"ioredis-mock": "^8.13.1",
|
|
39
|
-
"jest": "^
|
|
40
|
-
"ts-jest": "^29.
|
|
46
|
+
"jest": "^30.3.0",
|
|
47
|
+
"ts-jest": "^29.4.9",
|
|
41
48
|
"ts-node": "^10.9.2",
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
"dependencies": {
|
|
45
|
-
"generic-pool": "^3.9.0",
|
|
46
|
-
"ioredis": "^5.9.2"
|
|
49
|
+
"tsconfig-paths": "^4.2.0",
|
|
50
|
+
"typescript": "^6.0.3"
|
|
47
51
|
}
|
|
48
52
|
}
|
package/src/redis-pooling.ts
CHANGED
|
@@ -5,20 +5,25 @@ export interface RedisConfig {
|
|
|
5
5
|
url: string;
|
|
6
6
|
dbIndex?: number;
|
|
7
7
|
connectTimeout?: number;
|
|
8
|
+
acquireTimeout?: number;
|
|
8
9
|
max?: number;
|
|
9
10
|
min?: number;
|
|
10
11
|
testOnBorrow?: boolean;
|
|
11
12
|
enableTls?: boolean;
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
export interface RedisPoolOptions {
|
|
16
|
+
debug?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
14
19
|
export interface RedisClient extends Redis {
|
|
15
|
-
getKeys(pattern: string): Promise<string[]>;
|
|
16
|
-
deleteKeys(pattern: string): Promise<PromiseSettledResult<number>[]>;
|
|
17
|
-
_originalDbIndex?: number; // 内部状態管理用変数
|
|
20
|
+
getKeys(pattern: string, count?: number): Promise<string[]>;
|
|
21
|
+
deleteKeys(pattern: string, count?: number): Promise<PromiseSettledResult<number>[]>;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
const logHeader = '[RedisPooling]';
|
|
21
25
|
const DEFAULT_CONNECT_TIMEOUT = 5000;
|
|
26
|
+
const DEFAULT_ACQUIRE_TIMEOUT = 10000;
|
|
22
27
|
const DEFAULT_MAX_POOLING_SIZE = 10;
|
|
23
28
|
const DEFAULT_MIN_POOLING_SIZE = 0;
|
|
24
29
|
const REDIS_PING_TIMEOUT_MS = 3000; // 3秒
|
|
@@ -28,18 +33,17 @@ export class RedisPool {
|
|
|
28
33
|
private readonly url: string;
|
|
29
34
|
private readonly db: number;
|
|
30
35
|
private readonly connectTimeout: number;
|
|
36
|
+
private readonly acquireTimeout: number;
|
|
31
37
|
private readonly max: number;
|
|
32
38
|
private readonly min: number;
|
|
33
39
|
private readonly testOnBorrow: boolean;
|
|
34
40
|
private readonly tls?: { rejectUnauthorized: false };
|
|
35
41
|
|
|
36
|
-
private
|
|
42
|
+
private pool?: genericPool.Pool<RedisClient>;
|
|
37
43
|
private initialized = false;
|
|
38
44
|
private readonly debug: boolean;
|
|
39
45
|
|
|
40
|
-
constructor(config: RedisConfig, options?: {
|
|
41
|
-
debug?: boolean;
|
|
42
|
-
}) {
|
|
46
|
+
constructor(config: RedisConfig, options?: RedisPoolOptions) {
|
|
43
47
|
if (!config.url) {
|
|
44
48
|
throw new Error(`${logHeader} Redis connection url is required.`);
|
|
45
49
|
}
|
|
@@ -47,6 +51,7 @@ export class RedisPool {
|
|
|
47
51
|
this.url = config.url;
|
|
48
52
|
this.db = config.dbIndex ?? 0;
|
|
49
53
|
this.connectTimeout = config.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT;
|
|
54
|
+
this.acquireTimeout = config.acquireTimeout ?? DEFAULT_ACQUIRE_TIMEOUT;
|
|
50
55
|
this.max = config.max ?? DEFAULT_MAX_POOLING_SIZE;
|
|
51
56
|
this.min = config.min ?? DEFAULT_MIN_POOLING_SIZE;
|
|
52
57
|
this.testOnBorrow = config.testOnBorrow ?? true;
|
|
@@ -65,69 +70,55 @@ export class RedisPool {
|
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
const index = dbIndex ?? this.db;
|
|
68
|
-
const pool = this.getPool(
|
|
73
|
+
const pool = this.getPool();
|
|
74
|
+
const client = await pool.acquire();
|
|
69
75
|
try {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
this.debugLog(logHeader, `Redis client ${index} has been acquired.`);
|
|
73
|
-
return client;
|
|
76
|
+
// プールの接続は、前の利用者が選択したDBを保持したまま返却される。そのため貸し出すたびに対象のDBを選択する
|
|
77
|
+
await client.select(index);
|
|
74
78
|
} catch (error) {
|
|
79
|
+
await pool.destroy(client);
|
|
75
80
|
throw error;
|
|
76
81
|
}
|
|
82
|
+
this.debugLog(logHeader, `Redis client ${index} has been acquired.`);
|
|
83
|
+
return client;
|
|
77
84
|
}
|
|
78
85
|
|
|
79
86
|
public async release(client?: RedisClient): Promise<void> {
|
|
80
|
-
if (client) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
// Redisクライアントの状態が再利用できない場合はプールから破棄
|
|
91
|
-
needDestroy = true;
|
|
92
|
-
break;
|
|
93
|
-
case client.status === 'ready':
|
|
94
|
-
try {
|
|
95
|
-
// 元のDBインデックスに戻す
|
|
96
|
-
await client.select(dbIndex);
|
|
97
|
-
} catch (error) {
|
|
98
|
-
// selectに失敗する→Redisクライアントが不正な状態にあると判断できるのでプールから破棄
|
|
99
|
-
needDestroy = true;
|
|
100
|
-
}
|
|
101
|
-
break;
|
|
102
|
-
default:
|
|
103
|
-
}
|
|
104
|
-
if (needDestroy) {
|
|
105
|
-
// Redisクライアントの破棄
|
|
106
|
-
await pool.destroy(client);
|
|
107
|
-
this.debugLog(logHeader, `Redis client ${dbIndex} destroyed due to invalid status.`);
|
|
108
|
-
} else {
|
|
109
|
-
// Redisクライアントの返却
|
|
110
|
-
await pool.release(client);
|
|
111
|
-
this.debugLog(logHeader, `Redis client ${dbIndex} released.`);
|
|
112
|
-
}
|
|
87
|
+
if (!client || !this.pool) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (client.status === 'end' || client.status === 'close') {
|
|
91
|
+
// 再利用できない状態のRedisクライアントはプールから破棄する
|
|
92
|
+
await this.pool.destroy(client);
|
|
93
|
+
this.debugLog(logHeader, 'Redis client destroyed due to invalid status.');
|
|
94
|
+
} else {
|
|
95
|
+
await this.pool.release(client);
|
|
96
|
+
this.debugLog(logHeader, 'Redis client released.');
|
|
113
97
|
}
|
|
114
98
|
}
|
|
115
99
|
|
|
116
100
|
public async destroy(timeoutMs = 5000): Promise<void> {
|
|
117
|
-
|
|
118
|
-
|
|
101
|
+
const pool = this.pool;
|
|
102
|
+
if (!pool) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
this.debugLog(logHeader, 'Destroying Redis pool...');
|
|
106
|
+
let timer: NodeJS.Timeout | undefined;
|
|
107
|
+
try {
|
|
119
108
|
await Promise.race([
|
|
120
109
|
(async () => {
|
|
121
110
|
await pool.drain();
|
|
122
111
|
await pool.clear();
|
|
123
112
|
})(),
|
|
124
113
|
new Promise<void>((_, reject) => {
|
|
125
|
-
setTimeout(() => reject(new Error(`${logHeader} Timeout while draining Redis pool
|
|
114
|
+
timer = setTimeout(() => reject(new Error(`${logHeader} Timeout while draining Redis pool`)), timeoutMs);
|
|
126
115
|
})
|
|
127
116
|
]);
|
|
128
|
-
|
|
129
|
-
|
|
117
|
+
} finally {
|
|
118
|
+
clearTimeout(timer);
|
|
130
119
|
}
|
|
120
|
+
this.debugLog(logHeader, 'Redis pool destroyed.');
|
|
121
|
+
this.pool = undefined;
|
|
131
122
|
}
|
|
132
123
|
|
|
133
124
|
/**
|
|
@@ -144,20 +135,37 @@ export class RedisPool {
|
|
|
144
135
|
tls: this.tls,
|
|
145
136
|
});
|
|
146
137
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
138
|
+
try {
|
|
139
|
+
await this.waitForReady(client);
|
|
140
|
+
} finally {
|
|
141
|
+
client.quit().catch(() => client.disconnect());
|
|
142
|
+
}
|
|
143
|
+
}
|
|
151
144
|
|
|
152
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Redisクライアントの接続が完了するまで待つ。接続に失敗した場合は、そのエラーで reject する
|
|
147
|
+
*
|
|
148
|
+
* @param {Redis} client
|
|
149
|
+
*/
|
|
150
|
+
private async waitForReady(client: Redis): Promise<void> {
|
|
151
|
+
if (client.status === 'ready') {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
return new Promise<void>((resolve, reject) => {
|
|
155
|
+
const onReady = () => {
|
|
153
156
|
cleanup();
|
|
154
157
|
resolve();
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
client.once('error', (error) => {
|
|
158
|
+
};
|
|
159
|
+
const onError = (error: Error) => {
|
|
158
160
|
cleanup();
|
|
159
161
|
reject(error);
|
|
160
|
-
}
|
|
162
|
+
};
|
|
163
|
+
const cleanup = () => {
|
|
164
|
+
client.off('ready', onReady);
|
|
165
|
+
client.off('error', onError);
|
|
166
|
+
};
|
|
167
|
+
client.once('ready', onReady);
|
|
168
|
+
client.once('error', onError);
|
|
161
169
|
});
|
|
162
170
|
}
|
|
163
171
|
|
|
@@ -179,26 +187,24 @@ export class RedisPool {
|
|
|
179
187
|
}
|
|
180
188
|
}
|
|
181
189
|
|
|
182
|
-
|
|
190
|
+
private debugLog(...args: any[]): void {
|
|
183
191
|
if (this.debug) {
|
|
184
192
|
console.debug(...args);
|
|
185
193
|
}
|
|
186
194
|
}
|
|
187
195
|
|
|
188
|
-
private getPool(
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
pool = this.createSingleDbPool(dbIndex);
|
|
192
|
-
this.poolMap.set(dbIndex, pool);
|
|
196
|
+
private getPool(): genericPool.Pool<RedisClient> {
|
|
197
|
+
if (!this.pool) {
|
|
198
|
+
this.pool = this.createPool();
|
|
193
199
|
}
|
|
194
|
-
return pool;
|
|
200
|
+
return this.pool;
|
|
195
201
|
}
|
|
196
202
|
|
|
197
|
-
private
|
|
203
|
+
private createPool(): genericPool.Pool<RedisClient> {
|
|
198
204
|
const factory: genericPool.Factory<RedisClient> = {
|
|
199
205
|
create: async (): Promise<RedisClient> => {
|
|
200
206
|
const client = new Redis(this.url, {
|
|
201
|
-
db:
|
|
207
|
+
db: this.db,
|
|
202
208
|
connectTimeout: this.connectTimeout,
|
|
203
209
|
keepAlive: 1,
|
|
204
210
|
enableOfflineQueue: true,
|
|
@@ -212,31 +218,28 @@ export class RedisPool {
|
|
|
212
218
|
},
|
|
213
219
|
reconnectOnError: (error) => {
|
|
214
220
|
process.emitWarning(`${logHeader} detected error (on reconnectOnError). ${error.message}`);
|
|
215
|
-
|
|
221
|
+
// フェイルオーバー後にレプリカへ接続したままになっている場合だけ、再接続で解消できる
|
|
222
|
+
return error.message.startsWith('READONLY');
|
|
216
223
|
}
|
|
217
224
|
}) as RedisClient;
|
|
218
|
-
|
|
219
|
-
client._originalDbIndex = dbIndex;
|
|
220
225
|
client.on('error', error => {
|
|
221
226
|
process.emitWarning(`${logHeader} detected error (on error). ${error.message}`);
|
|
222
227
|
});
|
|
223
228
|
|
|
224
|
-
// カスタムメソッド内の処理でthis.debugLogなどが参照できなくなるため、poolのインスタンスを変数キャプチャする
|
|
225
|
-
const poolInstance = this;
|
|
226
|
-
|
|
227
229
|
// カスタムメソッド START
|
|
228
230
|
|
|
229
231
|
/**
|
|
230
232
|
* 指定されたパターンに一致するキーを Redis から全て取得する
|
|
231
233
|
*
|
|
232
234
|
* @param {string} pattern
|
|
235
|
+
* @param {number} [count] 1度にスキャンする件数 デフォルト:1000
|
|
233
236
|
* @returns {Promise<string[]>}
|
|
234
237
|
*/
|
|
235
|
-
client.getKeys = async function(pattern: string): Promise<string[]> {
|
|
238
|
+
client.getKeys = async function(pattern: string, count?: number): Promise<string[]> {
|
|
236
239
|
const allKeys: string[] = [];
|
|
237
240
|
const stream = this.scanStream({
|
|
238
241
|
match: pattern,
|
|
239
|
-
count:
|
|
242
|
+
count: count ?? 1000
|
|
240
243
|
});
|
|
241
244
|
|
|
242
245
|
return new Promise((resolve, reject) => {
|
|
@@ -245,8 +248,8 @@ export class RedisPool {
|
|
|
245
248
|
allKeys.push(...keys);
|
|
246
249
|
}
|
|
247
250
|
});
|
|
248
|
-
stream.
|
|
249
|
-
stream.
|
|
251
|
+
stream.once('end', () => resolve(allKeys));
|
|
252
|
+
stream.once('error', (err: Error) => {
|
|
250
253
|
reject(err);
|
|
251
254
|
});
|
|
252
255
|
});
|
|
@@ -267,54 +270,54 @@ export class RedisPool {
|
|
|
267
270
|
* .reduce((acc, cur) => acc + (cur as PromiseFulfilledResult<number>).value, 0);
|
|
268
271
|
*
|
|
269
272
|
* @param {string} pattern
|
|
273
|
+
* @param {number} [count] 1度にスキャンする件数 デフォルト:1000
|
|
270
274
|
* @returns {Promise<PromiseSettledResult<number>>}
|
|
271
275
|
*/
|
|
272
|
-
client.deleteKeys = async function(pattern: string): Promise<PromiseSettledResult<number>[]> {
|
|
276
|
+
client.deleteKeys = async function(pattern: string, count?: number): Promise<PromiseSettledResult<number>[]> {
|
|
273
277
|
const stream = this.scanStream({
|
|
274
278
|
match: pattern,
|
|
275
|
-
count:
|
|
279
|
+
count: count ?? 1000
|
|
276
280
|
});
|
|
281
|
+
const results: PromiseSettledResult<number>[] = [];
|
|
277
282
|
|
|
278
283
|
return new Promise((resolve, reject) => {
|
|
279
|
-
const tasks: Promise<number>[] = [];
|
|
280
284
|
stream.on('data', async (keys: string[]) => {
|
|
281
285
|
if (keys.length) {
|
|
282
|
-
|
|
286
|
+
stream.pause();
|
|
287
|
+
try {
|
|
283
288
|
// UNLINK を使用し、現在のインスタンス (this) で実行
|
|
284
289
|
const unlinkResult = await this.unlink(...keys);
|
|
285
|
-
|
|
286
|
-
|
|
290
|
+
results.push({
|
|
291
|
+
status: 'fulfilled',
|
|
292
|
+
value: unlinkResult
|
|
293
|
+
});
|
|
294
|
+
} catch (err) {
|
|
295
|
+
results.push({
|
|
296
|
+
status: 'rejected',
|
|
297
|
+
reason: err
|
|
298
|
+
});
|
|
299
|
+
} finally {
|
|
300
|
+
stream.resume();
|
|
301
|
+
}
|
|
287
302
|
}
|
|
288
303
|
});
|
|
289
|
-
stream.
|
|
290
|
-
const results = await Promise.allSettled(tasks);
|
|
304
|
+
stream.once('end', async () => {
|
|
291
305
|
resolve(results);
|
|
292
306
|
});
|
|
293
|
-
stream.
|
|
307
|
+
stream.once('error', (err: Error) => {
|
|
294
308
|
reject(err);
|
|
295
309
|
});
|
|
296
310
|
});
|
|
297
311
|
};
|
|
298
312
|
// カスタムメソッド END
|
|
299
313
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
// const onError = (error: Error) => {
|
|
308
|
-
// cleanup();
|
|
309
|
-
// reject(error);
|
|
310
|
-
// };
|
|
311
|
-
// const cleanup = () => {
|
|
312
|
-
// client.off('ready', onReady);
|
|
313
|
-
// client.off('error', onError);
|
|
314
|
-
// };
|
|
315
|
-
// client.once('ready', onReady);
|
|
316
|
-
// client.once('error', onError);
|
|
317
|
-
// });
|
|
314
|
+
try {
|
|
315
|
+
await this.waitForReady(client);
|
|
316
|
+
} catch (error) {
|
|
317
|
+
// 接続できなかったクライアントはプールに入れない。ioredisの再接続も止める
|
|
318
|
+
client.disconnect();
|
|
319
|
+
throw error;
|
|
320
|
+
}
|
|
318
321
|
|
|
319
322
|
return client;
|
|
320
323
|
},
|
|
@@ -335,7 +338,8 @@ export class RedisPool {
|
|
|
335
338
|
return genericPool.createPool(factory, {
|
|
336
339
|
max: this.max,
|
|
337
340
|
min: this.min,
|
|
338
|
-
testOnBorrow: this.testOnBorrow
|
|
341
|
+
testOnBorrow: this.testOnBorrow,
|
|
342
|
+
acquireTimeoutMillis: this.acquireTimeout
|
|
339
343
|
});
|
|
340
344
|
}
|
|
341
345
|
}
|