@digitalwalletcorp/redis-pooling 1.1.0 → 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 CHANGED
@@ -101,8 +101,10 @@ async function manageCache() {
101
101
  console.log('Matching keys:', keys);
102
102
 
103
103
  // Delete all matching keys using UNLINK
104
- const deleted = await client.deleteKeys('cache:*');
105
- console.log(`Deleted ${deleted} keys`);
104
+ const delResults = await client.deleteKeys('cache:*');
105
+ const succeeded = delResults.filter(a => a.status === 'fulfilled');
106
+ const delCount = succeeded.reduce((acc, cur) => acc + (cur as PromiseFulfilledResult<number>).value, 0);
107
+ console.log(`Deleted ${delCount} keys`);
106
108
  } finally {
107
109
  await redisPool.release(client);
108
110
  }
@@ -122,6 +124,7 @@ Creates a Redis connection pool.
122
124
  | `max` | number | 10 | Maximum number of clients in the pool. |
123
125
  | `min` | number | 0 | Minimum number of clients in the pool. |
124
126
  | `connectTimeout` | number | 5000 | Connection timeout in milliseconds. |
127
+ | `acquireTimeout` | number | 10000 | Timeout in milliseconds to acquire a client from the pool. |
125
128
  | `testOnBorrow` | boolean | true | Enable connection validate on borrow. |
126
129
  | `enableTls` | boolean | false | Enable TLS for Redis connection. |
127
130
 
@@ -140,13 +143,16 @@ Extends the standard `ioredis` `Redis` client with additional helpers:
140
143
  | Method | Signature | Description |
141
144
  | ----------------------------- | ------------------- | ----------------------------------------------------------------------------------------------- |
142
145
  | `getKeys(pattern: string)` | `Promise<string[]>` | Scan and return all keys matching a pattern. |
143
- | `deleteKeys(pattern: string)` | `Promise<number>` | Scan and delete all keys matching a pattern using `UNLINK`. Returns the number of deleted keys. |
146
+ | `deleteKeys(pattern: string)` | `Promise<PromiseSettledResult<number>[]>` | Scan and delete all keys matching a pattern using `UNLINK`. Returns an array of results, one for each batch processed by `UNLINK`. Each result contains the number of keys deleted in that batch. |
144
147
 
145
148
  #### 🗄 Database Index Handling
146
149
 
147
150
  This library provides a Redis connection pool capable of managing Redis clients
148
151
  connected to different database indexes (`SELECT db`).
149
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
+
150
156
  **Calling `acquire(dbIndex)`:**
151
157
 
152
158
  > When `dbIndex` is specified, the acquired client will be connected to the specified database index.
@@ -3,29 +3,48 @@ 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<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
- private readonly enableTls?;
24
- private readonly poolMap;
25
- constructor(config: RedisConfig);
24
+ private readonly tls?;
25
+ private pool?;
26
+ private initialized;
27
+ private readonly debug;
28
+ constructor(config: RedisConfig, options?: {
29
+ debug?: boolean;
30
+ });
26
31
  acquire(dbIndex?: number): Promise<RedisClient>;
27
32
  release(client?: RedisClient): Promise<void>;
28
33
  destroy(timeoutMs?: number): Promise<void>;
34
+ /**
35
+ * 初めてacquireが呼ばれた時に、指定された接続情報で接続できるかチェックする
36
+ *
37
+ * @param {number} dbIndex
38
+ */
39
+ private checkConnectivity;
40
+ /**
41
+ * Redisクライアントの接続が完了するまで待つ。接続に失敗した場合は、そのエラーで reject する
42
+ *
43
+ * @param {Redis} client
44
+ */
45
+ private waitForReady;
46
+ private ping;
47
+ debugLog(...args: any[]): void;
29
48
  private getPool;
30
- private createSingleDbPool;
49
+ private createPool;
31
50
  }
@@ -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,136 +46,203 @@ class RedisPool {
45
46
  url;
46
47
  db;
47
48
  connectTimeout;
49
+ acquireTimeout;
48
50
  max;
49
51
  min;
50
52
  testOnBorrow;
51
- enableTls;
52
- poolMap = new Map();
53
- constructor(config) {
53
+ tls;
54
+ pool;
55
+ initialized = false;
56
+ debug;
57
+ constructor(config, options) {
54
58
  if (!config.url) {
55
59
  throw new Error(`${logHeader} Redis connection url is required.`);
56
60
  }
57
61
  this.url = config.url;
58
62
  this.db = config.dbIndex ?? 0;
59
63
  this.connectTimeout = config.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT;
64
+ this.acquireTimeout = config.acquireTimeout ?? DEFAULT_ACQUIRE_TIMEOUT;
60
65
  this.max = config.max ?? DEFAULT_MAX_POOLING_SIZE;
61
66
  this.min = config.min ?? DEFAULT_MIN_POOLING_SIZE;
62
67
  this.testOnBorrow = config.testOnBorrow ?? true;
63
- this.enableTls = config.enableTls;
68
+ this.tls = config.enableTls ? { rejectUnauthorized: false } : undefined;
69
+ this.debug = options?.debug ?? false;
64
70
  }
65
71
  async acquire(dbIndex) {
72
+ if (!this.initialized) {
73
+ // 初回acquire呼び出し時のみ接続チェックを行う
74
+ // ホスト不正やパスワード不正による接続不可等を検知する
75
+ // ※ generic-poolのfactoryの方に入ってしまうとエラーを呼び出し元に伝播させることが難しいため、プールとは別の接続でチェックする
76
+ await this.checkConnectivity(dbIndex ?? this.db);
77
+ this.initialized = true;
78
+ }
66
79
  const index = dbIndex ?? this.db;
67
- const pool = this.getPool(index);
80
+ const pool = this.getPool();
68
81
  const client = await pool.acquire();
69
- console.debug(logHeader, `Redis client ${index} has been acquired.`);
82
+ try {
83
+ // プールの接続は、前の利用者が選択したDBを保持したまま返却される。そのため貸し出すたびに対象のDBを選択する
84
+ await client.select(index);
85
+ }
86
+ catch (error) {
87
+ await pool.destroy(client);
88
+ throw error;
89
+ }
90
+ this.debugLog(logHeader, `Redis client ${index} has been acquired.`);
70
91
  return client;
71
92
  }
72
93
  async release(client) {
73
- if (client) {
74
- const dbIndex = client._originalDbIndex ?? this.db;
75
- const pool = this.poolMap.get(dbIndex);
76
- if (!pool) {
77
- return;
78
- }
79
- let needDestroy = false;
80
- switch (true) {
81
- case client.status === 'end':
82
- case client.status === 'close':
83
- // Redisクライアントの状態が再利用できない場合はプールから破棄
84
- needDestroy = true;
85
- break;
86
- case client.status === 'ready':
87
- try {
88
- // 元のDBインデックスに戻す
89
- await client.select(dbIndex);
90
- }
91
- catch (error) {
92
- // selectに失敗する→Redisクライアントが不正な状態にあると判断できるのでプールから破棄
93
- needDestroy = true;
94
- }
95
- break;
96
- default:
97
- }
98
- if (needDestroy) {
99
- // Redisクライアントの破棄
100
- await pool.destroy(client);
101
- console.debug(logHeader, `Redis client ${dbIndex} destroyed due to invalid status.`);
102
- }
103
- else {
104
- // Redisクライアントの返却
105
- await pool.release(client);
106
- console.debug(logHeader, `Redis client ${dbIndex} released.`);
107
- }
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.');
108
105
  }
109
106
  }
110
107
  async destroy(timeoutMs = 5000) {
111
- for (const [dbIndex, pool] of this.poolMap.entries()) {
112
- console.debug(logHeader, `Destroying Redis pool for DB index ${dbIndex}...`);
108
+ const pool = this.pool;
109
+ if (!pool) {
110
+ return;
111
+ }
112
+ this.debugLog(logHeader, 'Destroying Redis pool...');
113
+ let timer;
114
+ try {
113
115
  await Promise.race([
114
116
  (async () => {
115
117
  await pool.drain();
116
118
  await pool.clear();
117
119
  })(),
118
120
  new Promise((_, reject) => {
119
- setTimeout(() => reject(new Error(`${logHeader} Timeout while draining Redis pool for DB index ${dbIndex}`)), timeoutMs);
121
+ timer = setTimeout(() => reject(new Error(`${logHeader} Timeout while draining Redis pool`)), timeoutMs);
120
122
  })
121
123
  ]);
122
- console.debug(logHeader, `Redis pool for DB index ${dbIndex} destroyed.`);
123
- this.poolMap.delete(dbIndex);
124
124
  }
125
+ finally {
126
+ clearTimeout(timer);
127
+ }
128
+ this.debugLog(logHeader, 'Redis pool destroyed.');
129
+ this.pool = undefined;
125
130
  }
126
- getPool(dbIndex) {
127
- let pool = this.poolMap.get(dbIndex);
128
- if (!pool) {
129
- pool = this.createSingleDbPool(dbIndex);
130
- this.poolMap.set(dbIndex, pool);
131
+ /**
132
+ * 初めてacquireが呼ばれた時に、指定された接続情報で接続できるかチェックする
133
+ *
134
+ * @param {number} dbIndex
135
+ */
136
+ async checkConnectivity(dbIndex) {
137
+ const client = new ioredis_1.Redis(this.url, {
138
+ db: dbIndex,
139
+ retryStrategy: () => null, // 再接続無効
140
+ reconnectOnError: () => false, // 再接続無効
141
+ connectTimeout: this.connectTimeout,
142
+ tls: this.tls,
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
+ }
160
+ return new Promise((resolve, reject) => {
161
+ const onReady = () => {
162
+ cleanup();
163
+ resolve();
164
+ };
165
+ const onError = (error) => {
166
+ cleanup();
167
+ reject(error);
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);
175
+ });
176
+ }
177
+ async ping(client) {
178
+ try {
179
+ this.debugLog(logHeader, 'start validate');
180
+ const timeout = new Promise((_, reject) => {
181
+ setTimeout(() => reject(new Error(`Redis PING timeout after ${REDIS_PING_TIMEOUT_MS}ms`)), REDIS_PING_TIMEOUT_MS);
182
+ });
183
+ await Promise.race([
184
+ client.ping(),
185
+ timeout
186
+ ]);
187
+ this.debugLog(logHeader, 'ping succeeded');
188
+ return client.status === 'ready';
189
+ }
190
+ catch (error) {
191
+ this.debugLog(logHeader, 'ping failed');
192
+ return false;
193
+ }
194
+ }
195
+ debugLog(...args) {
196
+ if (this.debug) {
197
+ console.debug(...args);
198
+ }
199
+ }
200
+ getPool() {
201
+ if (!this.pool) {
202
+ this.pool = this.createPool();
131
203
  }
132
- return pool;
204
+ return this.pool;
133
205
  }
134
- createSingleDbPool(dbIndex) {
206
+ createPool() {
135
207
  const factory = {
136
208
  create: async () => {
137
- let client;
138
- try {
139
- client = new ioredis_1.Redis(this.url, {
140
- db: dbIndex,
141
- connectTimeout: this.connectTimeout,
142
- keepAlive: 1,
143
- enableOfflineQueue: true,
144
- tls: this.enableTls ? { rejectUnauthorized: false } : undefined,
145
- retryStrategy: (times) => {
146
- const delay = Math.min(times * 50, 1000);
147
- if (process.env.NODE_ENV !== 'test') {
148
- console.debug(logHeader, `retry strategy called ${times} times. delaying ${delay}ms`);
149
- }
150
- return delay;
151
- },
152
- reconnectOnError: (error) => {
153
- process.emitWarning(`${logHeader} detected error (on reconnect). ${error.message}`);
154
- return true;
209
+ const client = new ioredis_1.Redis(this.url, {
210
+ db: this.db,
211
+ connectTimeout: this.connectTimeout,
212
+ keepAlive: 1,
213
+ enableOfflineQueue: true,
214
+ tls: this.tls,
215
+ retryStrategy: (times) => {
216
+ const delay = Math.min(times * 50, 1000);
217
+ if (process.env.NODE_ENV !== 'test') {
218
+ this.debugLog(logHeader, `retry strategy called ${times} times. delaying ${delay}ms`);
155
219
  }
156
- });
157
- }
158
- catch (error) {
159
- console.error(error);
160
- throw error;
161
- }
162
- client._originalDbIndex = dbIndex;
220
+ return delay;
221
+ },
222
+ reconnectOnError: (error) => {
223
+ process.emitWarning(`${logHeader} detected error (on reconnectOnError). ${error.message}`);
224
+ // フェイルオーバー後にレプリカへ接続したままになっている場合だけ、再接続で解消できる
225
+ return error.message.startsWith('READONLY');
226
+ }
227
+ });
163
228
  client.on('error', error => {
164
229
  process.emitWarning(`${logHeader} detected error (on error). ${error.message}`);
165
230
  });
231
+ // カスタムメソッド内の処理でthis.debugLogなどが参照できなくなるため、poolのインスタンスを変数キャプチャする
232
+ const poolInstance = this;
166
233
  // カスタムメソッド START
167
234
  /**
168
235
  * 指定されたパターンに一致するキーを Redis から全て取得する
169
236
  *
170
237
  * @param {string} pattern
238
+ * @param {number} [count] 1度にスキャンする件数 デフォルト:1000
171
239
  * @returns {Promise<string[]>}
172
240
  */
173
- client.getKeys = async function (pattern) {
241
+ client.getKeys = async function (pattern, count) {
174
242
  const allKeys = [];
175
243
  const stream = this.scanStream({
176
244
  match: pattern,
177
- count: 1000 // ioredisが1度にスキャンする件数の目安。1000件を超えるデータがあっても全件返却される
245
+ count: count ?? 1000
178
246
  });
179
247
  return new Promise((resolve, reject) => {
180
248
  stream.on('data', (keys) => {
@@ -182,106 +250,98 @@ class RedisPool {
182
250
  allKeys.push(...keys);
183
251
  }
184
252
  });
185
- stream.on('end', () => resolve(allKeys));
186
- stream.on('error', (err) => {
187
- console.error(logHeader, `Error during getKeys scan for pattern '${pattern}'.`, err);
253
+ stream.once('end', () => resolve(allKeys));
254
+ stream.once('error', (err) => {
188
255
  reject(err);
189
256
  });
190
257
  });
191
258
  };
192
259
  /**
193
260
  * 指定されたパターンに一致するキーを Redis から全て削除する (UNLINKを使用)
261
+ * 返却値の配列サイズはscanStreamが'data'を受信した回数で、この受信したデータで削除された件数がvalueに設定されている。
262
+ *
263
+ * [
264
+ * { status: 'fulfilled', value: 100 }, // 1バッチ目で100件削除
265
+ * { status: 'fulfilled', value: 80 } // 2バッチ目で80件削除
266
+ * ]
267
+ *
268
+ * 成功した件数は以下のようにして取得可能
269
+ *
270
+ * const delResults = await redisClient.deleteKeys('pattern');
271
+ * const delCount = delResults.filter(a => a.status === 'fulfilled')
272
+ * .reduce((acc, cur) => acc + (cur as PromiseFulfilledResult<number>).value, 0);
194
273
  *
195
274
  * @param {string} pattern
196
- * @returns {Promise<number>}
275
+ * @param {number} [count] 1度にスキャンする件数 デフォルト:1000
276
+ * @returns {Promise<PromiseSettledResult<number>>}
197
277
  */
198
- client.deleteKeys = async function (pattern) {
199
- let deletedCount = 0;
278
+ client.deleteKeys = async function (pattern, count) {
200
279
  const stream = this.scanStream({
201
280
  match: pattern,
202
- count: 1000 // 1度にスキャンする件数
281
+ count: count ?? 1000
203
282
  });
283
+ const results = [];
204
284
  return new Promise((resolve, reject) => {
205
- const tasks = [];
206
285
  stream.on('data', async (keys) => {
207
286
  if (keys.length) {
208
- tasks.push((async () => {
209
- try {
210
- // UNLINK を使用し、現在のインスタンス (this) で実行
211
- const unlinkResult = await this.unlink(...keys);
212
- deletedCount += unlinkResult;
213
- console.debug(logHeader, `Deleted ${unlinkResult} keys in a batch for pattern '${pattern}'. Total: ${deletedCount}`);
214
- }
215
- catch (error) {
216
- process.emitWarning(`${logHeader} Error during UNLINK for pattern '${pattern}'. ${error.message}`);
217
- }
218
- })());
287
+ stream.pause();
288
+ try {
289
+ // UNLINK を使用し、現在のインスタンス (this) で実行
290
+ const unlinkResult = await this.unlink(...keys);
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
+ }
219
305
  }
220
306
  });
221
- stream.on('end', async () => {
222
- await Promise.all(tasks);
223
- resolve(deletedCount);
307
+ stream.once('end', async () => {
308
+ resolve(results);
224
309
  });
225
- stream.on('error', (err) => {
226
- console.error(logHeader, `Error during deleteKeys scan for pattern '${pattern}'.`, err);
310
+ stream.once('error', (err) => {
227
311
  reject(err);
228
312
  });
229
313
  });
230
314
  };
231
315
  // カスタムメソッド END
232
- // 接続が不安定な場合は以下のロジックが生きるかもしれないので残しておく
233
- // 動作確認した限りでは以下のロジックはなくても問題なく動作する
234
- // await new Promise<void>((resolve, reject) => {
235
- // const onReady = () => {
236
- // cleanup();
237
- // resolve();
238
- // };
239
- // const onError = (error: Error) => {
240
- // cleanup();
241
- // reject(error);
242
- // };
243
- // const cleanup = () => {
244
- // client.off('ready', onReady);
245
- // client.off('error', onError);
246
- // };
247
- // client.once('ready', onReady);
248
- // client.once('error', onError);
249
- // });
316
+ try {
317
+ await this.waitForReady(client);
318
+ }
319
+ catch (error) {
320
+ // 接続できなかったクライアントはプールに入れない。ioredisの再接続も止める
321
+ client.disconnect();
322
+ throw error;
323
+ }
250
324
  return client;
251
325
  },
252
326
  destroy: async (client) => {
253
327
  try {
254
328
  await client.quit();
255
- console.debug(logHeader, 'client quit');
329
+ this.debugLog(logHeader, 'client quit');
256
330
  }
257
331
  catch (error) {
258
332
  client.disconnect();
259
- console.debug(logHeader, 'client disconnected');
333
+ this.debugLog(logHeader, 'client disconnected');
260
334
  }
261
335
  },
262
336
  validate: async (client) => {
263
- try {
264
- console.debug(logHeader, 'start validate');
265
- const timeout = new Promise((_, reject) => {
266
- setTimeout(() => reject(new Error(`Redis PING timeout after ${REDIS_PING_TIMEOUT_MS}ms`)), REDIS_PING_TIMEOUT_MS);
267
- });
268
- await Promise.race([
269
- client.ping(),
270
- timeout
271
- ]);
272
- console.debug(logHeader, 'ping succeeded');
273
- return client.status === 'ready';
274
- }
275
- catch (error) {
276
- console.debug(logHeader, 'ping failed');
277
- return false;
278
- }
337
+ return await this.ping(client);
279
338
  }
280
339
  };
281
340
  return genericPool.createPool(factory, {
282
341
  max: this.max,
283
342
  min: this.min,
284
- testOnBorrow: this.testOnBorrow
343
+ testOnBorrow: this.testOnBorrow,
344
+ acquireTimeoutMillis: this.acquireTimeout
285
345
  });
286
346
  }
287
347
  }
@@ -1 +1 @@
1
- {"version":3,"file":"redis-pooling.js","sourceRoot":"","sources":["../src/redis-pooling.ts"],"names":[],"mappings":"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"}
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.1.0",
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": "^29.5.14",
37
+ "@types/jest": "^30.0.0",
38
38
  "ioredis-mock": "^8.13.1",
39
- "jest": "^29.7.0",
40
- "ts-jest": "^29.3.4",
39
+ "jest": "^30.3.0",
40
+ "ts-jest": "^29.4.9",
41
41
  "ts-node": "^10.9.2",
42
- "typescript": "^5.8.3"
42
+ "tsconfig-paths": "^4.2.0",
43
+ "typescript": "^6.0.3"
43
44
  },
44
45
  "dependencies": {
45
46
  "generic-pool": "^3.9.0",
@@ -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<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,14 +29,19 @@ 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
- private readonly enableTls?: boolean;
36
+ private readonly tls?: { rejectUnauthorized: false };
35
37
 
36
- private readonly poolMap = new Map<number, genericPool.Pool<RedisClient>>();
38
+ private pool?: genericPool.Pool<RedisClient>;
39
+ private initialized = false;
40
+ private readonly debug: boolean;
37
41
 
38
- constructor(config: RedisConfig) {
42
+ constructor(config: RedisConfig, options?: {
43
+ debug?: boolean;
44
+ }) {
39
45
  if (!config.url) {
40
46
  throw new Error(`${logHeader} Redis connection url is required.`);
41
47
  }
@@ -43,130 +49,198 @@ export class RedisPool {
43
49
  this.url = config.url;
44
50
  this.db = config.dbIndex ?? 0;
45
51
  this.connectTimeout = config.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT;
52
+ this.acquireTimeout = config.acquireTimeout ?? DEFAULT_ACQUIRE_TIMEOUT;
46
53
  this.max = config.max ?? DEFAULT_MAX_POOLING_SIZE;
47
54
  this.min = config.min ?? DEFAULT_MIN_POOLING_SIZE;
48
55
  this.testOnBorrow = config.testOnBorrow ?? true;
49
- this.enableTls = config.enableTls;
56
+ this.tls = config.enableTls ? { rejectUnauthorized: false } : undefined;
57
+
58
+ this.debug = options?.debug ?? false;
50
59
  }
51
60
 
52
61
  public async acquire(dbIndex?: number): Promise<RedisClient> {
62
+ if (!this.initialized) {
63
+ // 初回acquire呼び出し時のみ接続チェックを行う
64
+ // ホスト不正やパスワード不正による接続不可等を検知する
65
+ // ※ generic-poolのfactoryの方に入ってしまうとエラーを呼び出し元に伝播させることが難しいため、プールとは別の接続でチェックする
66
+ await this.checkConnectivity(dbIndex ?? this.db);
67
+ this.initialized = true;
68
+ }
69
+
53
70
  const index = dbIndex ?? this.db;
54
- const pool = this.getPool(index);
71
+ const pool = this.getPool();
55
72
  const client = await pool.acquire();
56
-
57
- console.debug(logHeader, `Redis client ${index} has been acquired.`);
73
+ try {
74
+ // プールの接続は、前の利用者が選択したDBを保持したまま返却される。そのため貸し出すたびに対象のDBを選択する
75
+ await client.select(index);
76
+ } catch (error) {
77
+ await pool.destroy(client);
78
+ throw error;
79
+ }
80
+ this.debugLog(logHeader, `Redis client ${index} has been acquired.`);
58
81
  return client;
59
82
  }
60
83
 
61
84
  public async release(client?: RedisClient): Promise<void> {
62
- if (client) {
63
- const dbIndex = client._originalDbIndex ?? this.db;
64
- const pool = this.poolMap.get(dbIndex);
65
- if (!pool) {
66
- return;
67
- }
68
- let needDestroy = false;
69
- switch (true) {
70
- case client.status === 'end':
71
- case client.status === 'close':
72
- // Redisクライアントの状態が再利用できない場合はプールから破棄
73
- needDestroy = true;
74
- break;
75
- case client.status === 'ready':
76
- try {
77
- // 元のDBインデックスに戻す
78
- await client.select(dbIndex);
79
- } catch (error) {
80
- // selectに失敗する→Redisクライアントが不正な状態にあると判断できるのでプールから破棄
81
- needDestroy = true;
82
- }
83
- break;
84
- default:
85
- }
86
- if (needDestroy) {
87
- // Redisクライアントの破棄
88
- await pool.destroy(client);
89
- console.debug(logHeader, `Redis client ${dbIndex} destroyed due to invalid status.`);
90
- } else {
91
- // Redisクライアントの返却
92
- await pool.release(client);
93
- console.debug(logHeader, `Redis client ${dbIndex} released.`);
94
- }
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.');
95
95
  }
96
96
  }
97
97
 
98
98
  public async destroy(timeoutMs = 5000): Promise<void> {
99
- for (const [dbIndex, pool] of this.poolMap.entries()) {
100
- console.debug(logHeader, `Destroying Redis pool for DB index ${dbIndex}...`);
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 {
101
106
  await Promise.race([
102
107
  (async () => {
103
108
  await pool.drain();
104
109
  await pool.clear();
105
110
  })(),
106
111
  new Promise<void>((_, reject) => {
107
- setTimeout(() => reject(new Error(`${logHeader} Timeout while draining Redis pool for DB index ${dbIndex}`)), timeoutMs);
112
+ timer = setTimeout(() => reject(new Error(`${logHeader} Timeout while draining Redis pool`)), timeoutMs);
108
113
  })
109
114
  ]);
110
- console.debug(logHeader, `Redis pool for DB index ${dbIndex} destroyed.`);
111
- this.poolMap.delete(dbIndex);
115
+ } finally {
116
+ clearTimeout(timer);
112
117
  }
118
+ this.debugLog(logHeader, 'Redis pool destroyed.');
119
+ this.pool = undefined;
113
120
  }
114
121
 
115
- private getPool(dbIndex: number): genericPool.Pool<RedisClient> {
116
- let pool = this.poolMap.get(dbIndex);
117
- if (!pool) {
118
- pool = this.createSingleDbPool(dbIndex);
119
- this.poolMap.set(dbIndex, pool);
122
+ /**
123
+ * 初めてacquireが呼ばれた時に、指定された接続情報で接続できるかチェックする
124
+ *
125
+ * @param {number} dbIndex
126
+ */
127
+ private async checkConnectivity(dbIndex: number): Promise<void> {
128
+ const client = new Redis(this.url, {
129
+ db: dbIndex,
130
+ retryStrategy: () => null, // 再接続無効
131
+ reconnectOnError: () => false, // 再接続無効
132
+ connectTimeout: this.connectTimeout,
133
+ tls: this.tls,
134
+ });
135
+
136
+ try {
137
+ await this.waitForReady(client);
138
+ } finally {
139
+ client.quit().catch(() => client.disconnect());
140
+ }
141
+ }
142
+
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 = () => {
154
+ cleanup();
155
+ resolve();
156
+ };
157
+ const onError = (error: Error) => {
158
+ cleanup();
159
+ reject(error);
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);
167
+ });
168
+ }
169
+
170
+ private async ping(client: Redis): Promise<boolean> {
171
+ try {
172
+ this.debugLog(logHeader, 'start validate');
173
+ const timeout = new Promise<void>((_, reject) => {
174
+ setTimeout(() => reject(new Error(`Redis PING timeout after ${REDIS_PING_TIMEOUT_MS}ms`)), REDIS_PING_TIMEOUT_MS);
175
+ });
176
+ await Promise.race([
177
+ client.ping(),
178
+ timeout
179
+ ]);
180
+ this.debugLog(logHeader, 'ping succeeded');
181
+ return client.status === 'ready';
182
+ } catch (error: any) {
183
+ this.debugLog(logHeader, 'ping failed');
184
+ return false;
185
+ }
186
+ }
187
+
188
+ public debugLog(...args: any[]): void {
189
+ if (this.debug) {
190
+ console.debug(...args);
120
191
  }
121
- return pool;
122
192
  }
123
193
 
124
- private createSingleDbPool(dbIndex: number): genericPool.Pool<RedisClient> {
194
+ private getPool(): genericPool.Pool<RedisClient> {
195
+ if (!this.pool) {
196
+ this.pool = this.createPool();
197
+ }
198
+ return this.pool;
199
+ }
200
+
201
+ private createPool(): genericPool.Pool<RedisClient> {
125
202
  const factory: genericPool.Factory<RedisClient> = {
126
203
  create: async (): Promise<RedisClient> => {
127
- let client;
128
- try {
129
- client = new Redis(this.url, {
130
- db: dbIndex,
131
- connectTimeout: this.connectTimeout,
132
- keepAlive: 1,
133
- enableOfflineQueue: true,
134
- tls: this.enableTls ? { rejectUnauthorized: false } : undefined,
135
- retryStrategy: (times) => {
136
- const delay = Math.min(times * 50, 1000);
137
- if (process.env.NODE_ENV !== 'test') {
138
- console.debug(logHeader, `retry strategy called ${times} times. delaying ${delay}ms`);
139
- }
140
- return delay;
141
- },
142
- reconnectOnError: (error) => {
143
- process.emitWarning(`${logHeader} detected error (on reconnect). ${error.message}`);
144
- return true;
204
+ const client = new Redis(this.url, {
205
+ db: this.db,
206
+ connectTimeout: this.connectTimeout,
207
+ keepAlive: 1,
208
+ enableOfflineQueue: true,
209
+ tls: this.tls,
210
+ retryStrategy: (times) => {
211
+ const delay = Math.min(times * 50, 1000);
212
+ if (process.env.NODE_ENV !== 'test') {
213
+ this.debugLog(logHeader, `retry strategy called ${times} times. delaying ${delay}ms`);
145
214
  }
146
- }) as RedisClient;
147
- } catch (error) {
148
- console.error(error);
149
- throw error;
150
- }
151
-
152
- client._originalDbIndex = dbIndex;
215
+ return delay;
216
+ },
217
+ reconnectOnError: (error) => {
218
+ process.emitWarning(`${logHeader} detected error (on reconnectOnError). ${error.message}`);
219
+ // フェイルオーバー後にレプリカへ接続したままになっている場合だけ、再接続で解消できる
220
+ return error.message.startsWith('READONLY');
221
+ }
222
+ }) as RedisClient;
153
223
  client.on('error', error => {
154
224
  process.emitWarning(`${logHeader} detected error (on error). ${error.message}`);
155
225
  });
156
226
 
227
+ // カスタムメソッド内の処理でthis.debugLogなどが参照できなくなるため、poolのインスタンスを変数キャプチャする
228
+ const poolInstance = this;
229
+
157
230
  // カスタムメソッド START
158
231
 
159
232
  /**
160
233
  * 指定されたパターンに一致するキーを Redis から全て取得する
161
234
  *
162
235
  * @param {string} pattern
236
+ * @param {number} [count] 1度にスキャンする件数 デフォルト:1000
163
237
  * @returns {Promise<string[]>}
164
238
  */
165
- client.getKeys = async function(pattern: string): Promise<string[]> {
239
+ client.getKeys = async function(pattern: string, count?: number): Promise<string[]> {
166
240
  const allKeys: string[] = [];
167
241
  const stream = this.scanStream({
168
242
  match: pattern,
169
- count: 1000 // ioredisが1度にスキャンする件数の目安。1000件を超えるデータがあっても全件返却される
243
+ count: count ?? 1000
170
244
  });
171
245
 
172
246
  return new Promise((resolve, reject) => {
@@ -175,107 +249,98 @@ export class RedisPool {
175
249
  allKeys.push(...keys);
176
250
  }
177
251
  });
178
- stream.on('end', () => resolve(allKeys));
179
- stream.on('error', (err: Error) => {
180
- console.error(logHeader, `Error during getKeys scan for pattern '${pattern}'.`, err);
252
+ stream.once('end', () => resolve(allKeys));
253
+ stream.once('error', (err: Error) => {
181
254
  reject(err);
182
255
  });
183
256
  });
184
257
  };
185
258
  /**
186
259
  * 指定されたパターンに一致するキーを Redis から全て削除する (UNLINKを使用)
260
+ * 返却値の配列サイズはscanStreamが'data'を受信した回数で、この受信したデータで削除された件数がvalueに設定されている。
261
+ *
262
+ * [
263
+ * { status: 'fulfilled', value: 100 }, // 1バッチ目で100件削除
264
+ * { status: 'fulfilled', value: 80 } // 2バッチ目で80件削除
265
+ * ]
266
+ *
267
+ * 成功した件数は以下のようにして取得可能
268
+ *
269
+ * const delResults = await redisClient.deleteKeys('pattern');
270
+ * const delCount = delResults.filter(a => a.status === 'fulfilled')
271
+ * .reduce((acc, cur) => acc + (cur as PromiseFulfilledResult<number>).value, 0);
187
272
  *
188
273
  * @param {string} pattern
189
- * @returns {Promise<number>}
274
+ * @param {number} [count] 1度にスキャンする件数 デフォルト:1000
275
+ * @returns {Promise<PromiseSettledResult<number>>}
190
276
  */
191
- client.deleteKeys = async function(pattern: string): Promise<number> {
192
- let deletedCount = 0;
277
+ client.deleteKeys = async function(pattern: string, count?: number): Promise<PromiseSettledResult<number>[]> {
193
278
  const stream = this.scanStream({
194
279
  match: pattern,
195
- count: 1000 // 1度にスキャンする件数
280
+ count: count ?? 1000
196
281
  });
282
+ const results: PromiseSettledResult<number>[] = [];
197
283
 
198
284
  return new Promise((resolve, reject) => {
199
- const tasks: Promise<void>[] = [];
200
285
  stream.on('data', async (keys: string[]) => {
201
286
  if (keys.length) {
202
- tasks.push((async () => {
203
- try {
204
- // UNLINK を使用し、現在のインスタンス (this) で実行
205
- const unlinkResult = await this.unlink(...keys);
206
- deletedCount += unlinkResult;
207
- console.debug(logHeader, `Deleted ${unlinkResult} keys in a batch for pattern '${pattern}'. Total: ${deletedCount}`);
208
- } catch (error: any) {
209
- process.emitWarning(`${logHeader} Error during UNLINK for pattern '${pattern}'. ${error.message}`);
210
- }
211
- })());
287
+ stream.pause();
288
+ try {
289
+ // UNLINK を使用し、現在のインスタンス (this) で実行
290
+ const unlinkResult = await this.unlink(...keys);
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
+ }
212
303
  }
213
304
  });
214
- stream.on('end', async () => {
215
- await Promise.all(tasks);
216
- resolve(deletedCount);
305
+ stream.once('end', async () => {
306
+ resolve(results);
217
307
  });
218
- stream.on('error', (err: Error) => {
219
- console.error(logHeader, `Error during deleteKeys scan for pattern '${pattern}'.`, err);
308
+ stream.once('error', (err: Error) => {
220
309
  reject(err);
221
310
  });
222
311
  });
223
312
  };
224
313
  // カスタムメソッド END
225
314
 
226
- // 接続が不安定な場合は以下のロジックが生きるかもしれないので残しておく
227
- // 動作確認した限りでは以下のロジックはなくても問題なく動作する
228
- // await new Promise<void>((resolve, reject) => {
229
- // const onReady = () => {
230
- // cleanup();
231
- // resolve();
232
- // };
233
- // const onError = (error: Error) => {
234
- // cleanup();
235
- // reject(error);
236
- // };
237
- // const cleanup = () => {
238
- // client.off('ready', onReady);
239
- // client.off('error', onError);
240
- // };
241
- // client.once('ready', onReady);
242
- // client.once('error', onError);
243
- // });
315
+ try {
316
+ await this.waitForReady(client);
317
+ } catch (error) {
318
+ // 接続できなかったクライアントはプールに入れない。ioredisの再接続も止める
319
+ client.disconnect();
320
+ throw error;
321
+ }
244
322
 
245
323
  return client;
246
324
  },
247
325
  destroy: async (client: Redis) => {
248
326
  try {
249
327
  await client.quit();
250
- console.debug(logHeader, 'client quit');
328
+ this.debugLog(logHeader, 'client quit');
251
329
  } catch (error) {
252
330
  client.disconnect();
253
- console.debug(logHeader, 'client disconnected');
331
+ this.debugLog(logHeader, 'client disconnected');
254
332
  }
255
333
  },
256
334
  validate: async (client: Redis) => {
257
- try {
258
- console.debug(logHeader, 'start validate');
259
- const timeout = new Promise<void>((_, reject) => {
260
- setTimeout(() => reject(new Error(`Redis PING timeout after ${REDIS_PING_TIMEOUT_MS}ms`)), REDIS_PING_TIMEOUT_MS);
261
- });
262
- await Promise.race([
263
- client.ping(),
264
- timeout
265
- ]);
266
- console.debug(logHeader, 'ping succeeded');
267
- return client.status === 'ready';
268
- } catch (error) {
269
- console.debug(logHeader, 'ping failed');
270
- return false;
271
- }
335
+ return await this.ping(client);
272
336
  }
273
337
  };
274
338
 
275
339
  return genericPool.createPool(factory, {
276
340
  max: this.max,
277
341
  min: this.min,
278
- testOnBorrow: this.testOnBorrow
342
+ testOnBorrow: this.testOnBorrow,
343
+ acquireTimeoutMillis: this.acquireTimeout
279
344
  });
280
345
  }
281
346
  }