Skip to content

Commit

Permalink
Node: add node cluster mode and standalone examples (#2357)
Browse files Browse the repository at this point in the history
* Node: add node cluster mode example

Signed-off-by: Adar Ovadia <adarov@amazon.com>

---------

Signed-off-by: Adar Ovadia <adarov@amazon.com>
Co-authored-by: Adar Ovadia <adarov@amazon.com>
  • Loading branch information
adarovadya and Adar Ovadia committed Sep 29, 2024
1 parent 2c7ec4d commit 2510ab9
Show file tree
Hide file tree
Showing 4 changed files with 293 additions and 16 deletions.
146 changes: 146 additions & 0 deletions examples/node/cluster_example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/**
* Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
*/

import {
ClosingError,
ConnectionError,
GlideClusterClient,
InfoOptions,
Logger,
RequestError,
TimeoutError,
} from "@valkey/valkey-glide";

/**
* Creates and returns a GlideClusterClient instance.
* This function initializes a GlideClusterClient with the provided list of nodes.
* The nodesList may contain the address of one or more cluster nodes, and the
* client will automatically discover all nodes in the cluster.
* @param nodesList A list of tuples where each tuple contains a host (str) and port (int). Defaults to [("localhost", 6379)].
* @returns An instance of GlideClusterClient connected to the discovered nodes.
*/
async function createClient(nodesList = [{ host: "localhost", port: 6379 }]) {
const addresses = nodesList.map((node) => ({
host: node.host,
port: node.port,
}));

// Check `GlideClusterClientConfiguration` for additional options.
return await GlideClusterClient.createClient({
addresses: addresses,
// if the server uses TLS, you'll need to enable it. Otherwise the connection attempt will time out silently.
// useTLS: true,
});
}

/**
* Executes the main logic of the application, performing basic operations
* such as SET, GET, PING, and INFO REPLICATION using the provided GlideClusterClient.
* @param client An instance of GlideClusterClient.
*/
async function appLogic(client: GlideClusterClient) {
// Send SET and GET
const setResponse = await client.set("foo", "bar");
Logger.log("info", "app", `Set response is: ${setResponse}`);

const getResponse = await client.get("foo");
Logger.log("info", "app", `Get response is: ${getResponse?.toString()}`);

// Send PING to all primaries (according to Redis's PING request_policy)
const pong = await client.ping();
Logger.log("info", "app", `PING response: ${pong}`);

// Send INFO REPLICATION to all nodes
const infoReplResps = await client.info({
sections: [InfoOptions.Replication],
});
const infoReplicationValues = Object.values(infoReplResps);

Logger.log(
"info",
"app",
`INFO REPLICATION responses from all nodes are:\n`,
);

infoReplicationValues.forEach((item) =>
Logger.log("info", "glide", item as string),
);
}

/**
* Executes the application logic with exception handling.
*/
async function execAppLogic() {
// Loop through with exception handling
while (true) {
let client;

try {
client = await createClient();
return await appLogic(client);
} catch (error) {
switch (true) {
case error instanceof ClosingError:
// If the error message contains "NOAUTH", raise the exception
// because it indicates a critical authentication issue.
if ((error as ClosingError).message.includes("NOAUTH")) {
Logger.log(
"error",
"glide",
`Authentication error encountered: ${error}`,
);
} else {
Logger.log(
"warn",
"glide",
`Client has closed and needs to be re-created: ${error}`,
);
}

throw error;
case error instanceof TimeoutError:
// A request timed out. You may choose to retry the execution based on your application's logic
Logger.log("error", "glide", `Timeout error: ${error}`);
throw error;
case error instanceof ConnectionError:
// The client wasn't able to reestablish the connection within the given retries
Logger.log("error", "glide", `Connection error: ${error}`);
throw error;
case error instanceof RequestError:
// Other error reported during a request, such as a server response error
Logger.log(
"error",
"glide",
`RequestError encountered: ${error}`,
);
throw error;
default:
Logger.log("error", "glide", `Unexpected error: ${error}`);
throw error;
}
} finally {
try {
if (client) {
await client.close();
}
} catch (error) {
Logger.log(
"warn",
"glide",
`Error encountered while closing the client: ${error}`,
);
}
}
}
}

function main() {
// In this example, we will utilize the client's logger for all log messages
Logger.setLoggerConfig("info");
// Optional - set the logger to write to a file
// Logger.setLoggerConfig("info", fileName);
execAppLogic();
}

main();
130 changes: 130 additions & 0 deletions examples/node/standalone_example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/**
* Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
*/

import {
ClosingError,
ConnectionError,
GlideClient,
Logger,
RequestError,
TimeoutError,
} from "@valkey/valkey-glide";

/**
* Creates and returns a GlideClient instance.
* This function initializes a GlideClient with the provided list of nodes.
* The nodes_list may contain either only primary node or a mix of primary
* and replica nodes. The GlideClient use these nodes to connect to
* the Standalone setup servers.
* @param nodesList A list of tuples where each tuple contains a host (str) and port (int). Defaults to [("localhost", 6379)].
* @returns An instance of GlideClient connected to the discovered nodes.
*/
async function createClient(nodesList = [{ host: "localhost", port: 6379 }]) {
const addresses = nodesList.map((node) => ({
host: node.host,
port: node.port,
}));

// Check `GlideClientConfiguration` for additional options.
return await GlideClient.createClient({
addresses: addresses,
// if the server uses TLS, you'll need to enable it. Otherwise the connection attempt will time out silently.
// useTLS: true,
});
}

/**
* Executes the main logic of the application, performing basic operations
* such as SET, GET, PING, and INFO REPLICATION using the provided GlideClient.
* @param client An instance of GlideClient.
*/
async function appLogic(client: GlideClient) {
// Send SET and GET
const setResponse = await client.set("foo", "bar");
Logger.log("info", "app", `Set response is: ${setResponse}`);

const getResponse = await client.get("foo");
Logger.log("info", "app", `Get response is: ${getResponse?.toString()}`);

// Send PING to primary
const pong = await client.ping();
Logger.log("info", "app", `PING response: ${pong}`);
}

/**
* Executes the application logic with exception handling.
*/
async function execAppLogic() {
// Loop through with exception handling
while (true) {
let client;

try {
client = await createClient();
return await appLogic(client);
} catch (error) {
switch (true) {
case error instanceof ClosingError:
// If the error message contains "NOAUTH", raise the exception
// because it indicates a critical authentication issue.
if ((error as ClosingError).message.includes("NOAUTH")) {
Logger.log(
"error",
"glide",
`Authentication error encountered: ${error}`,
);
} else {
Logger.log(
"warn",
"glide",
`Client has closed and needs to be re-created: ${error}`,
);
}

throw error;
case error instanceof TimeoutError:
// A request timed out. You may choose to retry the execution based on your application's logic
Logger.log("error", "glide", `Timeout error: ${error}`);
throw error;
case error instanceof ConnectionError:
// The client wasn't able to reestablish the connection within the given retries
Logger.log("error", "glide", `Connection error: ${error}`);
throw error;
case error instanceof RequestError:
// Other error reported during a request, such as a server response error
Logger.log(
"error",
"glide",
`RequestError encountered: ${error}`,
);
throw error;
default:
Logger.log("error", "glide", `Unexpected error: ${error}`);
throw error;
}
} finally {
try {
if (client) {
await client.close();
}
} catch (error) {
Logger.log(
"warn",
"glide",
`Error encountered while closing the client: ${error}`,
);
}
}
}
}

function main() {
// In this example, we will utilize the client's logger for all log messages
Logger.setLoggerConfig("info");
// Optional - set the logger to write to a file
// Logger.setLoggerConfig("info", fileName);
execAppLogic();
}

main();
13 changes: 7 additions & 6 deletions examples/python/cluster_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,13 @@ async def exec_app_logic():
"glide",
f"Authentication error encountered: {e}",
)
raise e
Logger.log(
LogLevel.WARN,
"glide",
f"Client has closed and needs to be re-created: {e}",
)
else:
Logger.log(
LogLevel.WARN,
"glide",
f"Client has closed and needs to be re-created: {e}",
)
raise e
except TimeoutError as e:
# A request timed out. You may choose to retry the execution based on your application's logic
Logger.log(LogLevel.ERROR, "glide", f"TimeoutError encountered: {e}")
Expand Down
20 changes: 10 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"devDependencies": {
"@eslint/js": "^9.10.0",
"@types/eslint__js": "^8.42.3",
"@types/eslint-config-prettier": "^6.11.3",
"eslint": "^9.10.0",
"eslint-config-prettier": "^9.1.0",
"prettier": "^3.3.3",
"typescript": "^5.6.2",
"typescript-eslint": "^8.5.0"
}
"devDependencies": {
"@eslint/js": "^9.10.0",
"@types/eslint__js": "^8.42.3",
"@types/eslint-config-prettier": "^6.11.3",
"eslint": "^9.10.0",
"eslint-config-prettier": "^9.1.0",
"prettier": "^3.3.3",
"typescript": "^5.6.2",
"typescript-eslint": "^8.5.0"
}
}

0 comments on commit 2510ab9

Please sign in to comment.