@digitalwalletcorp/redis-pooling 1.1.1 → 1.2.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 +12 -5
- package/lib/redis-pooling.js +106 -94
- package/lib/redis-pooling.js.map +1 -1
- package/package.json +6 -5
- package/src/redis-pooling.ts +104 -99
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,25 +3,26 @@ 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
|
}
|
|
11
12
|
export interface RedisClient extends Redis {
|
|
12
|
-
getKeys(pattern: string): Promise<string[]>;
|
|
13
|
-
deleteKeys(pattern: string): Promise<PromiseSettledResult<number>[]>;
|
|
14
|
-
_originalDbIndex?: number;
|
|
13
|
+
getKeys(pattern: string, count?: number): Promise<string[]>;
|
|
14
|
+
deleteKeys(pattern: string, count?: number): Promise<PromiseSettledResult<number>[]>;
|
|
15
15
|
}
|
|
16
16
|
export declare class RedisPool {
|
|
17
17
|
private readonly url;
|
|
18
18
|
private readonly db;
|
|
19
19
|
private readonly connectTimeout;
|
|
20
|
+
private readonly acquireTimeout;
|
|
20
21
|
private readonly max;
|
|
21
22
|
private readonly min;
|
|
22
23
|
private readonly testOnBorrow;
|
|
23
24
|
private readonly tls?;
|
|
24
|
-
private
|
|
25
|
+
private pool?;
|
|
25
26
|
private initialized;
|
|
26
27
|
private readonly debug;
|
|
27
28
|
constructor(config: RedisConfig, options?: {
|
|
@@ -36,8 +37,14 @@ export declare class RedisPool {
|
|
|
36
37
|
* @param {number} dbIndex
|
|
37
38
|
*/
|
|
38
39
|
private checkConnectivity;
|
|
40
|
+
/**
|
|
41
|
+
* Redisクライアントの接続が完了するまで待つ。接続に失敗した場合は、そのエラーで reject する
|
|
42
|
+
*
|
|
43
|
+
* @param {Redis} client
|
|
44
|
+
*/
|
|
45
|
+
private waitForReady;
|
|
39
46
|
private ping;
|
|
40
47
|
debugLog(...args: any[]): void;
|
|
41
48
|
private getPool;
|
|
42
|
-
private
|
|
49
|
+
private createPool;
|
|
43
50
|
}
|
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,10 +221,10 @@ 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
|
});
|
|
@@ -228,13 +235,14 @@ class RedisPool {
|
|
|
228
235
|
* 指定されたパターンに一致するキーを Redis から全て取得する
|
|
229
236
|
*
|
|
230
237
|
* @param {string} pattern
|
|
238
|
+
* @param {number} [count] 1度にスキャンする件数 デフォルト:1000
|
|
231
239
|
* @returns {Promise<string[]>}
|
|
232
240
|
*/
|
|
233
|
-
client.getKeys = async function (pattern) {
|
|
241
|
+
client.getKeys = async function (pattern, count) {
|
|
234
242
|
const allKeys = [];
|
|
235
243
|
const stream = this.scanStream({
|
|
236
244
|
match: pattern,
|
|
237
|
-
count:
|
|
245
|
+
count: count ?? 1000
|
|
238
246
|
});
|
|
239
247
|
return new Promise((resolve, reject) => {
|
|
240
248
|
stream.on('data', (keys) => {
|
|
@@ -242,8 +250,8 @@ class RedisPool {
|
|
|
242
250
|
allKeys.push(...keys);
|
|
243
251
|
}
|
|
244
252
|
});
|
|
245
|
-
stream.
|
|
246
|
-
stream.
|
|
253
|
+
stream.once('end', () => resolve(allKeys));
|
|
254
|
+
stream.once('error', (err) => {
|
|
247
255
|
reject(err);
|
|
248
256
|
});
|
|
249
257
|
});
|
|
@@ -264,52 +272,55 @@ class RedisPool {
|
|
|
264
272
|
* .reduce((acc, cur) => acc + (cur as PromiseFulfilledResult<number>).value, 0);
|
|
265
273
|
*
|
|
266
274
|
* @param {string} pattern
|
|
275
|
+
* @param {number} [count] 1度にスキャンする件数 デフォルト:1000
|
|
267
276
|
* @returns {Promise<PromiseSettledResult<number>>}
|
|
268
277
|
*/
|
|
269
|
-
client.deleteKeys = async function (pattern) {
|
|
278
|
+
client.deleteKeys = async function (pattern, count) {
|
|
270
279
|
const stream = this.scanStream({
|
|
271
280
|
match: pattern,
|
|
272
|
-
count:
|
|
281
|
+
count: count ?? 1000
|
|
273
282
|
});
|
|
283
|
+
const results = [];
|
|
274
284
|
return new Promise((resolve, reject) => {
|
|
275
|
-
const tasks = [];
|
|
276
285
|
stream.on('data', async (keys) => {
|
|
277
286
|
if (keys.length) {
|
|
278
|
-
|
|
287
|
+
stream.pause();
|
|
288
|
+
try {
|
|
279
289
|
// UNLINK を使用し、現在のインスタンス (this) で実行
|
|
280
290
|
const unlinkResult = await this.unlink(...keys);
|
|
281
|
-
|
|
282
|
-
|
|
291
|
+
results.push({
|
|
292
|
+
status: 'fulfilled',
|
|
293
|
+
value: unlinkResult
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
results.push({
|
|
298
|
+
status: 'rejected',
|
|
299
|
+
reason: err
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
finally {
|
|
303
|
+
stream.resume();
|
|
304
|
+
}
|
|
283
305
|
}
|
|
284
306
|
});
|
|
285
|
-
stream.
|
|
286
|
-
const results = await Promise.allSettled(tasks);
|
|
307
|
+
stream.once('end', async () => {
|
|
287
308
|
resolve(results);
|
|
288
309
|
});
|
|
289
|
-
stream.
|
|
310
|
+
stream.once('error', (err) => {
|
|
290
311
|
reject(err);
|
|
291
312
|
});
|
|
292
313
|
});
|
|
293
314
|
};
|
|
294
315
|
// カスタムメソッド 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
|
-
// });
|
|
316
|
+
try {
|
|
317
|
+
await this.waitForReady(client);
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
// 接続できなかったクライアントはプールに入れない。ioredisの再接続も止める
|
|
321
|
+
client.disconnect();
|
|
322
|
+
throw error;
|
|
323
|
+
}
|
|
313
324
|
return client;
|
|
314
325
|
},
|
|
315
326
|
destroy: async (client) => {
|
|
@@ -329,7 +340,8 @@ class RedisPool {
|
|
|
329
340
|
return genericPool.createPool(factory, {
|
|
330
341
|
max: this.max,
|
|
331
342
|
min: this.min,
|
|
332
|
-
testOnBorrow: this.testOnBorrow
|
|
343
|
+
testOnBorrow: this.testOnBorrow,
|
|
344
|
+
acquireTimeoutMillis: this.acquireTimeout
|
|
333
345
|
});
|
|
334
346
|
}
|
|
335
347
|
}
|
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;AAkB5C,MAAM,SAAS,GAAG,gBAAgB,CAAC;AACnC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AACrC,MAAM,wBAAwB,GAAG,EAAE,CAAC;AACpC,MAAM,wBAAwB,GAAG,CAAC,CAAC;AACnC,MAAM,qBAAqB,GAAG,IAAI,CAAC,CAAC,KAAK;AAEzC,MAAa,SAAS;IAEH,GAAG,CAAS;IACZ,EAAE,CAAS;IACX,cAAc,CAAS;IACvB,GAAG,CAAS;IACZ,GAAG,CAAS;IACZ,YAAY,CAAU;IACtB,GAAG,CAAiC;
|
|
1
|
+
{"version":3,"file":"redis-pooling.js","sourceRoot":"","sources":["../src/redis-pooling.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@digitalwalletcorp/redis-pooling",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.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",
|
|
@@ -34,12 +34,13 @@
|
|
|
34
34
|
],
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/ioredis-mock": "^8.2.6",
|
|
37
|
-
"@types/jest": "^
|
|
37
|
+
"@types/jest": "^30.0.0",
|
|
38
38
|
"ioredis-mock": "^8.13.1",
|
|
39
|
-
"jest": "^
|
|
40
|
-
"ts-jest": "^29.
|
|
39
|
+
"jest": "^30.3.0",
|
|
40
|
+
"ts-jest": "^29.4.9",
|
|
41
41
|
"ts-node": "^10.9.2",
|
|
42
|
-
"
|
|
42
|
+
"tsconfig-paths": "^4.2.0",
|
|
43
|
+
"typescript": "^6.0.3"
|
|
43
44
|
},
|
|
44
45
|
"dependencies": {
|
|
45
46
|
"generic-pool": "^3.9.0",
|
package/src/redis-pooling.ts
CHANGED
|
@@ -5,6 +5,7 @@ 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;
|
|
@@ -12,13 +13,13 @@ export interface RedisConfig {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export interface RedisClient extends Redis {
|
|
15
|
-
getKeys(pattern: string): Promise<string[]>;
|
|
16
|
-
deleteKeys(pattern: string): Promise<PromiseSettledResult<number>[]>;
|
|
17
|
-
_originalDbIndex?: number; // 内部状態管理用変数
|
|
16
|
+
getKeys(pattern: string, count?: number): Promise<string[]>;
|
|
17
|
+
deleteKeys(pattern: string, count?: number): Promise<PromiseSettledResult<number>[]>;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const logHeader = '[RedisPooling]';
|
|
21
21
|
const DEFAULT_CONNECT_TIMEOUT = 5000;
|
|
22
|
+
const DEFAULT_ACQUIRE_TIMEOUT = 10000;
|
|
22
23
|
const DEFAULT_MAX_POOLING_SIZE = 10;
|
|
23
24
|
const DEFAULT_MIN_POOLING_SIZE = 0;
|
|
24
25
|
const REDIS_PING_TIMEOUT_MS = 3000; // 3秒
|
|
@@ -28,12 +29,13 @@ export class RedisPool {
|
|
|
28
29
|
private readonly url: string;
|
|
29
30
|
private readonly db: number;
|
|
30
31
|
private readonly connectTimeout: number;
|
|
32
|
+
private readonly acquireTimeout: number;
|
|
31
33
|
private readonly max: number;
|
|
32
34
|
private readonly min: number;
|
|
33
35
|
private readonly testOnBorrow: boolean;
|
|
34
36
|
private readonly tls?: { rejectUnauthorized: false };
|
|
35
37
|
|
|
36
|
-
private
|
|
38
|
+
private pool?: genericPool.Pool<RedisClient>;
|
|
37
39
|
private initialized = false;
|
|
38
40
|
private readonly debug: boolean;
|
|
39
41
|
|
|
@@ -47,6 +49,7 @@ export class RedisPool {
|
|
|
47
49
|
this.url = config.url;
|
|
48
50
|
this.db = config.dbIndex ?? 0;
|
|
49
51
|
this.connectTimeout = config.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT;
|
|
52
|
+
this.acquireTimeout = config.acquireTimeout ?? DEFAULT_ACQUIRE_TIMEOUT;
|
|
50
53
|
this.max = config.max ?? DEFAULT_MAX_POOLING_SIZE;
|
|
51
54
|
this.min = config.min ?? DEFAULT_MIN_POOLING_SIZE;
|
|
52
55
|
this.testOnBorrow = config.testOnBorrow ?? true;
|
|
@@ -65,69 +68,55 @@ export class RedisPool {
|
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
const index = dbIndex ?? this.db;
|
|
68
|
-
const pool = this.getPool(
|
|
71
|
+
const pool = this.getPool();
|
|
72
|
+
const client = await pool.acquire();
|
|
69
73
|
try {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
this.debugLog(logHeader, `Redis client ${index} has been acquired.`);
|
|
73
|
-
return client;
|
|
74
|
+
// プールの接続は、前の利用者が選択したDBを保持したまま返却される。そのため貸し出すたびに対象のDBを選択する
|
|
75
|
+
await client.select(index);
|
|
74
76
|
} catch (error) {
|
|
77
|
+
await pool.destroy(client);
|
|
75
78
|
throw error;
|
|
76
79
|
}
|
|
80
|
+
this.debugLog(logHeader, `Redis client ${index} has been acquired.`);
|
|
81
|
+
return client;
|
|
77
82
|
}
|
|
78
83
|
|
|
79
84
|
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
|
-
}
|
|
85
|
+
if (!client || !this.pool) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (client.status === 'end' || client.status === 'close') {
|
|
89
|
+
// 再利用できない状態のRedisクライアントはプールから破棄する
|
|
90
|
+
await this.pool.destroy(client);
|
|
91
|
+
this.debugLog(logHeader, 'Redis client destroyed due to invalid status.');
|
|
92
|
+
} else {
|
|
93
|
+
await this.pool.release(client);
|
|
94
|
+
this.debugLog(logHeader, 'Redis client released.');
|
|
113
95
|
}
|
|
114
96
|
}
|
|
115
97
|
|
|
116
98
|
public async destroy(timeoutMs = 5000): Promise<void> {
|
|
117
|
-
|
|
118
|
-
|
|
99
|
+
const pool = this.pool;
|
|
100
|
+
if (!pool) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
this.debugLog(logHeader, 'Destroying Redis pool...');
|
|
104
|
+
let timer: NodeJS.Timeout | undefined;
|
|
105
|
+
try {
|
|
119
106
|
await Promise.race([
|
|
120
107
|
(async () => {
|
|
121
108
|
await pool.drain();
|
|
122
109
|
await pool.clear();
|
|
123
110
|
})(),
|
|
124
111
|
new Promise<void>((_, reject) => {
|
|
125
|
-
setTimeout(() => reject(new Error(`${logHeader} Timeout while draining Redis pool
|
|
112
|
+
timer = setTimeout(() => reject(new Error(`${logHeader} Timeout while draining Redis pool`)), timeoutMs);
|
|
126
113
|
})
|
|
127
114
|
]);
|
|
128
|
-
|
|
129
|
-
|
|
115
|
+
} finally {
|
|
116
|
+
clearTimeout(timer);
|
|
130
117
|
}
|
|
118
|
+
this.debugLog(logHeader, 'Redis pool destroyed.');
|
|
119
|
+
this.pool = undefined;
|
|
131
120
|
}
|
|
132
121
|
|
|
133
122
|
/**
|
|
@@ -144,20 +133,37 @@ export class RedisPool {
|
|
|
144
133
|
tls: this.tls,
|
|
145
134
|
});
|
|
146
135
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
136
|
+
try {
|
|
137
|
+
await this.waitForReady(client);
|
|
138
|
+
} finally {
|
|
139
|
+
client.quit().catch(() => client.disconnect());
|
|
140
|
+
}
|
|
141
|
+
}
|
|
151
142
|
|
|
152
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Redisクライアントの接続が完了するまで待つ。接続に失敗した場合は、そのエラーで reject する
|
|
145
|
+
*
|
|
146
|
+
* @param {Redis} client
|
|
147
|
+
*/
|
|
148
|
+
private async waitForReady(client: Redis): Promise<void> {
|
|
149
|
+
if (client.status === 'ready') {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
return new Promise<void>((resolve, reject) => {
|
|
153
|
+
const onReady = () => {
|
|
153
154
|
cleanup();
|
|
154
155
|
resolve();
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
client.once('error', (error) => {
|
|
156
|
+
};
|
|
157
|
+
const onError = (error: Error) => {
|
|
158
158
|
cleanup();
|
|
159
159
|
reject(error);
|
|
160
|
-
}
|
|
160
|
+
};
|
|
161
|
+
const cleanup = () => {
|
|
162
|
+
client.off('ready', onReady);
|
|
163
|
+
client.off('error', onError);
|
|
164
|
+
};
|
|
165
|
+
client.once('ready', onReady);
|
|
166
|
+
client.once('error', onError);
|
|
161
167
|
});
|
|
162
168
|
}
|
|
163
169
|
|
|
@@ -185,20 +191,18 @@ export class RedisPool {
|
|
|
185
191
|
}
|
|
186
192
|
}
|
|
187
193
|
|
|
188
|
-
private getPool(
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
pool = this.createSingleDbPool(dbIndex);
|
|
192
|
-
this.poolMap.set(dbIndex, pool);
|
|
194
|
+
private getPool(): genericPool.Pool<RedisClient> {
|
|
195
|
+
if (!this.pool) {
|
|
196
|
+
this.pool = this.createPool();
|
|
193
197
|
}
|
|
194
|
-
return pool;
|
|
198
|
+
return this.pool;
|
|
195
199
|
}
|
|
196
200
|
|
|
197
|
-
private
|
|
201
|
+
private createPool(): genericPool.Pool<RedisClient> {
|
|
198
202
|
const factory: genericPool.Factory<RedisClient> = {
|
|
199
203
|
create: async (): Promise<RedisClient> => {
|
|
200
204
|
const client = new Redis(this.url, {
|
|
201
|
-
db:
|
|
205
|
+
db: this.db,
|
|
202
206
|
connectTimeout: this.connectTimeout,
|
|
203
207
|
keepAlive: 1,
|
|
204
208
|
enableOfflineQueue: true,
|
|
@@ -212,11 +216,10 @@ export class RedisPool {
|
|
|
212
216
|
},
|
|
213
217
|
reconnectOnError: (error) => {
|
|
214
218
|
process.emitWarning(`${logHeader} detected error (on reconnectOnError). ${error.message}`);
|
|
215
|
-
|
|
219
|
+
// フェイルオーバー後にレプリカへ接続したままになっている場合だけ、再接続で解消できる
|
|
220
|
+
return error.message.startsWith('READONLY');
|
|
216
221
|
}
|
|
217
222
|
}) as RedisClient;
|
|
218
|
-
|
|
219
|
-
client._originalDbIndex = dbIndex;
|
|
220
223
|
client.on('error', error => {
|
|
221
224
|
process.emitWarning(`${logHeader} detected error (on error). ${error.message}`);
|
|
222
225
|
});
|
|
@@ -230,13 +233,14 @@ export class RedisPool {
|
|
|
230
233
|
* 指定されたパターンに一致するキーを Redis から全て取得する
|
|
231
234
|
*
|
|
232
235
|
* @param {string} pattern
|
|
236
|
+
* @param {number} [count] 1度にスキャンする件数 デフォルト:1000
|
|
233
237
|
* @returns {Promise<string[]>}
|
|
234
238
|
*/
|
|
235
|
-
client.getKeys = async function(pattern: string): Promise<string[]> {
|
|
239
|
+
client.getKeys = async function(pattern: string, count?: number): Promise<string[]> {
|
|
236
240
|
const allKeys: string[] = [];
|
|
237
241
|
const stream = this.scanStream({
|
|
238
242
|
match: pattern,
|
|
239
|
-
count:
|
|
243
|
+
count: count ?? 1000
|
|
240
244
|
});
|
|
241
245
|
|
|
242
246
|
return new Promise((resolve, reject) => {
|
|
@@ -245,8 +249,8 @@ export class RedisPool {
|
|
|
245
249
|
allKeys.push(...keys);
|
|
246
250
|
}
|
|
247
251
|
});
|
|
248
|
-
stream.
|
|
249
|
-
stream.
|
|
252
|
+
stream.once('end', () => resolve(allKeys));
|
|
253
|
+
stream.once('error', (err: Error) => {
|
|
250
254
|
reject(err);
|
|
251
255
|
});
|
|
252
256
|
});
|
|
@@ -267,54 +271,54 @@ export class RedisPool {
|
|
|
267
271
|
* .reduce((acc, cur) => acc + (cur as PromiseFulfilledResult<number>).value, 0);
|
|
268
272
|
*
|
|
269
273
|
* @param {string} pattern
|
|
274
|
+
* @param {number} [count] 1度にスキャンする件数 デフォルト:1000
|
|
270
275
|
* @returns {Promise<PromiseSettledResult<number>>}
|
|
271
276
|
*/
|
|
272
|
-
client.deleteKeys = async function(pattern: string): Promise<PromiseSettledResult<number>[]> {
|
|
277
|
+
client.deleteKeys = async function(pattern: string, count?: number): Promise<PromiseSettledResult<number>[]> {
|
|
273
278
|
const stream = this.scanStream({
|
|
274
279
|
match: pattern,
|
|
275
|
-
count:
|
|
280
|
+
count: count ?? 1000
|
|
276
281
|
});
|
|
282
|
+
const results: PromiseSettledResult<number>[] = [];
|
|
277
283
|
|
|
278
284
|
return new Promise((resolve, reject) => {
|
|
279
|
-
const tasks: Promise<number>[] = [];
|
|
280
285
|
stream.on('data', async (keys: string[]) => {
|
|
281
286
|
if (keys.length) {
|
|
282
|
-
|
|
287
|
+
stream.pause();
|
|
288
|
+
try {
|
|
283
289
|
// UNLINK を使用し、現在のインスタンス (this) で実行
|
|
284
290
|
const unlinkResult = await this.unlink(...keys);
|
|
285
|
-
|
|
286
|
-
|
|
291
|
+
results.push({
|
|
292
|
+
status: 'fulfilled',
|
|
293
|
+
value: unlinkResult
|
|
294
|
+
});
|
|
295
|
+
} catch (err) {
|
|
296
|
+
results.push({
|
|
297
|
+
status: 'rejected',
|
|
298
|
+
reason: err
|
|
299
|
+
});
|
|
300
|
+
} finally {
|
|
301
|
+
stream.resume();
|
|
302
|
+
}
|
|
287
303
|
}
|
|
288
304
|
});
|
|
289
|
-
stream.
|
|
290
|
-
const results = await Promise.allSettled(tasks);
|
|
305
|
+
stream.once('end', async () => {
|
|
291
306
|
resolve(results);
|
|
292
307
|
});
|
|
293
|
-
stream.
|
|
308
|
+
stream.once('error', (err: Error) => {
|
|
294
309
|
reject(err);
|
|
295
310
|
});
|
|
296
311
|
});
|
|
297
312
|
};
|
|
298
313
|
// カスタムメソッド END
|
|
299
314
|
|
|
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
|
-
// });
|
|
315
|
+
try {
|
|
316
|
+
await this.waitForReady(client);
|
|
317
|
+
} catch (error) {
|
|
318
|
+
// 接続できなかったクライアントはプールに入れない。ioredisの再接続も止める
|
|
319
|
+
client.disconnect();
|
|
320
|
+
throw error;
|
|
321
|
+
}
|
|
318
322
|
|
|
319
323
|
return client;
|
|
320
324
|
},
|
|
@@ -335,7 +339,8 @@ export class RedisPool {
|
|
|
335
339
|
return genericPool.createPool(factory, {
|
|
336
340
|
max: this.max,
|
|
337
341
|
min: this.min,
|
|
338
|
-
testOnBorrow: this.testOnBorrow
|
|
342
|
+
testOnBorrow: this.testOnBorrow,
|
|
343
|
+
acquireTimeoutMillis: this.acquireTimeout
|
|
339
344
|
});
|
|
340
345
|
}
|
|
341
346
|
}
|