Skip to content

Commit bf168bf

Browse files
affonsovxShinnRyuu
authored andcommitted
Node: Add Multi-Database Support for Cluster Mode (Valkey 9.0) (#4657)
* - Implement database selection for cluster clients when database_id != 0 - Send SELECT command to all cluster nodes using MultiNode routing - Add comprehensive error handling with clear error messages - Include test for database selection error handling in cluster mode - Support backward compatibility with servers that don't support multi-db cluster mode - Enable cluster-databases=16 for Valkey 9.0+ in cluster manager This enables cluster clients to work with non-default databases on Valkey 9.0+ servers configured with cluster-databases support, while gracefully handling older servers that don't support this feature. Signed-off-by: affonsov <67347924+affonsov@users.noreply.github.com> * update valkey9 multi db tests Signed-off-by: affonsov <67347924+affonsov@users.noreply.github.com> * fix lint Signed-off-by: affonsov <67347924+affonsov@users.noreply.github.com> * fixing valkey 9 cluster tests Signed-off-by: affonsov <67347924+affonsov@users.noreply.github.com> * add to route to all nodes in the core Signed-off-by: affonsov <67347924+affonsov@users.noreply.github.com> * rust lint fix Signed-off-by: affonsov <67347924+affonsov@users.noreply.github.com> * implement database selection in cluster mode via connection parameters - Add database_id field to ClusterParams and BuilderParams structs - Add database() method to ClusterClientBuilder for setting database ID - Pass database_id through connection info instead of post-connection SELECT - Remove SELECT command from cluster routing - Remove post-connection SELECT command logic from create_cluster_client - Add comprehensive tests for database isolation and reconnection behavior - Verify database ID persistence across node reconnections - Test multi-database cluster support with proper isolation verification This change moves database selection from a post-connection SELECT command to a connection parameter, which is more reliable for cluster mode and handles reconnections automatically. The approach works with servers that support multi-database cluster configurations (like Valkey 9.0+). Signed-off-by: affonsov <67347924+affonsov@users.noreply.github.com> * add valkey9 constraint in the core tests Signed-off-by: affonsov <67347924+affonsov@users.noreply.github.com> * fix core test not skipping test when version was lower than valkey 9 Signed-off-by: affonsov <67347924+affonsov@users.noreply.github.com> * Fix version check Signed-off-by: affonsov <67347924+affonsov@users.noreply.github.com> * refactored test test_set_database_id_after_reconnection to be similar from standalone removed is_valkey_9_or_higher Signed-off-by: affonsov <67347924+affonsov@users.noreply.github.com> * removed tests and cleanup Signed-off-by: affonsov <67347924+affonsov@users.noreply.github.com> * renamed builder.database to builder.database_id Signed-off-by: affonsov <67347924+affonsov@users.noreply.github.com> * node changes Signed-off-by: affonsov <67347924+affonsov@users.noreply.github.com> * move tests to sharedtests.ts Signed-off-by: affonsov <67347924+affonsov@users.noreply.github.com> * reafactoring to select to all nodes being in the core Signed-off-by: affonsov <67347924+affonsov@users.noreply.github.com> * node: Remove SELECT command support from cluster client - Remove select() method from GlideClusterClient - Remove createSelect import from cluster client - Remove extensive test coverage for SELECT functionality - Remove database ID validation tests from client internals - Add minimal database ID test for cluster client configuration - Clean up ConfigurationError import that's no longer needed This change removes the SELECT command implementation that was added for Valkey 9.0+ cluster support, likely due to reliability concerns with database switching in cluster mode or to simplify the API. Signed-off-by: affonsov <67347924+affonsov@users.noreply.github.com> * added changelog Signed-off-by: affonsov <67347924+affonsov@users.noreply.github.com> * fix test, and removed comment Signed-off-by: affonsov <67347924+affonsov@users.noreply.github.com> --------- Signed-off-by: affonsov <67347924+affonsov@users.noreply.github.com>
1 parent f20b79c commit bf168bf

File tree

6 files changed

+97
-14
lines changed

6 files changed

+97
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* JAVA: Valkey 9 new commands HASH items expiration ([#4556](https://github.com/valkey-io/valkey-glide/pull/4556))
77
* NODE: Valkey 9 support - Add Hash Field Expiration Commands Support ([#4598](https://github.com/valkey-io/valkey-glide/pull/4598))
88
* Python: Valkey 9 new hash field expiration commands ([#4610](https://github.com/valkey-io/valkey-glide/pull/4610))
9+
* Node: Add Multi-Database Support for Cluster Mode (Valkey 9.0) ([#4657](https://github.com/valkey-io/valkey-glide/pull/4657))
910
* Java: Multi-Database Support for Cluster Mode Valkey 9.0 ([#4658](https://github.com/valkey-io/valkey-glide/pull/4658))
1011
* Go: Add Multi-Database Support for Cluster Mode Valkey 9.0 ([#4660](https://github.com/valkey-io/valkey-glide/pull/4660))
1112
* Python: Add Multi-Database Support for Cluster Mode Valkey 9.0 ([#4659](https://github.com/valkey-io/valkey-glide/pull/4659))

node/src/BaseClient.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,13 @@ export type ReadFrom =
563563
* - **Standalone Mode**: In standalone mode, only the provided nodes will be used.
564564
* - **Lazy Connect**: Set `lazyConnect` to `true` to defer connection establishment until the first command is sent.
565565
*
566+
* ### Database Selection
567+
*
568+
* - **Database ID**: Use `databaseId` to specify which logical database to connect to (0-15 by default).
569+
* - **Cluster Mode**: Requires Valkey 9.0+ with multi-database cluster mode enabled.
570+
* - **Standalone Mode**: Works with all Valkey versions.
571+
* - **Reconnection**: Database selection persists across reconnections.
572+
*
566573
* ### Security Settings
567574
*
568575
* - **TLS**: Enable secure communication using `useTLS`.
@@ -610,6 +617,7 @@ export type ReadFrom =
610617
* { host: 'redis-node-1.example.com', port: 6379 },
611618
* { host: 'redis-node-2.example.com' }, // Defaults to port 6379
612619
* ],
620+
* databaseId: 5, // Connect to database 5
613621
* useTLS: true,
614622
* credentials: {
615623
* username: 'myUser',
@@ -655,6 +663,33 @@ export interface BaseClientConfiguration {
655663
*/
656664
port?: number;
657665
}[];
666+
/**
667+
* Index of the logical database to connect to.
668+
*
669+
* @remarks
670+
* - **Standalone Mode**: Works with all Valkey versions.
671+
* - **Cluster Mode**: Requires Valkey 9.0+ with multi-database cluster mode enabled.
672+
* - **Reconnection**: Database selection persists across reconnections.
673+
* - **Default**: If not specified, defaults to database 0.
674+
* - **Range**: Must be non-negative. The server will validate the upper limit based on its configuration.
675+
* - **Server Validation**: The server determines the maximum database ID based on its `databases` configuration (standalone) or `cluster-databases` configuration (cluster mode).
676+
*
677+
* @example
678+
* ```typescript
679+
* // Connect to database 5
680+
* const config: BaseClientConfiguration = {
681+
* addresses: [{ host: 'localhost', port: 6379 }],
682+
* databaseId: 5
683+
* };
684+
*
685+
* // Connect to a higher database ID (server will validate the limit)
686+
* const configHighDb: BaseClientConfiguration = {
687+
* addresses: [{ host: 'localhost', port: 6379 }],
688+
* databaseId: 100
689+
* };
690+
* ```
691+
*/
692+
databaseId?: number;
658693
/**
659694
* True if communication with the cluster should use Transport Level Security.
660695
* Should match the TLS configuration of the server/cluster,
@@ -1164,15 +1199,13 @@ export class BaseClient {
11641199
}
11651200
}
11661201

1167-
/**
1168-
* @internal
1169-
*/
11701202
protected constructor(
11711203
socket: net.Socket,
11721204
options?: BaseClientConfiguration,
11731205
) {
11741206
// if logger has been initialized by the external-user on info level this log will be shown
11751207
Logger.log("info", "Client lifetime", `construct client`);
1208+
11761209
this.config = options;
11771210
this.requestTimeout =
11781211
options?.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT_IN_MILLISECONDS;
@@ -8952,6 +8985,7 @@ export class BaseClient {
89528985
clusterModeEnabled: false,
89538986
readFrom,
89548987
authenticationInfo,
8988+
databaseId: options.databaseId,
89558989
inflightRequestsLimit: options.inflightRequestsLimit,
89568990
clientAz: options.clientAz ?? null,
89578991
connectionRetryStrategy: options.connectionBackoff,

node/src/Batch.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4384,6 +4384,14 @@ export class Batch extends BaseBatch<Batch> {
43844384
/**
43854385
* Change the currently selected database.
43864386
*
4387+
* **WARNING**: This command is NOT RECOMMENDED for production use.
4388+
* Upon reconnection, the client will revert to the database_id specified
4389+
* in the client configuration (default: 0), NOT the database selected
4390+
* via this command.
4391+
*
4392+
* **RECOMMENDED APPROACH**: Use the `databaseId` parameter in client
4393+
* configuration instead of using SELECT in batch operations.
4394+
*
43874395
* @see {@link https://valkey.io/commands/select/|valkey.io} for details.
43884396
*
43894397
* @param index - The index of the database to select.

node/src/GlideClient.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -102,19 +102,19 @@ export namespace GlideClientConfiguration {
102102
/**
103103
* Configuration options for creating a {@link GlideClient | GlideClient}.
104104
*
105-
* Extends `BaseClientConfiguration` with properties specific to `GlideClient`, such as database selection,
105+
* Extends `BaseClientConfiguration` with properties specific to `GlideClient`, such as
106106
* reconnection strategies, and Pub/Sub subscription settings.
107107
*
108108
* @remarks
109109
* This configuration allows you to tailor the client's behavior when connecting to a standalone Valkey Glide server.
110110
*
111-
* - **Database Selection**: Use `databaseId` to specify which logical database to connect to.
111+
* - **Database Selection**: Use `databaseId` (inherited from BaseClientConfiguration) to specify which logical database to connect to.
112112
* - **Pub/Sub Subscriptions**: Predefine Pub/Sub channels and patterns to subscribe to upon connection establishment.
113113
*
114114
* @example
115115
* ```typescript
116116
* const config: GlideClientConfiguration = {
117-
* databaseId: 1,
117+
* databaseId: 1, // Inherited from BaseClientConfiguration
118118
* pubsubSubscriptions: {
119119
* channelsAndPatterns: {
120120
* [GlideClientConfiguration.PubSubChannelModes.Pattern]: new Set(['news.*']),
@@ -127,10 +127,6 @@ export namespace GlideClientConfiguration {
127127
* ```
128128
*/
129129
export type GlideClientConfiguration = BaseClientConfiguration & {
130-
/**
131-
* index of the logical database to connect to.
132-
*/
133-
databaseId?: number;
134130
/**
135131
* PubSub subscriptions to be used for the client.
136132
* Will be applied via SUBSCRIBE/PSUBSCRIBE commands during connection establishment.
@@ -173,7 +169,6 @@ export class GlideClient extends BaseClient {
173169
options: GlideClientConfiguration,
174170
): connection_request.IConnectionRequest {
175171
const configuration = super.createClientRequest(options);
176-
configuration.databaseId = options.databaseId;
177172

178173
this.configurePubsub(options, configuration);
179174

@@ -409,16 +404,32 @@ export class GlideClient extends BaseClient {
409404
/**
410405
* Changes the currently selected database.
411406
*
407+
* **WARNING**: This command is NOT RECOMMENDED for production use.
408+
* Upon reconnection, the client will revert to the database_id specified
409+
* in the client configuration (default: 0), NOT the database selected
410+
* via this command.
411+
*
412+
* **RECOMMENDED APPROACH**: Use the `databaseId` parameter in client
413+
* configuration instead:
414+
*
415+
* ```typescript
416+
* const client = await GlideClient.createClient({
417+
* addresses: [{ host: "localhost", port: 6379 }],
418+
* databaseId: 5 // Recommended: persists across reconnections
419+
* });
420+
* ```
421+
*
412422
* @see {@link https://valkey.io/commands/select/|valkey.io} for details.
413423
*
414424
* @param index - The index of the database to select.
415425
* @returns A simple `"OK"` response.
416426
*
417427
* @example
418428
* ```typescript
419-
* // Example usage of select method
429+
* // Example usage of select method (NOT RECOMMENDED)
420430
* const result = await client.select(2);
421431
* console.log(result); // Output: 'OK'
432+
* // Note: Database selection will be lost on reconnection!
422433
* ```
423434
*/
424435
public async select(index: number): Promise<"OK"> {

node/src/GlideClusterClient.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,11 @@ export namespace GlideClusterClientConfiguration {
166166
* @example
167167
* ```typescript
168168
* const config: GlideClusterClientConfiguration = {
169+
* addresses: [
170+
* { host: 'cluster-node-1.example.com', port: 6379 },
171+
* { host: 'cluster-node-2.example.com', port: 6379 },
172+
* ],
173+
* databaseId: 5, // Connect to database 5 (requires Valkey 9.0+ with multi-database cluster mode)
169174
* periodicChecks: {
170175
* duration_in_sec: 30, // Perform periodic checks every 30 seconds
171176
* },
@@ -544,12 +549,12 @@ export class GlideClusterClient extends BaseClient {
544549
/**
545550
* Creates a new `GlideClusterClient` instance and establishes connections to a Valkey Cluster.
546551
*
547-
* @param options - The configuration options for the client, including cluster addresses, authentication credentials, TLS settings, periodic checks, and Pub/Sub subscriptions.
552+
* @param options - The configuration options for the client, including cluster addresses, database selection, authentication credentials, TLS settings, periodic checks, and Pub/Sub subscriptions.
548553
* @returns A promise that resolves to a connected `GlideClusterClient` instance.
549554
*
550555
* @remarks
551556
* Use this static method to create and connect a `GlideClusterClient` to a Valkey Cluster.
552-
* The client will automatically handle connection establishment, including cluster topology discovery and handling of authentication and TLS configurations.
557+
* The client will automatically handle connection establishment, including cluster topology discovery, database selection, and handling of authentication and TLS configurations.
553558
*
554559
* @example
555560
* ```typescript
@@ -561,6 +566,7 @@ export class GlideClusterClient extends BaseClient {
561566
* { host: 'address1.example.com', port: 6379 },
562567
* { host: 'address2.example.com', port: 6379 },
563568
* ],
569+
* databaseId: 5, // Connect to database 5 (requires Valkey 9.0+)
564570
* credentials: {
565571
* username: 'user1',
566572
* password: 'passwordA',
@@ -589,6 +595,7 @@ export class GlideClusterClient extends BaseClient {
589595
*
590596
* @remarks
591597
* - **Cluster Topology Discovery**: The client will automatically discover the cluster topology based on the seed addresses provided.
598+
* - **Database Selection**: Use `databaseId` to specify which logical database to connect to. Requires Valkey 9.0+ with multi-database cluster mode enabled.
592599
* - **Authentication**: If `credentials` are provided, the client will attempt to authenticate using the specified username and password.
593600
* - **TLS**: If `useTLS` is set to `true`, the client will establish secure connections using TLS.
594601
* Should match the TLS configuration of the server/cluster, otherwise the connection attempt will fail.

node/tests/GlideClusterClient.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2868,4 +2868,26 @@ describe("GlideClusterClient", () => {
28682868
},
28692869
TIMEOUT,
28702870
);
2871+
2872+
it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
2873+
"should pass database id for cluster client_%p",
2874+
async (protocol) => {
2875+
// Skip test if version is below 9.0.0 (Valkey 9)
2876+
if (cluster.checkIfServerVersionLessThan("9.0.0")) return;
2877+
2878+
const client = await GlideClusterClient.createClient(
2879+
getClientConfigurationOption(cluster.getAddresses(), protocol, {
2880+
databaseId: 1,
2881+
}),
2882+
);
2883+
2884+
try {
2885+
// Simple test to verify the client works with the database ID
2886+
expect(await client.ping()).toEqual("PONG");
2887+
} finally {
2888+
client.close();
2889+
}
2890+
},
2891+
TIMEOUT,
2892+
);
28712893
});

0 commit comments

Comments
 (0)