@digitalwalletcorp/redis-pooling 1.0.1 → 1.1.1

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
@@ -10,10 +10,10 @@ Designed for both server-side Node.js applications and cron-style background job
10
10
  * **Connection Pooling**: Efficiently manage multiple Redis connections with `min` and `max` pool size configuration.
11
11
  * **Safe Acquire/Release**: Guaranteed handling of Redis client lifecycle with optional database selection (`SELECT dbIndex`).
12
12
  * **Custom Utilities**: Built-in helper methods like `getKeys(pattern)` and `deleteKeys(pattern)` for convenient Redis key operations.
13
- * **Connection Validation**: PING-based validation before borrowing from the pool.
13
+ * **Connection Validation**: PING-based validation with timeout before borrowing from the pool.
14
14
  * **TLS Support**: Optional TLS configuration for secure Redis connections.
15
15
 
16
- #### 📦 Instllation
16
+ #### 📦 Installation
17
17
 
18
18
  ```bash
19
19
  npm install @digitalwalletcorp/redis-pooling
@@ -28,15 +28,13 @@ yarn add @digitalwalletcorp/redis-pooling
28
28
  This pattern is ideal for short-lived Node.js scripts, such as cron jobs, where a pool is created, used, and destroyed within a single run.
29
29
 
30
30
  ```typescript
31
- import { createRedisPool } from '@digitalwalletcorp/redis-pooling';
31
+ import { RedisPool } from '@digitalwalletcorp/redis-pooling';
32
32
 
33
- const redisPool = createRedisPool({
33
+ const redisPool = new RedisPool({
34
34
  url: 'redis://localhost:6379',
35
- db: 0,
35
+ dbIndex: 0,
36
36
  max: 10,
37
- min: 2,
38
- connectTimeout: 5000,
39
- enableTls: false
37
+ min: 2
40
38
  });
41
39
 
42
40
  async function main() {
@@ -60,15 +58,13 @@ For long-running web applications, a singleton Redis pool ensures efficient reus
60
58
 
61
59
  `@/server/singleton/redis-pooling`
62
60
  ```typescript
63
- import { createRedisPool } from '@digitalwalletcorp/redis-pooling';
61
+ import { RedisPool } from '@digitalwalletcorp/redis-pooling';
64
62
 
65
- export const redisPool = createRedisPool({
63
+ export const redisPool = new RedisPool({
66
64
  url: 'redis://localhost:6379',
67
- db: 0,
65
+ dbIndex: 0,
68
66
  max: 20,
69
- min: 5,
70
- connectTimeout: 5000,
71
- enableTls: false
67
+ min: 5
72
68
  });
73
69
  ```
74
70
 
@@ -89,7 +85,7 @@ async function handleRequest() {
89
85
 
90
86
  ##### Example 3: Using Custom Methods (`getKeys`, `deleteKeys`)
91
87
 
92
- `ManagedRedisClient` extends the standard `ioredis` client with convenient helper methods.
88
+ `RedisClient` extends the standard `ioredis` client with convenient helper methods.
93
89
  `getKeys` and `deleteKeys` use `SCAN` and `UNLINK` internally to avoid blocking Redis with large datasets (unlike `KEYS`).
94
90
  All other standard Redis commands remain available.
95
91
 
@@ -105,8 +101,10 @@ async function manageCache() {
105
101
  console.log('Matching keys:', keys);
106
102
 
107
103
  // Delete all matching keys using UNLINK
108
- const deleted = await client.deleteKeys('cache:*');
109
- 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`);
110
108
  } finally {
111
109
  await redisPool.release(client);
112
110
  }
@@ -115,35 +113,59 @@ async function manageCache() {
115
113
 
116
114
  #### 📚 API Reference
117
115
 
118
- ##### `createRedisPool(config: RedisConfig): ManagedRedisPool`
116
+ ##### `new RedisPool(config: RedisConfig)`
119
117
 
120
- Creates a managed Redis pool.
118
+ Creates a Redis connection pool.
121
119
 
122
120
  | Property | Type | Default | Description |
123
121
  | ---------------- | ------- | -------- | -------------------------------------- |
124
122
  | `url` | string | required | Redis connection URL. |
125
- | `db` | number | 0 | Default Redis database index. |
126
- | `connectTimeout` | number | 5000 | Connection timeout in milliseconds. |
123
+ | `dbIndex` | number | 0 | Default Redis database index. |
127
124
  | `max` | number | 10 | Maximum number of clients in the pool. |
128
125
  | `min` | number | 0 | Minimum number of clients in the pool. |
126
+ | `connectTimeout` | number | 5000 | Connection timeout in milliseconds. |
127
+ | `testOnBorrow` | boolean | true | Enable connection validate on borrow. |
129
128
  | `enableTls` | boolean | false | Enable TLS for Redis connection. |
130
129
 
131
- ##### `ManagedRedisPool` Methods
130
+ ##### `RedisPool` Methods
132
131
 
133
132
  | Method | Signature | Description |
134
133
  | ------------------------------------- | ----------------------------- | -------------------------------------------------------------------------------------- |
135
- | `acquire(dbIndex?: number)` | `Promise<ManagedRedisClient>` | Acquire a Redis client from the pool. Optionally switch to a different database index. |
136
- | `release(client: ManagedRedisClient)` | `Promise<void>` | Release the Redis client back to the pool. |
137
- | `destroy()` | `Promise<void>` | Drain and clear all connections in the pool. |
134
+ | `acquire(dbIndex?: number)` | `Promise<RedisClient>` | Acquire a Redis client from the pool. Optionally switch to a different database index. |
135
+ | `release(client: RedisClient)` | `Promise<void>` | Release the Redis client back to the pool. |
136
+ | `destroy()` | `Promise<void>` | Drain and clear all connections in the pool. |
138
137
 
139
- ##### `ManagedRedisClient` Methods
138
+ ##### `RedisClient` Methods
140
139
 
141
140
  Extends the standard `ioredis` `Redis` client with additional helpers:
142
141
 
143
142
  | Method | Signature | Description |
144
143
  | ----------------------------- | ------------------- | ----------------------------------------------------------------------------------------------- |
145
144
  | `getKeys(pattern: string)` | `Promise<string[]>` | Scan and return all keys matching a pattern. |
146
- | `deleteKeys(pattern: string)` | `Promise<number>` | Scan and delete all keys matching a pattern using `UNLINK`. Returns the number of deleted keys. |
145
+ | `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. |
146
+
147
+ #### 🗄 Database Index Handling
148
+
149
+ This library provides a Redis connection pool capable of managing Redis clients
150
+ connected to different database indexes (`SELECT db`).
151
+
152
+ **Calling `acquire(dbIndex)`:**
153
+
154
+ > When `dbIndex` is specified, the acquired client will be connected to the specified database index.
155
+
156
+ **Calling `acquire()` without `dbIndex`:**
157
+ > When `dbIndex` is not specified, the acquired client will be connected to the default database index configured when the pool was created via **createRedisPool({ dbIndex })**.
158
+
159
+ ```typescript
160
+ // The default db index: 2
161
+ const pool = new RedisPool({ url, dbIndex: 2 });
162
+
163
+ // With dbIndex: connected to DB 1
164
+ const client1 = await pool.acquire(1);
165
+
166
+ // Without dbIndex: connected to DB 2 (default)
167
+ const client2 = await pool.acquire();
168
+ ```
147
169
 
148
170
  ---
149
171
  #### 💡 Notes
@@ -1,27 +1,43 @@
1
1
  import { Redis } from 'ioredis';
2
2
  export interface RedisConfig {
3
3
  url: string;
4
- db?: number;
4
+ dbIndex?: number;
5
5
  connectTimeout?: number;
6
6
  max?: number;
7
7
  min?: number;
8
8
  testOnBorrow?: boolean;
9
9
  enableTls?: boolean;
10
10
  }
11
- export interface ManagedRedisPool {
12
- acquire(dbIndex?: number): Promise<ManagedRedisClient>;
13
- release(client?: Redis): Promise<void>;
14
- destroy(timeoutMs?: number): Promise<void>;
15
- }
16
- export interface ManagedRedisClient extends Redis {
11
+ export interface RedisClient extends Redis {
17
12
  getKeys(pattern: string): Promise<string[]>;
18
- deleteKeys(pattern: string): Promise<number>;
13
+ deleteKeys(pattern: string): Promise<PromiseSettledResult<number>[]>;
19
14
  _originalDbIndex?: number;
20
15
  }
21
- /**
22
- * Redisコネクションプーリングを生成する
23
- *
24
- * @param {RedisConfig} config
25
- * @returns {ManagedRedisPool}
26
- */
27
- export declare const createRedisPool: (config: RedisConfig) => ManagedRedisPool;
16
+ export declare class RedisPool {
17
+ private readonly url;
18
+ private readonly db;
19
+ private readonly connectTimeout;
20
+ private readonly max;
21
+ private readonly min;
22
+ private readonly testOnBorrow;
23
+ private readonly tls?;
24
+ private readonly poolMap;
25
+ private initialized;
26
+ private readonly debug;
27
+ constructor(config: RedisConfig, options?: {
28
+ debug?: boolean;
29
+ });
30
+ acquire(dbIndex?: number): Promise<RedisClient>;
31
+ release(client?: RedisClient): Promise<void>;
32
+ destroy(timeoutMs?: number): Promise<void>;
33
+ /**
34
+ * 初めてacquireが呼ばれた時に、指定された接続情報で接続できるかチェックする
35
+ *
36
+ * @param {number} dbIndex
37
+ */
38
+ private checkConnectivity;
39
+ private ping;
40
+ debugLog(...args: any[]): void;
41
+ private getPool;
42
+ private createSingleDbPool;
43
+ }
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.createRedisPool = void 0;
36
+ exports.RedisPool = void 0;
37
37
  const ioredis_1 = require("ioredis");
38
38
  const genericPool = __importStar(require("generic-pool"));
39
39
  const logHeader = '[RedisPooling]';
@@ -41,56 +41,188 @@ const DEFAULT_CONNECT_TIMEOUT = 5000;
41
41
  const DEFAULT_MAX_POOLING_SIZE = 10;
42
42
  const DEFAULT_MIN_POOLING_SIZE = 0;
43
43
  const REDIS_PING_TIMEOUT_MS = 3000; // 3秒
44
- /**
45
- * Redisコネクションプーリングを生成する
46
- *
47
- * @param {RedisConfig} config
48
- * @returns {ManagedRedisPool}
49
- */
50
- const createRedisPool = (config) => {
51
- const url = config.url;
52
- const db = config.db ?? 0;
53
- const connectTimeout = config.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT;
54
- const max = config.max ?? DEFAULT_MAX_POOLING_SIZE;
55
- const min = config.min ?? DEFAULT_MIN_POOLING_SIZE;
56
- const testOnBorrow = config.testOnBorrow ?? true;
57
- if (!url) {
58
- throw new Error(`${logHeader} Redis connection url is required.`);
44
+ class RedisPool {
45
+ url;
46
+ db;
47
+ connectTimeout;
48
+ max;
49
+ min;
50
+ testOnBorrow;
51
+ tls;
52
+ poolMap = new Map();
53
+ initialized = false;
54
+ debug;
55
+ constructor(config, options) {
56
+ if (!config.url) {
57
+ throw new Error(`${logHeader} Redis connection url is required.`);
58
+ }
59
+ this.url = config.url;
60
+ this.db = config.dbIndex ?? 0;
61
+ this.connectTimeout = config.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT;
62
+ this.max = config.max ?? DEFAULT_MAX_POOLING_SIZE;
63
+ this.min = config.min ?? DEFAULT_MIN_POOLING_SIZE;
64
+ this.testOnBorrow = config.testOnBorrow ?? true;
65
+ this.tls = config.enableTls ? { rejectUnauthorized: false } : undefined;
66
+ this.debug = options?.debug ?? false;
59
67
  }
60
- // dbIndexごとのプール管理
61
- const poolMap = new Map();
62
- const createSingleDbPool = (dbIndex) => {
68
+ async acquire(dbIndex) {
69
+ if (!this.initialized) {
70
+ // 初回acquire呼び出し時のみ接続チェックを行う
71
+ // ホスト不正やパスワード不正による接続不可等を検知する
72
+ // ※ generic-poolのfactoryの方に入ってしまうとエラーを呼び出し元に伝播させることが難しいため、プールとは別の接続でチェックする
73
+ await this.checkConnectivity(dbIndex ?? this.db);
74
+ this.initialized = true;
75
+ }
76
+ const index = dbIndex ?? this.db;
77
+ const pool = this.getPool(index);
78
+ try {
79
+ const client = await pool.acquire();
80
+ this.debugLog(logHeader, `Redis client ${index} has been acquired.`);
81
+ return client;
82
+ }
83
+ catch (error) {
84
+ throw error;
85
+ }
86
+ }
87
+ async release(client) {
88
+ if (client) {
89
+ const dbIndex = client._originalDbIndex ?? this.db;
90
+ const pool = this.poolMap.get(dbIndex);
91
+ if (!pool) {
92
+ return;
93
+ }
94
+ let needDestroy = false;
95
+ switch (true) {
96
+ case client.status === 'end':
97
+ case client.status === 'close':
98
+ // Redisクライアントの状態が再利用できない場合はプールから破棄
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
+ }
123
+ }
124
+ }
125
+ async destroy(timeoutMs = 5000) {
126
+ for (const [dbIndex, pool] of this.poolMap.entries()) {
127
+ this.debugLog(logHeader, `Destroying Redis pool for DB index ${dbIndex}...`);
128
+ await Promise.race([
129
+ (async () => {
130
+ await pool.drain();
131
+ await pool.clear();
132
+ })(),
133
+ new Promise((_, reject) => {
134
+ setTimeout(() => reject(new Error(`${logHeader} Timeout while draining Redis pool for DB index ${dbIndex}`)), timeoutMs);
135
+ })
136
+ ]);
137
+ this.debugLog(logHeader, `Redis pool for DB index ${dbIndex} destroyed.`);
138
+ this.poolMap.delete(dbIndex);
139
+ }
140
+ }
141
+ /**
142
+ * 初めてacquireが呼ばれた時に、指定された接続情報で接続できるかチェックする
143
+ *
144
+ * @param {number} dbIndex
145
+ */
146
+ async checkConnectivity(dbIndex) {
147
+ const client = new ioredis_1.Redis(this.url, {
148
+ db: dbIndex,
149
+ retryStrategy: () => null, // 再接続無効
150
+ reconnectOnError: () => false, // 再接続無効
151
+ connectTimeout: this.connectTimeout,
152
+ tls: this.tls,
153
+ });
154
+ return new Promise((resolve, reject) => {
155
+ const cleanup = () => {
156
+ client.quit().catch(() => client.disconnect());
157
+ };
158
+ client.once('ready', () => {
159
+ cleanup();
160
+ resolve();
161
+ });
162
+ client.once('error', (error) => {
163
+ cleanup();
164
+ reject(error);
165
+ });
166
+ });
167
+ }
168
+ async ping(client) {
169
+ try {
170
+ this.debugLog(logHeader, 'start validate');
171
+ const timeout = new Promise((_, reject) => {
172
+ setTimeout(() => reject(new Error(`Redis PING timeout after ${REDIS_PING_TIMEOUT_MS}ms`)), REDIS_PING_TIMEOUT_MS);
173
+ });
174
+ await Promise.race([
175
+ client.ping(),
176
+ timeout
177
+ ]);
178
+ this.debugLog(logHeader, 'ping succeeded');
179
+ return client.status === 'ready';
180
+ }
181
+ catch (error) {
182
+ this.debugLog(logHeader, 'ping failed');
183
+ return false;
184
+ }
185
+ }
186
+ debugLog(...args) {
187
+ if (this.debug) {
188
+ console.debug(...args);
189
+ }
190
+ }
191
+ getPool(dbIndex) {
192
+ let pool = this.poolMap.get(dbIndex);
193
+ if (!pool) {
194
+ pool = this.createSingleDbPool(dbIndex);
195
+ this.poolMap.set(dbIndex, pool);
196
+ }
197
+ return pool;
198
+ }
199
+ createSingleDbPool(dbIndex) {
63
200
  const factory = {
64
201
  create: async () => {
65
- let client;
66
- try {
67
- client = new ioredis_1.Redis(url, {
68
- db: dbIndex,
69
- connectTimeout: connectTimeout,
70
- keepAlive: 1,
71
- enableOfflineQueue: true,
72
- tls: config.enableTls ? { rejectUnauthorized: false } : undefined,
73
- retryStrategy: (times) => {
74
- const delay = Math.min(times * 50, 1000);
75
- if (process.env.NODE_ENV !== 'test') {
76
- console.debug(logHeader, `retry strategy called ${times} times. delaying ${delay}ms`);
77
- }
78
- return delay;
79
- },
80
- reconnectOnError: (error) => {
81
- process.emitWarning(`${logHeader} detected error (on reconnect). ${error.message}`);
82
- return true;
202
+ const client = new ioredis_1.Redis(this.url, {
203
+ db: dbIndex,
204
+ connectTimeout: this.connectTimeout,
205
+ keepAlive: 1,
206
+ enableOfflineQueue: true,
207
+ tls: this.tls,
208
+ retryStrategy: (times) => {
209
+ const delay = Math.min(times * 50, 1000);
210
+ if (process.env.NODE_ENV !== 'test') {
211
+ this.debugLog(logHeader, `retry strategy called ${times} times. delaying ${delay}ms`);
83
212
  }
84
- });
85
- }
86
- catch (error) {
87
- console.error(error);
88
- throw error;
89
- }
213
+ return delay;
214
+ },
215
+ reconnectOnError: (error) => {
216
+ process.emitWarning(`${logHeader} detected error (on reconnectOnError). ${error.message}`);
217
+ return true;
218
+ }
219
+ });
90
220
  client._originalDbIndex = dbIndex;
91
221
  client.on('error', error => {
92
222
  process.emitWarning(`${logHeader} detected error (on error). ${error.message}`);
93
223
  });
224
+ // カスタムメソッド内の処理でthis.debugLogなどが参照できなくなるため、poolのインスタンスを変数キャプチャする
225
+ const poolInstance = this;
94
226
  // カスタムメソッド START
95
227
  /**
96
228
  * 指定されたパターンに一致するキーを Redis から全て取得する
@@ -112,19 +244,29 @@ const createRedisPool = (config) => {
112
244
  });
113
245
  stream.on('end', () => resolve(allKeys));
114
246
  stream.on('error', (err) => {
115
- console.error(logHeader, `Error during getKeys scan for pattern '${pattern}'.`, err);
116
247
  reject(err);
117
248
  });
118
249
  });
119
250
  };
120
251
  /**
121
252
  * 指定されたパターンに一致するキーを Redis から全て削除する (UNLINKを使用)
253
+ * 返却値の配列サイズはscanStreamが'data'を受信した回数で、この受信したデータで削除された件数がvalueに設定されている。
254
+ *
255
+ * [
256
+ * { status: 'fulfilled', value: 100 }, // 1バッチ目で100件削除
257
+ * { status: 'fulfilled', value: 80 } // 2バッチ目で80件削除
258
+ * ]
259
+ *
260
+ * 成功した件数は以下のようにして取得可能
261
+ *
262
+ * const delResults = await redisClient.deleteKeys('pattern');
263
+ * const delCount = delResults.filter(a => a.status === 'fulfilled')
264
+ * .reduce((acc, cur) => acc + (cur as PromiseFulfilledResult<number>).value, 0);
122
265
  *
123
266
  * @param {string} pattern
124
- * @returns {Promise<number>}
267
+ * @returns {Promise<PromiseSettledResult<number>>}
125
268
  */
126
269
  client.deleteKeys = async function (pattern) {
127
- let deletedCount = 0;
128
270
  const stream = this.scanStream({
129
271
  match: pattern,
130
272
  count: 1000 // 1度にスキャンする件数
@@ -134,24 +276,17 @@ const createRedisPool = (config) => {
134
276
  stream.on('data', async (keys) => {
135
277
  if (keys.length) {
136
278
  tasks.push((async () => {
137
- try {
138
- // UNLINK を使用し、現在のインスタンス (this) で実行
139
- const unlinkResult = await this.unlink(...keys);
140
- deletedCount += unlinkResult;
141
- console.debug(logHeader, `Deleted ${unlinkResult} keys in a batch for pattern '${pattern}'. Total: ${deletedCount}`);
142
- }
143
- catch (error) {
144
- process.emitWarning(`${logHeader} Error during UNLINK for pattern '${pattern}'. ${error.message}`);
145
- }
279
+ // UNLINK を使用し、現在のインスタンス (this) で実行
280
+ const unlinkResult = await this.unlink(...keys);
281
+ return unlinkResult;
146
282
  })());
147
283
  }
148
284
  });
149
285
  stream.on('end', async () => {
150
- await Promise.all(tasks);
151
- resolve(deletedCount);
286
+ const results = await Promise.allSettled(tasks);
287
+ resolve(results);
152
288
  });
153
289
  stream.on('error', (err) => {
154
- console.error(logHeader, `Error during deleteKeys scan for pattern '${pattern}'.`, err);
155
290
  reject(err);
156
291
  });
157
292
  });
@@ -180,108 +315,23 @@ const createRedisPool = (config) => {
180
315
  destroy: async (client) => {
181
316
  try {
182
317
  await client.quit();
183
- console.debug(logHeader, 'client quit');
318
+ this.debugLog(logHeader, 'client quit');
184
319
  }
185
320
  catch (error) {
186
321
  client.disconnect();
187
- console.debug(logHeader, 'client disconnected');
322
+ this.debugLog(logHeader, 'client disconnected');
188
323
  }
189
324
  },
190
325
  validate: async (client) => {
191
- try {
192
- console.debug(logHeader, 'start validate');
193
- const timeout = new Promise((_, reject) => {
194
- setTimeout(() => reject(new Error(`Redis PING timeout after ${REDIS_PING_TIMEOUT_MS}ms`)), REDIS_PING_TIMEOUT_MS);
195
- });
196
- await Promise.race([
197
- client.ping(),
198
- timeout
199
- ]);
200
- console.debug(logHeader, 'ping succeeded');
201
- return client.status === 'ready';
202
- }
203
- catch (error) {
204
- console.debug(logHeader, 'ping failed');
205
- return false;
206
- }
326
+ return await this.ping(client);
207
327
  }
208
328
  };
209
329
  return genericPool.createPool(factory, {
210
- max,
211
- min,
212
- testOnBorrow
330
+ max: this.max,
331
+ min: this.min,
332
+ testOnBorrow: this.testOnBorrow
213
333
  });
214
- };
215
- const getPool = (dbIndex) => {
216
- let pool = poolMap.get(dbIndex);
217
- if (!pool) {
218
- pool = createSingleDbPool(dbIndex);
219
- poolMap.set(dbIndex, pool);
220
- }
221
- return pool;
222
- };
223
- return {
224
- acquire: async (dbIndex) => {
225
- const index = dbIndex ?? 0;
226
- const pool = getPool(index);
227
- const client = await pool.acquire();
228
- console.debug(logHeader, `Redis client ${index} has been acquired.`);
229
- return client;
230
- },
231
- release: async (client) => {
232
- if (client) {
233
- const dbIndex = client._originalDbIndex ?? db;
234
- let needDestroy = false;
235
- switch (true) {
236
- case client.status === 'end':
237
- case client.status === 'close':
238
- // Redisクライアントの状態が再利用できない場合はプールから破棄
239
- needDestroy = true;
240
- break;
241
- case client.status === 'ready':
242
- try {
243
- // 元のDBインデックスに戻す
244
- await client.select(dbIndex);
245
- }
246
- catch (error) {
247
- // selectに失敗する→Redisクライアントが不正な状態にあると判断できるのでプールから破棄
248
- needDestroy = true;
249
- }
250
- break;
251
- default:
252
- }
253
- const pool = poolMap.get(dbIndex);
254
- if (pool) {
255
- if (needDestroy) {
256
- // Redisクライアントの破棄
257
- await pool.destroy(client);
258
- console.debug(logHeader, `Redis client ${dbIndex} destroyed due to invalid status.`);
259
- }
260
- else {
261
- // Redisクライアントの返却
262
- await pool.release(client);
263
- console.debug(logHeader, `Redis client ${dbIndex} released.`);
264
- }
265
- }
266
- }
267
- },
268
- destroy: async (timeoutMs = 5000) => {
269
- for (const [dbIndex, pool] of poolMap.entries()) {
270
- console.debug(logHeader, `Destroying Redis pool for DB index ${dbIndex}...`);
271
- await Promise.race([
272
- (async () => {
273
- await pool.drain();
274
- await pool.clear();
275
- })(),
276
- new Promise((_, reject) => {
277
- setTimeout(() => reject(new Error(`${logHeader} Timeout while draining Redis pool for DB index ${dbIndex}`)), timeoutMs);
278
- })
279
- ]);
280
- console.debug(logHeader, `Redis pool for DB index ${dbIndex} destroyed.`);
281
- poolMap.delete(dbIndex);
282
- }
283
- }
284
- };
285
- };
286
- exports.createRedisPool = createRedisPool;
334
+ }
335
+ }
336
+ exports.RedisPool = RedisPool;
287
337
  //# sourceMappingURL=redis-pooling.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"redis-pooling.js","sourceRoot":"","sources":["../src/redis-pooling.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,qCAAgC;AAChC,0DAA4C;AAwB5C,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;;;;;GAKG;AACI,MAAM,eAAe,GAAG,CAAC,MAAmB,EAAoB,EAAE;IAEvE,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC;IACvB,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC;IAC1B,MAAM,cAAc,GAAG,MAAM,CAAC,cAAc,IAAI,uBAAuB,CAAC;IACxE,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,IAAI,wBAAwB,CAAC;IACnD,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,IAAI,wBAAwB,CAAC;IACnD,MAAM,YAAY,GAAG,MAAM,CAAC,YAAY,IAAI,IAAI,CAAC;IAEjD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,GAAG,SAAS,oCAAoC,CAAC,CAAC;IACpE,CAAC;IAED,kBAAkB;IAClB,MAAM,OAAO,GAAG,IAAI,GAAG,EAAgD,CAAC;IAExE,MAAM,kBAAkB,GAAG,CAAC,OAAe,EAAwC,EAAE;QACnF,MAAM,OAAO,GAA4C;YACvD,MAAM,EAAE,KAAK,IAAiC,EAAE;gBAC9C,IAAI,MAAM,CAAC;gBACX,IAAI,CAAC;oBACH,MAAM,GAAG,IAAI,eAAK,CAAC,GAAG,EAAE;wBACtB,EAAE,EAAE,OAAO;wBACX,cAAc,EAAE,cAAc;wBAC9B,SAAS,EAAE,CAAC;wBACZ,kBAAkB,EAAE,IAAI;wBACxB,GAAG,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS;wBACjE,aAAa,EAAE,CAAC,KAAK,EAAE,EAAE;4BACvB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;4BACzC,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;gCACpC,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,yBAAyB,KAAK,oBAAoB,KAAK,IAAI,CAAC,CAAC;4BACxF,CAAC;4BACD,OAAO,KAAK,CAAC;wBACf,CAAC;wBACD,gBAAgB,EAAE,CAAC,KAAK,EAAE,EAAE;4BAC1B,OAAO,CAAC,WAAW,CAAC,GAAG,SAAS,mCAAmC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;4BACpF,OAAO,IAAI,CAAC;wBACd,CAAC;qBACF,CAAuB,CAAC;gBAC3B,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;oBACrB,MAAM,KAAK,CAAC;gBACd,CAAC;gBAED,MAAM,CAAC,gBAAgB,GAAG,OAAO,CAAC;gBAClC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE;oBACzB,OAAO,CAAC,WAAW,CAAC,GAAG,SAAS,+BAA+B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;gBAClF,CAAC,CAAC,CAAC;gBAEH,iBAAiB;gBAEjB;;;;;mBAKG;gBACH,MAAM,CAAC,OAAO,GAAG,KAAK,WAAU,OAAe;oBAC7C,MAAM,OAAO,GAAa,EAAE,CAAC;oBAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC;wBAC7B,KAAK,EAAE,OAAO;wBACd,KAAK,EAAE,IAAI,CAAC,kDAAkD;qBAC/D,CAAC,CAAC;oBAEH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;wBACrC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAc,EAAE,EAAE;4BACnC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gCAChB,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;4BACxB,CAAC;wBACH,CAAC,CAAC,CAAC;wBACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;wBACzC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;4BAChC,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,0CAA0C,OAAO,IAAI,EAAE,GAAG,CAAC,CAAC;4BACrF,MAAM,CAAC,GAAG,CAAC,CAAC;wBACd,CAAC,CAAC,CAAC;oBACL,CAAC,CAAC,CAAC;gBACL,CAAC,CAAC;gBACF;;;;;mBAKG;gBACH,MAAM,CAAC,UAAU,GAAG,KAAK,WAAU,OAAe;oBAChD,IAAI,YAAY,GAAG,CAAC,CAAC;oBACrB,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC;wBAC7B,KAAK,EAAE,OAAO;wBACd,KAAK,EAAE,IAAI,CAAC,cAAc;qBAC3B,CAAC,CAAC;oBAEH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;wBACrC,MAAM,KAAK,GAAoB,EAAE,CAAC;wBAClC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,IAAc,EAAE,EAAE;4BACzC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gCAChB,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE;oCACrB,IAAI,CAAC;wCACH,mCAAmC;wCACnC,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;wCAChD,YAAY,IAAI,YAAY,CAAC;wCAC7B,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,WAAW,YAAY,iCAAiC,OAAO,aAAa,YAAY,EAAE,CAAC,CAAC;oCACvH,CAAC;oCAAC,OAAO,KAAU,EAAE,CAAC;wCACpB,OAAO,CAAC,WAAW,CAAC,GAAG,SAAS,qCAAqC,OAAO,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;oCACrG,CAAC;gCACH,CAAC,CAAC,EAAE,CAAC,CAAC;4BACR,CAAC;wBACH,CAAC,CAAC,CAAC;wBACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE;4BAC1B,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;4BACzB,OAAO,CAAC,YAAY,CAAC,CAAC;wBACxB,CAAC,CAAC,CAAC;wBACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;4BAChC,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,6CAA6C,OAAO,IAAI,EAAE,GAAG,CAAC,CAAC;4BACxF,MAAM,CAAC,GAAG,CAAC,CAAC;wBACd,CAAC,CAAC,CAAC;oBACL,CAAC,CAAC,CAAC;gBACL,CAAC,CAAC;gBACF,eAAe;gBAEf,qCAAqC;gBACrC,iCAAiC;gBACjC,iDAAiD;gBACjD,4BAA4B;gBAC5B,iBAAiB;gBACjB,iBAAiB;gBACjB,OAAO;gBACP,wCAAwC;gBACxC,iBAAiB;gBACjB,qBAAqB;gBACrB,OAAO;gBACP,4BAA4B;gBAC5B,oCAAoC;gBACpC,oCAAoC;gBACpC,OAAO;gBACP,mCAAmC;gBACnC,mCAAmC;gBACnC,MAAM;gBAEN,OAAO,MAAM,CAAC;YAChB,CAAC;YACD,OAAO,EAAE,KAAK,EAAE,MAAa,EAAE,EAAE;gBAC/B,IAAI,CAAC;oBACH,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;oBACpB,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;gBAC1C,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,MAAM,CAAC,UAAU,EAAE,CAAC;oBACpB,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,qBAAqB,CAAC,CAAC;gBAClD,CAAC;YACH,CAAC;YACD,QAAQ,EAAE,KAAK,EAAE,MAAa,EAAE,EAAE;gBAChC,IAAI,CAAC;oBACH,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC;oBAC3C,MAAM,OAAO,GAAG,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;wBAC9C,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,4BAA4B,qBAAqB,IAAI,CAAC,CAAC,EAAE,qBAAqB,CAAC,CAAC;oBACpH,CAAC,CAAC,CAAC;oBACH,MAAM,OAAO,CAAC,IAAI,CAAC;wBACjB,MAAM,CAAC,IAAI,EAAE;wBACb,OAAO;qBACR,CAAC,CAAC;oBACH,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC;oBAC3C,OAAO,MAAM,CAAC,MAAM,KAAK,OAAO,CAAC;gBACnC,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;oBACxC,OAAO,KAAK,CAAC;gBACf,CAAC;YACH,CAAC;SACF,CAAC;QAEF,OAAO,WAAW,CAAC,UAAU,CAAC,OAAO,EAAE;YACrC,GAAG;YACH,GAAG;YACH,YAAY;SACb,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,CAAC,OAAe,EAAE,EAAE;QAClC,IAAI,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAChC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,IAAI,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;YACnC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAC7B,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IAEF,OAAO;QACL,OAAO,EAAE,KAAK,EAAE,OAAgB,EAA+B,EAAE;YAC/D,MAAM,KAAK,GAAG,OAAO,IAAI,CAAC,CAAC;YAC3B,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;YAC5B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACpC,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,gBAAgB,KAAK,qBAAqB,CAAC,CAAC;YACrE,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,MAA2B,EAAiB,EAAE;YAC5D,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,OAAO,GAAG,MAAM,CAAC,gBAAgB,IAAI,EAAE,CAAC;gBAC9C,IAAI,WAAW,GAAG,KAAK,CAAC;gBACxB,QAAQ,IAAI,EAAE,CAAC;oBACb,KAAK,MAAM,CAAC,MAAM,KAAK,KAAK,CAAC;oBAC7B,KAAK,MAAM,CAAC,MAAM,KAAK,OAAO;wBAC5B,mCAAmC;wBACnC,WAAW,GAAG,IAAI,CAAC;wBACnB,MAAM;oBACR,KAAK,MAAM,CAAC,MAAM,KAAK,OAAO;wBAC5B,IAAI,CAAC;4BACH,gBAAgB;4BAChB,MAAM,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;wBAC/B,CAAC;wBAAC,OAAO,KAAK,EAAE,CAAC;4BACf,kDAAkD;4BAClD,WAAW,GAAG,IAAI,CAAC;wBACrB,CAAC;wBACD,MAAM;oBACR,QAAQ;gBACV,CAAC;gBACD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBAClC,IAAI,IAAI,EAAE,CAAC;oBACT,IAAI,WAAW,EAAE,CAAC;wBAChB,iBAAiB;wBACjB,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;wBAC3B,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,gBAAgB,OAAO,mCAAmC,CAAC,CAAC;oBACvF,CAAC;yBAAM,CAAC;wBACN,iBAAiB;wBACjB,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;wBAC3B,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,gBAAgB,OAAO,YAAY,CAAC,CAAC;oBAChE,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,EAAiB,EAAE;YACjD,KAAK,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;gBAChD,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,sCAAsC,OAAO,KAAK,CAAC,CAAC;gBAC7E,MAAM,OAAO,CAAC,IAAI,CAAC;oBACjB,CAAC,KAAK,IAAI,EAAE;wBACV,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;wBACnB,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;oBACrB,CAAC,CAAC,EAAE;oBACJ,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;wBAC9B,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,SAAS,mDAAmD,OAAO,EAAE,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;oBAC3H,CAAC,CAAC;iBACH,CAAC,CAAC;gBACH,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,2BAA2B,OAAO,aAAa,CAAC,CAAC;gBAC1E,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC,CAAC;AAnPW,QAAA,eAAe,mBAmP1B"}
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.0.1",
3
+ "version": "1.1.1",
4
4
  "description": "This is a library for redis connection pooling",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -3,7 +3,7 @@ import * as genericPool from 'generic-pool';
3
3
 
4
4
  export interface RedisConfig {
5
5
  url: string;
6
- db?: number;
6
+ dbIndex?: number;
7
7
  connectTimeout?: number;
8
8
  max?: number;
9
9
  min?: number;
@@ -11,15 +11,9 @@ export interface RedisConfig {
11
11
  enableTls?: boolean;
12
12
  }
13
13
 
14
- export interface ManagedRedisPool {
15
- acquire(dbIndex?: number): Promise<ManagedRedisClient>;
16
- release(client?: Redis): Promise<void>;
17
- destroy(timeoutMs?: number): Promise<void>;
18
- }
19
-
20
- export interface ManagedRedisClient extends Redis {
14
+ export interface RedisClient extends Redis {
21
15
  getKeys(pattern: string): Promise<string[]>;
22
- deleteKeys(pattern: string): Promise<number>;
16
+ deleteKeys(pattern: string): Promise<PromiseSettledResult<number>[]>;
23
17
  _originalDbIndex?: number; // 内部状態管理用変数
24
18
  }
25
19
 
@@ -29,61 +23,207 @@ const DEFAULT_MAX_POOLING_SIZE = 10;
29
23
  const DEFAULT_MIN_POOLING_SIZE = 0;
30
24
  const REDIS_PING_TIMEOUT_MS = 3000; // 3秒
31
25
 
32
- /**
33
- * Redisコネクションプーリングを生成する
34
- *
35
- * @param {RedisConfig} config
36
- * @returns {ManagedRedisPool}
37
- */
38
- export const createRedisPool = (config: RedisConfig): ManagedRedisPool => {
26
+ export class RedisPool {
27
+
28
+ private readonly url: string;
29
+ private readonly db: number;
30
+ private readonly connectTimeout: number;
31
+ private readonly max: number;
32
+ private readonly min: number;
33
+ private readonly testOnBorrow: boolean;
34
+ private readonly tls?: { rejectUnauthorized: false };
39
35
 
40
- const url = config.url;
41
- const db = config.db ?? 0;
42
- const connectTimeout = config.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT;
43
- const max = config.max ?? DEFAULT_MAX_POOLING_SIZE;
44
- const min = config.min ?? DEFAULT_MIN_POOLING_SIZE;
45
- const testOnBorrow = config.testOnBorrow ?? true;
36
+ private readonly poolMap = new Map<number, genericPool.Pool<RedisClient>>();
37
+ private initialized = false;
38
+ private readonly debug: boolean;
39
+
40
+ constructor(config: RedisConfig, options?: {
41
+ debug?: boolean;
42
+ }) {
43
+ if (!config.url) {
44
+ throw new Error(`${logHeader} Redis connection url is required.`);
45
+ }
46
46
 
47
- if (!url) {
48
- throw new Error(`${logHeader} Redis connection url is required.`);
47
+ this.url = config.url;
48
+ this.db = config.dbIndex ?? 0;
49
+ this.connectTimeout = config.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT;
50
+ this.max = config.max ?? DEFAULT_MAX_POOLING_SIZE;
51
+ this.min = config.min ?? DEFAULT_MIN_POOLING_SIZE;
52
+ this.testOnBorrow = config.testOnBorrow ?? true;
53
+ this.tls = config.enableTls ? { rejectUnauthorized: false } : undefined;
54
+
55
+ this.debug = options?.debug ?? false;
49
56
  }
50
57
 
51
- // dbIndexごとのプール管理
52
- const poolMap = new Map<number, genericPool.Pool<ManagedRedisClient>>();
58
+ public async acquire(dbIndex?: number): Promise<RedisClient> {
59
+ if (!this.initialized) {
60
+ // 初回acquire呼び出し時のみ接続チェックを行う
61
+ // ホスト不正やパスワード不正による接続不可等を検知する
62
+ // ※ generic-poolのfactoryの方に入ってしまうとエラーを呼び出し元に伝播させることが難しいため、プールとは別の接続でチェックする
63
+ await this.checkConnectivity(dbIndex ?? this.db);
64
+ this.initialized = true;
65
+ }
53
66
 
54
- const createSingleDbPool = (dbIndex: number): genericPool.Pool<ManagedRedisClient> => {
55
- const factory: genericPool.Factory<ManagedRedisClient> = {
56
- create: async (): Promise<ManagedRedisClient> => {
57
- let client;
58
- try {
59
- client = new Redis(url, {
60
- db: dbIndex,
61
- connectTimeout: connectTimeout,
62
- keepAlive: 1,
63
- enableOfflineQueue: true,
64
- tls: config.enableTls ? { rejectUnauthorized: false } : undefined,
65
- retryStrategy: (times) => {
66
- const delay = Math.min(times * 50, 1000);
67
- if (process.env.NODE_ENV !== 'test') {
68
- console.debug(logHeader, `retry strategy called ${times} times. delaying ${delay}ms`);
69
- }
70
- return delay;
71
- },
72
- reconnectOnError: (error) => {
73
- process.emitWarning(`${logHeader} detected error (on reconnect). ${error.message}`);
74
- return true;
67
+ const index = dbIndex ?? this.db;
68
+ const pool = this.getPool(index);
69
+ try {
70
+ const client = await pool.acquire();
71
+
72
+ this.debugLog(logHeader, `Redis client ${index} has been acquired.`);
73
+ return client;
74
+ } catch (error) {
75
+ throw error;
76
+ }
77
+ }
78
+
79
+ public async release(client?: RedisClient): Promise<void> {
80
+ if (client) {
81
+ const dbIndex = client._originalDbIndex ?? this.db;
82
+ const pool = this.poolMap.get(dbIndex);
83
+ if (!pool) {
84
+ return;
85
+ }
86
+ let needDestroy = false;
87
+ switch (true) {
88
+ case client.status === 'end':
89
+ case client.status === 'close':
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
+ }
113
+ }
114
+ }
115
+
116
+ public async destroy(timeoutMs = 5000): Promise<void> {
117
+ for (const [dbIndex, pool] of this.poolMap.entries()) {
118
+ this.debugLog(logHeader, `Destroying Redis pool for DB index ${dbIndex}...`);
119
+ await Promise.race([
120
+ (async () => {
121
+ await pool.drain();
122
+ await pool.clear();
123
+ })(),
124
+ new Promise<void>((_, reject) => {
125
+ setTimeout(() => reject(new Error(`${logHeader} Timeout while draining Redis pool for DB index ${dbIndex}`)), timeoutMs);
126
+ })
127
+ ]);
128
+ this.debugLog(logHeader, `Redis pool for DB index ${dbIndex} destroyed.`);
129
+ this.poolMap.delete(dbIndex);
130
+ }
131
+ }
132
+
133
+ /**
134
+ * 初めてacquireが呼ばれた時に、指定された接続情報で接続できるかチェックする
135
+ *
136
+ * @param {number} dbIndex
137
+ */
138
+ private async checkConnectivity(dbIndex: number): Promise<void> {
139
+ const client = new Redis(this.url, {
140
+ db: dbIndex,
141
+ retryStrategy: () => null, // 再接続無効
142
+ reconnectOnError: () => false, // 再接続無効
143
+ connectTimeout: this.connectTimeout,
144
+ tls: this.tls,
145
+ });
146
+
147
+ return new Promise<void>((resolve, reject) => {
148
+ const cleanup = () => {
149
+ client.quit().catch(() => client.disconnect());
150
+ };
151
+
152
+ client.once('ready', () => {
153
+ cleanup();
154
+ resolve();
155
+ });
156
+
157
+ client.once('error', (error) => {
158
+ cleanup();
159
+ reject(error);
160
+ });
161
+ });
162
+ }
163
+
164
+ private async ping(client: Redis): Promise<boolean> {
165
+ try {
166
+ this.debugLog(logHeader, 'start validate');
167
+ const timeout = new Promise<void>((_, reject) => {
168
+ setTimeout(() => reject(new Error(`Redis PING timeout after ${REDIS_PING_TIMEOUT_MS}ms`)), REDIS_PING_TIMEOUT_MS);
169
+ });
170
+ await Promise.race([
171
+ client.ping(),
172
+ timeout
173
+ ]);
174
+ this.debugLog(logHeader, 'ping succeeded');
175
+ return client.status === 'ready';
176
+ } catch (error: any) {
177
+ this.debugLog(logHeader, 'ping failed');
178
+ return false;
179
+ }
180
+ }
181
+
182
+ public debugLog(...args: any[]): void {
183
+ if (this.debug) {
184
+ console.debug(...args);
185
+ }
186
+ }
187
+
188
+ private getPool(dbIndex: number): genericPool.Pool<RedisClient> {
189
+ let pool = this.poolMap.get(dbIndex);
190
+ if (!pool) {
191
+ pool = this.createSingleDbPool(dbIndex);
192
+ this.poolMap.set(dbIndex, pool);
193
+ }
194
+ return pool;
195
+ }
196
+
197
+ private createSingleDbPool(dbIndex: number): genericPool.Pool<RedisClient> {
198
+ const factory: genericPool.Factory<RedisClient> = {
199
+ create: async (): Promise<RedisClient> => {
200
+ const client = new Redis(this.url, {
201
+ db: dbIndex,
202
+ connectTimeout: this.connectTimeout,
203
+ keepAlive: 1,
204
+ enableOfflineQueue: true,
205
+ tls: this.tls,
206
+ retryStrategy: (times) => {
207
+ const delay = Math.min(times * 50, 1000);
208
+ if (process.env.NODE_ENV !== 'test') {
209
+ this.debugLog(logHeader, `retry strategy called ${times} times. delaying ${delay}ms`);
75
210
  }
76
- }) as ManagedRedisClient;
77
- } catch (error) {
78
- console.error(error);
79
- throw error;
80
- }
211
+ return delay;
212
+ },
213
+ reconnectOnError: (error) => {
214
+ process.emitWarning(`${logHeader} detected error (on reconnectOnError). ${error.message}`);
215
+ return true;
216
+ }
217
+ }) as RedisClient;
81
218
 
82
219
  client._originalDbIndex = dbIndex;
83
220
  client.on('error', error => {
84
221
  process.emitWarning(`${logHeader} detected error (on error). ${error.message}`);
85
222
  });
86
223
 
224
+ // カスタムメソッド内の処理でthis.debugLogなどが参照できなくなるため、poolのインスタンスを変数キャプチャする
225
+ const poolInstance = this;
226
+
87
227
  // カスタムメソッド START
88
228
 
89
229
  /**
@@ -107,46 +247,50 @@ export const createRedisPool = (config: RedisConfig): ManagedRedisPool => {
107
247
  });
108
248
  stream.on('end', () => resolve(allKeys));
109
249
  stream.on('error', (err: Error) => {
110
- console.error(logHeader, `Error during getKeys scan for pattern '${pattern}'.`, err);
111
250
  reject(err);
112
251
  });
113
252
  });
114
253
  };
115
254
  /**
116
255
  * 指定されたパターンに一致するキーを Redis から全て削除する (UNLINKを使用)
256
+ * 返却値の配列サイズはscanStreamが'data'を受信した回数で、この受信したデータで削除された件数がvalueに設定されている。
257
+ *
258
+ * [
259
+ * { status: 'fulfilled', value: 100 }, // 1バッチ目で100件削除
260
+ * { status: 'fulfilled', value: 80 } // 2バッチ目で80件削除
261
+ * ]
262
+ *
263
+ * 成功した件数は以下のようにして取得可能
264
+ *
265
+ * const delResults = await redisClient.deleteKeys('pattern');
266
+ * const delCount = delResults.filter(a => a.status === 'fulfilled')
267
+ * .reduce((acc, cur) => acc + (cur as PromiseFulfilledResult<number>).value, 0);
117
268
  *
118
269
  * @param {string} pattern
119
- * @returns {Promise<number>}
270
+ * @returns {Promise<PromiseSettledResult<number>>}
120
271
  */
121
- client.deleteKeys = async function(pattern: string): Promise<number> {
122
- let deletedCount = 0;
272
+ client.deleteKeys = async function(pattern: string): Promise<PromiseSettledResult<number>[]> {
123
273
  const stream = this.scanStream({
124
274
  match: pattern,
125
275
  count: 1000 // 1度にスキャンする件数
126
276
  });
127
277
 
128
278
  return new Promise((resolve, reject) => {
129
- const tasks: Promise<void>[] = [];
279
+ const tasks: Promise<number>[] = [];
130
280
  stream.on('data', async (keys: string[]) => {
131
281
  if (keys.length) {
132
282
  tasks.push((async () => {
133
- try {
134
- // UNLINK を使用し、現在のインスタンス (this) で実行
135
- const unlinkResult = await this.unlink(...keys);
136
- deletedCount += unlinkResult;
137
- console.debug(logHeader, `Deleted ${unlinkResult} keys in a batch for pattern '${pattern}'. Total: ${deletedCount}`);
138
- } catch (error: any) {
139
- process.emitWarning(`${logHeader} Error during UNLINK for pattern '${pattern}'. ${error.message}`);
140
- }
283
+ // UNLINK を使用し、現在のインスタンス (this) で実行
284
+ const unlinkResult = await this.unlink(...keys);
285
+ return unlinkResult;
141
286
  })());
142
287
  }
143
288
  });
144
289
  stream.on('end', async () => {
145
- await Promise.all(tasks);
146
- resolve(deletedCount);
290
+ const results = await Promise.allSettled(tasks);
291
+ resolve(results);
147
292
  });
148
293
  stream.on('error', (err: Error) => {
149
- console.error(logHeader, `Error during deleteKeys scan for pattern '${pattern}'.`, err);
150
294
  reject(err);
151
295
  });
152
296
  });
@@ -177,105 +321,21 @@ export const createRedisPool = (config: RedisConfig): ManagedRedisPool => {
177
321
  destroy: async (client: Redis) => {
178
322
  try {
179
323
  await client.quit();
180
- console.debug(logHeader, 'client quit');
324
+ this.debugLog(logHeader, 'client quit');
181
325
  } catch (error) {
182
326
  client.disconnect();
183
- console.debug(logHeader, 'client disconnected');
327
+ this.debugLog(logHeader, 'client disconnected');
184
328
  }
185
329
  },
186
330
  validate: async (client: Redis) => {
187
- try {
188
- console.debug(logHeader, 'start validate');
189
- const timeout = new Promise<void>((_, reject) => {
190
- setTimeout(() => reject(new Error(`Redis PING timeout after ${REDIS_PING_TIMEOUT_MS}ms`)), REDIS_PING_TIMEOUT_MS);
191
- });
192
- await Promise.race([
193
- client.ping(),
194
- timeout
195
- ]);
196
- console.debug(logHeader, 'ping succeeded');
197
- return client.status === 'ready';
198
- } catch (error) {
199
- console.debug(logHeader, 'ping failed');
200
- return false;
201
- }
331
+ return await this.ping(client);
202
332
  }
203
333
  };
204
334
 
205
335
  return genericPool.createPool(factory, {
206
- max,
207
- min,
208
- testOnBorrow
336
+ max: this.max,
337
+ min: this.min,
338
+ testOnBorrow: this.testOnBorrow
209
339
  });
210
- };
211
-
212
- const getPool = (dbIndex: number) => {
213
- let pool = poolMap.get(dbIndex);
214
- if (!pool) {
215
- pool = createSingleDbPool(dbIndex);
216
- poolMap.set(dbIndex, pool);
217
- }
218
- return pool;
219
- };
220
-
221
- return {
222
- acquire: async (dbIndex?: number): Promise<ManagedRedisClient> => {
223
- const index = dbIndex ?? 0;
224
- const pool = getPool(index);
225
- const client = await pool.acquire();
226
- console.debug(logHeader, `Redis client ${index} has been acquired.`);
227
- return client;
228
- },
229
- release: async (client?: ManagedRedisClient): Promise<void> => {
230
- if (client) {
231
- const dbIndex = client._originalDbIndex ?? db;
232
- let needDestroy = false;
233
- switch (true) {
234
- case client.status === 'end':
235
- case client.status === 'close':
236
- // Redisクライアントの状態が再利用できない場合はプールから破棄
237
- needDestroy = true;
238
- break;
239
- case client.status === 'ready':
240
- try {
241
- // 元のDBインデックスに戻す
242
- await client.select(dbIndex);
243
- } catch (error) {
244
- // selectに失敗する→Redisクライアントが不正な状態にあると判断できるのでプールから破棄
245
- needDestroy = true;
246
- }
247
- break;
248
- default:
249
- }
250
- const pool = poolMap.get(dbIndex);
251
- if (pool) {
252
- if (needDestroy) {
253
- // Redisクライアントの破棄
254
- await pool.destroy(client);
255
- console.debug(logHeader, `Redis client ${dbIndex} destroyed due to invalid status.`);
256
- } else {
257
- // Redisクライアントの返却
258
- await pool.release(client);
259
- console.debug(logHeader, `Redis client ${dbIndex} released.`);
260
- }
261
- }
262
- }
263
- },
264
- destroy: async (timeoutMs = 5000): Promise<void> => {
265
- for (const [dbIndex, pool] of poolMap.entries()) {
266
- console.debug(logHeader, `Destroying Redis pool for DB index ${dbIndex}...`);
267
- await Promise.race([
268
- (async () => {
269
- await pool.drain();
270
- await pool.clear();
271
- })(),
272
- new Promise<void>((_, reject) => {
273
- setTimeout(() => reject(new Error(`${logHeader} Timeout while draining Redis pool for DB index ${dbIndex}`)), timeoutMs);
274
- })
275
- ]);
276
- console.debug(logHeader, `Redis pool for DB index ${dbIndex} destroyed.`);
277
- poolMap.delete(dbIndex);
278
- }
279
- }
280
340
  }
281
- };
341
+ }