@ydbjs/core
Version:
Core driver for YDB: manages connections, endpoint discovery, authentication, and service client creation. Foundation for all YDB client operations.
165 lines • 6 kB
JavaScript
import {} from '@ydbjs/api/discovery';
import { loggers } from '@ydbjs/debug';
import { LazyConnection } from './conn.js';
let dbg = loggers.driver.extend('pool');
const PESSIMIZATION_TIMEOUT_MS = 60_000;
export class ConnectionPool {
connections = new Set();
pessimized = new Set();
#channelOptions;
#channelCredentials;
constructor(channelCredentials, channelOptions) {
dbg.log('creating connection pool');
this.#channelCredentials = channelCredentials;
if (channelOptions) {
this.#channelOptions = channelOptions;
}
}
/**
* Get a channel based on load balancing rules
* @param preferNodeId The preferred node id to use
* @returns A connection from the pool
*/
acquire(preferNodeId) {
let candidate = null;
this.#refreshPessimizedChannels();
// Try to find preferred node or any good connection
for (let connection of this.connections) {
candidate ??= connection;
if (connection.nodeId === preferNodeId) {
dbg.log('returning preferred connection to node %d', preferNodeId);
return connection;
}
}
if (candidate) {
// Move to end of set for round-robin
this.connections.delete(candidate);
this.connections.add(candidate);
dbg.log('returning round-robin connection to node %d (pool size: %d)', candidate.nodeId, this.connections.size);
return candidate;
}
// Fallback to pessimized connections
dbg.log('no good connections available, falling back to pessimized connections');
for (let connection of this.pessimized) {
candidate ??= connection;
if (connection.nodeId === preferNodeId) {
dbg.log('returning preferred pessimized connection to node %d', preferNodeId);
return connection;
}
}
if (candidate) {
this.pessimized.delete(candidate);
this.pessimized.add(candidate);
dbg.log('returning pessimized connection to node %d (pessimized size: %d)', candidate.nodeId, this.pessimized.size);
return candidate;
}
dbg.log('no connections available in pool');
throw new Error('No connection available');
}
/**
* Release a connection back to the pool
* @param conn The connection to release
* @returns The connection pool instance
*/
release(conn) {
this.connections.delete(conn);
this.connections.add(conn);
this.pessimized.delete(conn);
this.pessimized.add(conn);
dbg.log('released connection to node %d address %s', conn.nodeId, conn.address);
return this;
}
/**
* Add a new connection to the pool
* @param endpoint The endpoint information for the new connection
*/
add(endpoint) {
let connection = this.findByNodeId(BigInt(endpoint.nodeId));
if (connection) {
dbg.log('replacing existing connection to node %d', endpoint.nodeId);
this.remove(connection);
connection.channel.close();
}
connection = new LazyConnection(endpoint, this.#channelCredentials, this.#channelOptions);
this.connections.add(connection);
dbg.log('added connection to node %d address %s (pool size: %d)', connection.nodeId, connection.address, this.connections.size);
return this;
}
/**
* Find a connection by node id
* @param nodeId The node id to search for
* @returns The connection if found, undefined otherwise
*/
findByNodeId(nodeId) {
for (let connection of this.connections) {
if (connection.nodeId === nodeId) {
return connection;
}
}
for (let connection of this.pessimized) {
if (connection.nodeId === nodeId) {
return connection;
}
}
return undefined;
}
/**
* Remove a connection from the pool
* @param connection The connection to remove
*/
remove(connection) {
this.connections.delete(connection);
this.pessimized.delete(connection);
dbg.log('removed connection to node %d address %s', connection.nodeId, connection.address);
return this;
}
/**
* Pessimize a connection for a set amount of time
* @param connection The connection to pessimize
*/
pessimize(connection) {
connection.pessimizedUntil = Date.now() + PESSIMIZATION_TIMEOUT_MS;
this.pessimized.add(connection);
this.connections.delete(connection);
dbg.log('pessimized node %d address %s', connection.nodeId, connection.address);
return this;
}
/**
* Check pessimized channels and restore them if the timeout has elapsed
*/
#refreshPessimizedChannels() {
let now = Date.now();
for (let connection of this.pessimized) {
if (connection.pessimizedUntil < now) {
this.pessimized.delete(connection);
this.connections.add(connection);
dbg.log('un-pessimized node %d address %s', connection.nodeId, connection.address);
}
}
}
/**
* Close all connections in the pool
*/
close() {
dbg.log('closing connection pool (%d active, %d pessimized)', this.connections.size, this.pessimized.size);
for (let connection of this.connections) {
connection.close();
}
for (let connection of this.pessimized) {
connection.close();
}
this.connections.clear();
this.pessimized.clear();
dbg.log('connection pool closed');
}
/**
* Destroy the connection pool.
*/
destroy() {
this.close();
}
[Symbol.dispose]() {
return this.destroy();
}
}
//# sourceMappingURL=pool.js.map