Skip to content

Commit 3e87d19

Browse files
committed
Fix typo and improve Sentinel docs
1 parent 9459660 commit 3e87d19

File tree

4 files changed

+118
-36
lines changed

4 files changed

+118
-36
lines changed

docs/sentinel.md

+17-14
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const sentinel = await createSentinel({
1414
port: 1234
1515
}]
1616
})
17-
.on('error', err => console.error('Redis Sentinel Error', err));
17+
.on('error', err => console.error('Redis Sentinel Error', err))
1818
.connect();
1919

2020
await sentinel.set('key', 'value');
@@ -26,16 +26,19 @@ In the above example, we configure the sentinel object to fetch the configuratio
2626

2727
## `createSentinel` configuration
2828

29-
| Property | Default | Description |
30-
|-----------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
31-
| name | | The sentinel identifier for a particular database cluster |
32-
| sentinelRootNodes | | An array of root nodes that are part of the sentinel cluster, which will be used to get the topology. Each element in the array is a client configuration object. There is no need to specify every node in the cluster: 3 should be enough to reliably connect and obtain the sentinel configuration from the server |
33-
| maxCommandRediscovers | `16` | The maximum number of times a command will retry due to topology changes. |
34-
| nodeClientOptions | | The configuration values for every node in the cluster. Use this for example when specifying an ACL user to connect with |
35-
| sentinelClientOptions | | The configuration values for every sentinel in the cluster. Use this for example when specifying an ACL user to connect with |
36-
| masterPoolSize | `1` | The number of clients connected to the master node |
37-
| replicaPoolSize | `0` | The number of clients connected to each replica node. When greater than 0, the client will distribute the load by executing read-only commands (such as `GET`, `GEOSEARCH`, etc.) across all the cluster nodes. |
38-
| reserveClient | `false` | When `true`, one client will be reserved for the sentinel object. When `false`, the sentinel object will wait for the first available client from the pool. |
29+
| Property | Default | Description |
30+
|----------------------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
31+
| name | | The sentinel identifier for a particular database cluster |
32+
| sentinelRootNodes | | An array of root nodes that are part of the sentinel cluster, which will be used to get the topology. Each element in the array is a client configuration object. There is no need to specify every node in the cluster: 3 should be enough to reliably connect and obtain the sentinel configuration from the server |
33+
| maxCommandRediscovers | `16` | The maximum number of times a command will retry due to topology changes. |
34+
| nodeClientOptions | | The configuration values for every node in the cluster. Use this for example when specifying an ACL user to connect with |
35+
| sentinelClientOptions | | The configuration values for every sentinel in the cluster. Use this for example when specifying an ACL user to connect with |
36+
| masterPoolSize | `1` | The number of clients connected to the master node |
37+
| replicaPoolSize | `0` | The number of clients connected to each replica node. When greater than 0, the client will distribute the load by executing read-only commands (such as `GET`, `GEOSEARCH`, etc.) across all the cluster nodes. |
38+
| scanInterval | `10000` | Interval in milliseconds to periodically scan for changes in the sentinel topology. The client will query the sentinel for changes at this interval. |
39+
| passthroughClientErrorEvents | `false` | When `true`, error events from client instances inside the sentinel will be propagated to the sentinel instance. This allows handling all client errors through a single error handler on the sentinel instance. |
40+
| reserveClient | `false` | When `true`, one client will be reserved for the sentinel object. When `false`, the sentinel object will wait for the first available client from the pool. |
41+
3942
## PubSub
4043

4144
It supports PubSub via the normal mechanisms, including migrating the listeners if the node they are connected to goes down.
@@ -60,7 +63,7 @@ createSentinel({
6063
});
6164
```
6265

63-
In addition, it also provides the ability have a pool of clients connected to the replica nodes, and to direct all read-only commands to them:
66+
In addition, it also provides the ability have a pool of clients connected to the replica nodes, and to direct all read-only commands to them:
6467

6568
```javascript
6669
createSentinel({
@@ -85,9 +88,9 @@ const result = await sentinel.use(async client => {
8588
});
8689
```
8790

88-
`.getMasterClientLease()`
91+
`.acquire()`
8992
```javascript
90-
const clientLease = await sentinel.getMasterClientLease();
93+
const clientLease = await sentinel.acquire();
9194

9295
try {
9396
await clientLease.watch('key');

packages/client/lib/sentinel/index.spec.ts

+19-19
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ describe(`test with scripts`, () => {
134134
}, GLOBAL.SENTINEL.WITH_SCRIPT);
135135

136136
testUtils.testWithClientSentinel('with script multi', async sentinel => {
137-
const reply = await sentinel.multi().set('key', 2).square('key').exec();
137+
const reply = await sentinel.multi().set('key', 2).square('key').exec();
138138
assert.deepEqual(reply, ['OK', 4]);
139139
}, GLOBAL.SENTINEL.WITH_SCRIPT);
140140

@@ -148,7 +148,7 @@ describe(`test with scripts`, () => {
148148
);
149149
}, GLOBAL.SENTINEL.WITH_SCRIPT)
150150
});
151-
151+
152152

153153
describe(`test with functions`, () => {
154154
testUtils.testWithClientSentinel('with function', async sentinel => {
@@ -178,14 +178,14 @@ describe(`test with functions`, () => {
178178
MATH_FUNCTION.code,
179179
{ REPLACE: true }
180180
);
181-
181+
182182
const reply = await sentinel.use(
183183
async (client: any) => {
184184
await client.set('key', '2');
185185
return client.math.square('key');
186186
}
187187
);
188-
188+
189189
assert.equal(reply, 4);
190190
}, GLOBAL.SENTINEL.WITH_FUNCTION);
191191
});
@@ -216,7 +216,7 @@ describe(`test with replica pool size 1`, () => {
216216
testUtils.testWithClientSentinel('client lease', async sentinel => {
217217
sentinel.on("error", () => { });
218218

219-
const clientLease = await sentinel.aquire();
219+
const clientLease = await sentinel.acquire();
220220
clientLease.set('x', 456);
221221

222222
let matched = false;
@@ -243,7 +243,7 @@ describe(`test with replica pool size 1`, () => {
243243
return await client.get("x");
244244
}
245245
)
246-
246+
247247
await sentinel.set("x", 1);
248248
assert.equal(await promise, null);
249249
}, GLOBAL.SENTINEL.WITH_REPLICA_POOL_SIZE_1);
@@ -276,7 +276,7 @@ describe(`test with masterPoolSize 2, reserve client true`, () => {
276276
assert.equal(await promise2, "2");
277277
}, Object.assign(GLOBAL.SENTINEL.WITH_RESERVE_CLIENT_MASTER_POOL_SIZE_2, {skipTest: true}));
278278
});
279-
279+
280280
describe(`test with masterPoolSize 2`, () => {
281281
testUtils.testWithClientSentinel('multple clients', async sentinel => {
282282
sentinel.on("error", () => { });
@@ -313,26 +313,26 @@ describe(`test with masterPoolSize 2`, () => {
313313
}, GLOBAL.SENTINEL.WITH_MASTER_POOL_SIZE_2);
314314

315315
testUtils.testWithClientSentinel('lease - watch - clean', async sentinel => {
316-
const leasedClient = await sentinel.aquire();
316+
const leasedClient = await sentinel.acquire();
317317
await leasedClient.set('x', 1);
318318
await leasedClient.watch('x');
319319
assert.deepEqual(await leasedClient.multi().get('x').exec(), ['1'])
320320
}, GLOBAL.SENTINEL.WITH_MASTER_POOL_SIZE_2);
321321

322322
testUtils.testWithClientSentinel('lease - watch - dirty', async sentinel => {
323-
const leasedClient = await sentinel.aquire();
323+
const leasedClient = await sentinel.acquire();
324324
await leasedClient.set('x', 1);
325325
await leasedClient.watch('x');
326326
await leasedClient.set('x', 2);
327327

328328
await assert.rejects(leasedClient.multi().get('x').exec(), new WatchError());
329329
}, GLOBAL.SENTINEL.WITH_MASTER_POOL_SIZE_2);
330330
});
331-
331+
332332

333333
// TODO: Figure out how to modify the test utils
334334
// so it would have fine grained controll over
335-
// sentinel
335+
// sentinel
336336
// it should somehow replicate the `SentinelFramework` object functionallities
337337
async function steadyState(frame: SentinelFramework) {
338338
let checkedMaster = false;
@@ -439,7 +439,7 @@ describe.skip('legacy tests', () => {
439439
sentinel.on('error', () => { });
440440
}
441441

442-
if (this!.currentTest!.state === 'failed') {
442+
if (this!.currentTest!.state === 'failed') {
443443
console.log(`longest event loop blocked delta: ${longestDelta}`);
444444
console.log(`longest event loop blocked in failing test: ${longestTestDelta}`);
445445
console.log("trace:");
@@ -454,7 +454,7 @@ describe.skip('legacy tests', () => {
454454
frame.sentinelMaster(),
455455
frame.sentinelReplicas()
456456
])
457-
457+
458458
console.log(`sentinel sentinels:\n${JSON.stringify(results[0], undefined, '\t')}`);
459459
console.log(`sentinel master:\n${JSON.stringify(results[1], undefined, '\t')}`);
460460
console.log(`sentinel replicas:\n${JSON.stringify(results[2], undefined, '\t')}`);
@@ -492,7 +492,7 @@ describe.skip('legacy tests', () => {
492492
);
493493
});
494494

495-
// stops master to force sentinel to update
495+
// stops master to force sentinel to update
496496
it('stop master', async function () {
497497
this.timeout(60000);
498498

@@ -538,8 +538,8 @@ describe.skip('legacy tests', () => {
538538

539539
tracer.push("connected");
540540

541-
const client = await sentinel.aquire();
542-
tracer.push("aquired lease");
541+
const client = await sentinel.acquire();
542+
tracer.push("acquired lease");
543543

544544
await client.set("x", 1);
545545
await client.watch("x");
@@ -586,7 +586,7 @@ describe.skip('legacy tests', () => {
586586
await sentinel.connect();
587587
tracer.push("connected");
588588

589-
const client = await sentinel.aquire();
589+
const client = await sentinel.acquire();
590590
tracer.push("got leased client");
591591
await client.set("x", 1);
592592
await client.watch("x");
@@ -965,10 +965,10 @@ describe.skip('legacy tests', () => {
965965
tracer.push("adding node");
966966
await frame.addNode();
967967
tracer.push("added node and waiting on added promise");
968-
await nodeAddedPromise;
968+
await nodeAddedPromise;
969969
})
970970
});
971971
});
972972

973973

974-
974+

packages/client/lib/sentinel/index.ts

+74-1
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,30 @@ export class RedisSentinelClient<
3232
#internal: RedisSentinelInternal<M, F, S, RESP, TYPE_MAPPING>;
3333
readonly _self: RedisSentinelClient<M, F, S, RESP, TYPE_MAPPING>;
3434

35+
/**
36+
* Indicates if the client connection is open
37+
*
38+
* @returns `true` if the client connection is open, `false` otherwise
39+
*/
40+
3541
get isOpen() {
3642
return this._self.#internal.isOpen;
3743
}
3844

45+
/**
46+
* Indicates if the client connection is ready to accept commands
47+
*
48+
* @returns `true` if the client connection is ready, `false` otherwise
49+
*/
3950
get isReady() {
4051
return this._self.#internal.isReady;
4152
}
4253

54+
/**
55+
* Gets the command options configured for this client
56+
*
57+
* @returns The command options for this client or `undefined` if none were set
58+
*/
4359
get commandOptions() {
4460
return this._self.#commandOptions;
4561
}
@@ -222,6 +238,16 @@ export class RedisSentinelClient<
222238

223239
unwatch = this.UNWATCH;
224240

241+
/**
242+
* Releases the client lease back to the pool
243+
*
244+
* After calling this method, the client instance should no longer be used as it
245+
* will be returned to the client pool and may be given to other operations.
246+
*
247+
* @returns A promise that resolves when the client is ready to be reused, or undefined
248+
* if the client was immediately ready
249+
* @throws Error if the lease has already been released
250+
*/
225251
release() {
226252
if (this._self.#clientInfo === undefined) {
227253
throw new Error('RedisSentinelClient lease already released');
@@ -245,10 +271,20 @@ export default class RedisSentinel<
245271
#internal: RedisSentinelInternal<M, F, S, RESP, TYPE_MAPPING>;
246272
#options: RedisSentinelOptions<M, F, S, RESP, TYPE_MAPPING>;
247273

274+
/**
275+
* Indicates if the sentinel connection is open
276+
*
277+
* @returns `true` if the sentinel connection is open, `false` otherwise
278+
*/
248279
get isOpen() {
249280
return this._self.#internal.isOpen;
250281
}
251282

283+
/**
284+
* Indicates if the sentinel connection is ready to accept commands
285+
*
286+
* @returns `true` if the sentinel connection is ready, `false` otherwise
287+
*/
252288
get isReady() {
253289
return this._self.#internal.isReady;
254290
}
@@ -511,7 +547,28 @@ export default class RedisSentinel<
511547

512548
pUnsubscribe = this.PUNSUBSCRIBE;
513549

514-
async aquire(): Promise<RedisSentinelClientType<M, F, S, RESP, TYPE_MAPPING>> {
550+
/**
551+
* Acquires a master client lease for exclusive operations
552+
*
553+
* Used when multiple commands need to run on an exclusive client (for example, using `WATCH/MULTI/EXEC`).
554+
* The returned client must be released after use with the `release()` method.
555+
*
556+
* @returns A promise that resolves to a Redis client connected to the master node
557+
* @example
558+
* ```javascript
559+
* const clientLease = await sentinel.acquire();
560+
*
561+
* try {
562+
* await clientLease.watch('key');
563+
* const resp = await clientLease.multi()
564+
* .get('key')
565+
* .exec();
566+
* } finally {
567+
* clientLease.release();
568+
* }
569+
* ```
570+
*/
571+
async acquire(): Promise<RedisSentinelClientType<M, F, S, RESP, TYPE_MAPPING>> {
515572
const clientInfo = await this._self.#internal.getClientLease();
516573
return RedisSentinelClient.create(this._self.#options, this._self.#internal, clientInfo, this._self.#commandOptions);
517574
}
@@ -641,6 +698,12 @@ class RedisSentinelInternal<
641698
});
642699
}
643700

701+
/**
702+
* Gets a client lease from the master client pool
703+
*
704+
* @returns A client info object or a promise that resolves to a client info object
705+
* when a client becomes available
706+
*/
644707
getClientLease(): ClientInfo | Promise<ClientInfo> {
645708
const id = this.#masterClientQueue.shift();
646709
if (id !== undefined) {
@@ -650,6 +713,16 @@ class RedisSentinelInternal<
650713
return this.#masterClientQueue.wait().then(id => ({ id }));
651714
}
652715

716+
/**
717+
* Releases a client lease back to the pool
718+
*
719+
* If the client was used for a transaction that might have left it in a dirty state,
720+
* it will be reset before being returned to the pool.
721+
*
722+
* @param clientInfo The client info object representing the client to release
723+
* @returns A promise that resolves when the client is ready to be reused, or undefined
724+
* if the client was immediately ready or no longer exists
725+
*/
653726
releaseClientLease(clientInfo: ClientInfo) {
654727
const client = this.#masterClients[clientInfo.id];
655728
// client can be undefined if releasing in middle of a reconfigure

packages/client/lib/sentinel/types.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,17 @@ export interface RedisSentinelOptions<
4949
*/
5050
replicaPoolSize?: number;
5151
/**
52-
* TODO
52+
* Interval in milliseconds to periodically scan for changes in the sentinel topology.
53+
* The client will query the sentinel for changes at this interval.
54+
*
55+
* Default: 10000 (10 seconds)
5356
*/
5457
scanInterval?: number;
5558
/**
56-
* TODO
59+
* When `true`, error events from client instances inside the sentinel will be propagated to the sentinel instance.
60+
* This allows handling all client errors through a single error handler on the sentinel instance.
61+
*
62+
* Default: false
5763
*/
5864
passthroughClientErrorEvents?: boolean;
5965
/**

0 commit comments

Comments
 (0)