@digitalwalletcorp/redis-pooling 1.1.0 → 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 +5 -3
- package/lib/redis-pooling.d.ts +15 -3
- package/lib/redis-pooling.js +115 -67
- package/lib/redis-pooling.js.map +1 -1
- package/package.json +1 -1
- package/src/redis-pooling.ts +127 -67
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
|
|
105
|
-
|
|
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
|
}
|
|
@@ -140,7 +142,7 @@ Extends the standard `ioredis` `Redis` client with additional helpers:
|
|
|
140
142
|
| Method | Signature | Description |
|
|
141
143
|
| ----------------------------- | ------------------- | ----------------------------------------------------------------------------------------------- |
|
|
142
144
|
| `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
|
|
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. |
|
|
144
146
|
|
|
145
147
|
#### 🗄 Database Index Handling
|
|
146
148
|
|
package/lib/redis-pooling.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export interface RedisConfig {
|
|
|
10
10
|
}
|
|
11
11
|
export interface RedisClient extends Redis {
|
|
12
12
|
getKeys(pattern: string): Promise<string[]>;
|
|
13
|
-
deleteKeys(pattern: string): Promise<number>;
|
|
13
|
+
deleteKeys(pattern: string): Promise<PromiseSettledResult<number>[]>;
|
|
14
14
|
_originalDbIndex?: number;
|
|
15
15
|
}
|
|
16
16
|
export declare class RedisPool {
|
|
@@ -20,12 +20,24 @@ export declare class RedisPool {
|
|
|
20
20
|
private readonly max;
|
|
21
21
|
private readonly min;
|
|
22
22
|
private readonly testOnBorrow;
|
|
23
|
-
private readonly
|
|
23
|
+
private readonly tls?;
|
|
24
24
|
private readonly poolMap;
|
|
25
|
-
|
|
25
|
+
private initialized;
|
|
26
|
+
private readonly debug;
|
|
27
|
+
constructor(config: RedisConfig, options?: {
|
|
28
|
+
debug?: boolean;
|
|
29
|
+
});
|
|
26
30
|
acquire(dbIndex?: number): Promise<RedisClient>;
|
|
27
31
|
release(client?: RedisClient): Promise<void>;
|
|
28
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;
|
|
29
41
|
private getPool;
|
|
30
42
|
private createSingleDbPool;
|
|
31
43
|
}
|
package/lib/redis-pooling.js
CHANGED
|
@@ -48,9 +48,11 @@ class RedisPool {
|
|
|
48
48
|
max;
|
|
49
49
|
min;
|
|
50
50
|
testOnBorrow;
|
|
51
|
-
|
|
51
|
+
tls;
|
|
52
52
|
poolMap = new Map();
|
|
53
|
-
|
|
53
|
+
initialized = false;
|
|
54
|
+
debug;
|
|
55
|
+
constructor(config, options) {
|
|
54
56
|
if (!config.url) {
|
|
55
57
|
throw new Error(`${logHeader} Redis connection url is required.`);
|
|
56
58
|
}
|
|
@@ -60,14 +62,27 @@ class RedisPool {
|
|
|
60
62
|
this.max = config.max ?? DEFAULT_MAX_POOLING_SIZE;
|
|
61
63
|
this.min = config.min ?? DEFAULT_MIN_POOLING_SIZE;
|
|
62
64
|
this.testOnBorrow = config.testOnBorrow ?? true;
|
|
63
|
-
this.
|
|
65
|
+
this.tls = config.enableTls ? { rejectUnauthorized: false } : undefined;
|
|
66
|
+
this.debug = options?.debug ?? false;
|
|
64
67
|
}
|
|
65
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
|
+
}
|
|
66
76
|
const index = dbIndex ?? this.db;
|
|
67
77
|
const pool = this.getPool(index);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
+
}
|
|
71
86
|
}
|
|
72
87
|
async release(client) {
|
|
73
88
|
if (client) {
|
|
@@ -98,18 +113,18 @@ class RedisPool {
|
|
|
98
113
|
if (needDestroy) {
|
|
99
114
|
// Redisクライアントの破棄
|
|
100
115
|
await pool.destroy(client);
|
|
101
|
-
|
|
116
|
+
this.debugLog(logHeader, `Redis client ${dbIndex} destroyed due to invalid status.`);
|
|
102
117
|
}
|
|
103
118
|
else {
|
|
104
119
|
// Redisクライアントの返却
|
|
105
120
|
await pool.release(client);
|
|
106
|
-
|
|
121
|
+
this.debugLog(logHeader, `Redis client ${dbIndex} released.`);
|
|
107
122
|
}
|
|
108
123
|
}
|
|
109
124
|
}
|
|
110
125
|
async destroy(timeoutMs = 5000) {
|
|
111
126
|
for (const [dbIndex, pool] of this.poolMap.entries()) {
|
|
112
|
-
|
|
127
|
+
this.debugLog(logHeader, `Destroying Redis pool for DB index ${dbIndex}...`);
|
|
113
128
|
await Promise.race([
|
|
114
129
|
(async () => {
|
|
115
130
|
await pool.drain();
|
|
@@ -119,10 +134,60 @@ class RedisPool {
|
|
|
119
134
|
setTimeout(() => reject(new Error(`${logHeader} Timeout while draining Redis pool for DB index ${dbIndex}`)), timeoutMs);
|
|
120
135
|
})
|
|
121
136
|
]);
|
|
122
|
-
|
|
137
|
+
this.debugLog(logHeader, `Redis pool for DB index ${dbIndex} destroyed.`);
|
|
123
138
|
this.poolMap.delete(dbIndex);
|
|
124
139
|
}
|
|
125
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
|
+
}
|
|
126
191
|
getPool(dbIndex) {
|
|
127
192
|
let pool = this.poolMap.get(dbIndex);
|
|
128
193
|
if (!pool) {
|
|
@@ -134,35 +199,30 @@ class RedisPool {
|
|
|
134
199
|
createSingleDbPool(dbIndex) {
|
|
135
200
|
const factory = {
|
|
136
201
|
create: async () => {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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;
|
|
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`);
|
|
155
212
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
213
|
+
return delay;
|
|
214
|
+
},
|
|
215
|
+
reconnectOnError: (error) => {
|
|
216
|
+
process.emitWarning(`${logHeader} detected error (on reconnectOnError). ${error.message}`);
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
});
|
|
162
220
|
client._originalDbIndex = dbIndex;
|
|
163
221
|
client.on('error', error => {
|
|
164
222
|
process.emitWarning(`${logHeader} detected error (on error). ${error.message}`);
|
|
165
223
|
});
|
|
224
|
+
// カスタムメソッド内の処理でthis.debugLogなどが参照できなくなるため、poolのインスタンスを変数キャプチャする
|
|
225
|
+
const poolInstance = this;
|
|
166
226
|
// カスタムメソッド START
|
|
167
227
|
/**
|
|
168
228
|
* 指定されたパターンに一致するキーを Redis から全て取得する
|
|
@@ -184,19 +244,29 @@ class RedisPool {
|
|
|
184
244
|
});
|
|
185
245
|
stream.on('end', () => resolve(allKeys));
|
|
186
246
|
stream.on('error', (err) => {
|
|
187
|
-
console.error(logHeader, `Error during getKeys scan for pattern '${pattern}'.`, err);
|
|
188
247
|
reject(err);
|
|
189
248
|
});
|
|
190
249
|
});
|
|
191
250
|
};
|
|
192
251
|
/**
|
|
193
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);
|
|
194
265
|
*
|
|
195
266
|
* @param {string} pattern
|
|
196
|
-
* @returns {Promise<number
|
|
267
|
+
* @returns {Promise<PromiseSettledResult<number>>}
|
|
197
268
|
*/
|
|
198
269
|
client.deleteKeys = async function (pattern) {
|
|
199
|
-
let deletedCount = 0;
|
|
200
270
|
const stream = this.scanStream({
|
|
201
271
|
match: pattern,
|
|
202
272
|
count: 1000 // 1度にスキャンする件数
|
|
@@ -206,24 +276,17 @@ class RedisPool {
|
|
|
206
276
|
stream.on('data', async (keys) => {
|
|
207
277
|
if (keys.length) {
|
|
208
278
|
tasks.push((async () => {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
}
|
|
279
|
+
// UNLINK を使用し、現在のインスタンス (this) で実行
|
|
280
|
+
const unlinkResult = await this.unlink(...keys);
|
|
281
|
+
return unlinkResult;
|
|
218
282
|
})());
|
|
219
283
|
}
|
|
220
284
|
});
|
|
221
285
|
stream.on('end', async () => {
|
|
222
|
-
await Promise.
|
|
223
|
-
resolve(
|
|
286
|
+
const results = await Promise.allSettled(tasks);
|
|
287
|
+
resolve(results);
|
|
224
288
|
});
|
|
225
289
|
stream.on('error', (err) => {
|
|
226
|
-
console.error(logHeader, `Error during deleteKeys scan for pattern '${pattern}'.`, err);
|
|
227
290
|
reject(err);
|
|
228
291
|
});
|
|
229
292
|
});
|
|
@@ -252,30 +315,15 @@ class RedisPool {
|
|
|
252
315
|
destroy: async (client) => {
|
|
253
316
|
try {
|
|
254
317
|
await client.quit();
|
|
255
|
-
|
|
318
|
+
this.debugLog(logHeader, 'client quit');
|
|
256
319
|
}
|
|
257
320
|
catch (error) {
|
|
258
321
|
client.disconnect();
|
|
259
|
-
|
|
322
|
+
this.debugLog(logHeader, 'client disconnected');
|
|
260
323
|
}
|
|
261
324
|
},
|
|
262
325
|
validate: async (client) => {
|
|
263
|
-
|
|
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
|
-
}
|
|
326
|
+
return await this.ping(client);
|
|
279
327
|
}
|
|
280
328
|
};
|
|
281
329
|
return genericPool.createPool(factory, {
|
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,
|
|
1
|
+
{"version":3,"file":"redis-pooling.js","sourceRoot":"","sources":["../src/redis-pooling.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/redis-pooling.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface RedisConfig {
|
|
|
13
13
|
|
|
14
14
|
export interface RedisClient extends Redis {
|
|
15
15
|
getKeys(pattern: string): Promise<string[]>;
|
|
16
|
-
deleteKeys(pattern: string): Promise<number>;
|
|
16
|
+
deleteKeys(pattern: string): Promise<PromiseSettledResult<number>[]>;
|
|
17
17
|
_originalDbIndex?: number; // 内部状態管理用変数
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -31,11 +31,15 @@ export class RedisPool {
|
|
|
31
31
|
private readonly max: number;
|
|
32
32
|
private readonly min: number;
|
|
33
33
|
private readonly testOnBorrow: boolean;
|
|
34
|
-
private readonly
|
|
34
|
+
private readonly tls?: { rejectUnauthorized: false };
|
|
35
35
|
|
|
36
36
|
private readonly poolMap = new Map<number, genericPool.Pool<RedisClient>>();
|
|
37
|
+
private initialized = false;
|
|
38
|
+
private readonly debug: boolean;
|
|
37
39
|
|
|
38
|
-
constructor(config: RedisConfig
|
|
40
|
+
constructor(config: RedisConfig, options?: {
|
|
41
|
+
debug?: boolean;
|
|
42
|
+
}) {
|
|
39
43
|
if (!config.url) {
|
|
40
44
|
throw new Error(`${logHeader} Redis connection url is required.`);
|
|
41
45
|
}
|
|
@@ -46,16 +50,30 @@ export class RedisPool {
|
|
|
46
50
|
this.max = config.max ?? DEFAULT_MAX_POOLING_SIZE;
|
|
47
51
|
this.min = config.min ?? DEFAULT_MIN_POOLING_SIZE;
|
|
48
52
|
this.testOnBorrow = config.testOnBorrow ?? true;
|
|
49
|
-
this.
|
|
53
|
+
this.tls = config.enableTls ? { rejectUnauthorized: false } : undefined;
|
|
54
|
+
|
|
55
|
+
this.debug = options?.debug ?? false;
|
|
50
56
|
}
|
|
51
57
|
|
|
52
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
|
+
}
|
|
66
|
+
|
|
53
67
|
const index = dbIndex ?? this.db;
|
|
54
68
|
const pool = this.getPool(index);
|
|
55
|
-
|
|
69
|
+
try {
|
|
70
|
+
const client = await pool.acquire();
|
|
56
71
|
|
|
57
|
-
|
|
58
|
-
|
|
72
|
+
this.debugLog(logHeader, `Redis client ${index} has been acquired.`);
|
|
73
|
+
return client;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
59
77
|
}
|
|
60
78
|
|
|
61
79
|
public async release(client?: RedisClient): Promise<void> {
|
|
@@ -86,18 +104,18 @@ export class RedisPool {
|
|
|
86
104
|
if (needDestroy) {
|
|
87
105
|
// Redisクライアントの破棄
|
|
88
106
|
await pool.destroy(client);
|
|
89
|
-
|
|
107
|
+
this.debugLog(logHeader, `Redis client ${dbIndex} destroyed due to invalid status.`);
|
|
90
108
|
} else {
|
|
91
109
|
// Redisクライアントの返却
|
|
92
110
|
await pool.release(client);
|
|
93
|
-
|
|
111
|
+
this.debugLog(logHeader, `Redis client ${dbIndex} released.`);
|
|
94
112
|
}
|
|
95
113
|
}
|
|
96
114
|
}
|
|
97
115
|
|
|
98
116
|
public async destroy(timeoutMs = 5000): Promise<void> {
|
|
99
117
|
for (const [dbIndex, pool] of this.poolMap.entries()) {
|
|
100
|
-
|
|
118
|
+
this.debugLog(logHeader, `Destroying Redis pool for DB index ${dbIndex}...`);
|
|
101
119
|
await Promise.race([
|
|
102
120
|
(async () => {
|
|
103
121
|
await pool.drain();
|
|
@@ -107,11 +125,66 @@ export class RedisPool {
|
|
|
107
125
|
setTimeout(() => reject(new Error(`${logHeader} Timeout while draining Redis pool for DB index ${dbIndex}`)), timeoutMs);
|
|
108
126
|
})
|
|
109
127
|
]);
|
|
110
|
-
|
|
128
|
+
this.debugLog(logHeader, `Redis pool for DB index ${dbIndex} destroyed.`);
|
|
111
129
|
this.poolMap.delete(dbIndex);
|
|
112
130
|
}
|
|
113
131
|
}
|
|
114
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
|
+
|
|
115
188
|
private getPool(dbIndex: number): genericPool.Pool<RedisClient> {
|
|
116
189
|
let pool = this.poolMap.get(dbIndex);
|
|
117
190
|
if (!pool) {
|
|
@@ -124,36 +197,33 @@ export class RedisPool {
|
|
|
124
197
|
private createSingleDbPool(dbIndex: number): genericPool.Pool<RedisClient> {
|
|
125
198
|
const factory: genericPool.Factory<RedisClient> = {
|
|
126
199
|
create: async (): Promise<RedisClient> => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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;
|
|
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`);
|
|
145
210
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
211
|
+
return delay;
|
|
212
|
+
},
|
|
213
|
+
reconnectOnError: (error) => {
|
|
214
|
+
process.emitWarning(`${logHeader} detected error (on reconnectOnError). ${error.message}`);
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
}) as RedisClient;
|
|
151
218
|
|
|
152
219
|
client._originalDbIndex = dbIndex;
|
|
153
220
|
client.on('error', error => {
|
|
154
221
|
process.emitWarning(`${logHeader} detected error (on error). ${error.message}`);
|
|
155
222
|
});
|
|
156
223
|
|
|
224
|
+
// カスタムメソッド内の処理でthis.debugLogなどが参照できなくなるため、poolのインスタンスを変数キャプチャする
|
|
225
|
+
const poolInstance = this;
|
|
226
|
+
|
|
157
227
|
// カスタムメソッド START
|
|
158
228
|
|
|
159
229
|
/**
|
|
@@ -177,46 +247,50 @@ export class RedisPool {
|
|
|
177
247
|
});
|
|
178
248
|
stream.on('end', () => resolve(allKeys));
|
|
179
249
|
stream.on('error', (err: Error) => {
|
|
180
|
-
console.error(logHeader, `Error during getKeys scan for pattern '${pattern}'.`, err);
|
|
181
250
|
reject(err);
|
|
182
251
|
});
|
|
183
252
|
});
|
|
184
253
|
};
|
|
185
254
|
/**
|
|
186
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);
|
|
187
268
|
*
|
|
188
269
|
* @param {string} pattern
|
|
189
|
-
* @returns {Promise<number
|
|
270
|
+
* @returns {Promise<PromiseSettledResult<number>>}
|
|
190
271
|
*/
|
|
191
|
-
client.deleteKeys = async function(pattern: string): Promise<number> {
|
|
192
|
-
let deletedCount = 0;
|
|
272
|
+
client.deleteKeys = async function(pattern: string): Promise<PromiseSettledResult<number>[]> {
|
|
193
273
|
const stream = this.scanStream({
|
|
194
274
|
match: pattern,
|
|
195
275
|
count: 1000 // 1度にスキャンする件数
|
|
196
276
|
});
|
|
197
277
|
|
|
198
278
|
return new Promise((resolve, reject) => {
|
|
199
|
-
const tasks: Promise<
|
|
279
|
+
const tasks: Promise<number>[] = [];
|
|
200
280
|
stream.on('data', async (keys: string[]) => {
|
|
201
281
|
if (keys.length) {
|
|
202
282
|
tasks.push((async () => {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
}
|
|
283
|
+
// UNLINK を使用し、現在のインスタンス (this) で実行
|
|
284
|
+
const unlinkResult = await this.unlink(...keys);
|
|
285
|
+
return unlinkResult;
|
|
211
286
|
})());
|
|
212
287
|
}
|
|
213
288
|
});
|
|
214
289
|
stream.on('end', async () => {
|
|
215
|
-
await Promise.
|
|
216
|
-
resolve(
|
|
290
|
+
const results = await Promise.allSettled(tasks);
|
|
291
|
+
resolve(results);
|
|
217
292
|
});
|
|
218
293
|
stream.on('error', (err: Error) => {
|
|
219
|
-
console.error(logHeader, `Error during deleteKeys scan for pattern '${pattern}'.`, err);
|
|
220
294
|
reject(err);
|
|
221
295
|
});
|
|
222
296
|
});
|
|
@@ -247,28 +321,14 @@ export class RedisPool {
|
|
|
247
321
|
destroy: async (client: Redis) => {
|
|
248
322
|
try {
|
|
249
323
|
await client.quit();
|
|
250
|
-
|
|
324
|
+
this.debugLog(logHeader, 'client quit');
|
|
251
325
|
} catch (error) {
|
|
252
326
|
client.disconnect();
|
|
253
|
-
|
|
327
|
+
this.debugLog(logHeader, 'client disconnected');
|
|
254
328
|
}
|
|
255
329
|
},
|
|
256
330
|
validate: async (client: Redis) => {
|
|
257
|
-
|
|
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
|
-
}
|
|
331
|
+
return await this.ping(client);
|
|
272
332
|
}
|
|
273
333
|
};
|
|
274
334
|
|