UNPKG

@clickup/ent-framework

Version:

A PostgreSQL graph-database-alike library with microsharding and row-level security

205 lines 10.3 kB
import type { DesperateAny, MaybeCallable, PickPartial, MaybeAsyncCallable } from "../internal/misc"; import type { Client } from "./Client"; import { Island } from "./Island"; import type { LocalCache } from "./LocalCache"; import type { Loggers } from "./Loggers"; import { Shard } from "./Shard"; import type { ShardNamer } from "./ShardNamer"; /** * Options for Cluster constructor. */ export interface ClusterOptions<TClient extends Client, TNode> { /** Islands configuration of the Cluster. */ islands: MaybeAsyncCallable<ClusterIslands<TNode>>; /** Given a node of some Island, instantiates a Client for this node. Called * when a new node appears in the Cluster statically or dynamically. */ createClient: (node: TNode) => TClient; /** Loggers to be injected into all Clients returned by createClient(). */ loggers: Loggers; /** An instance of LocalCache which may be used for auxiliary purposes when * discovering Shards/Clients. */ localCache?: LocalCache | null; /** How often to recheck for changes in `options.islands`. If it is SYNC, then * by default - often, like every 500 ms (since it's assumed that * `options.islands` calculation is cheap). If it is ASYNC, then by default - * not so often, every `shardsDiscoverIntervalMs` (we assume that getting the * list of Island nodes may be expensive, e.g. fetching from AWS API or so). * If the Islands list here changes, then we trigger Shards rediscovery and * Clients recreation ASAP. */ reloadIslandsIntervalMs?: MaybeCallable<number>; /** Info on how to build/parse Shard names. */ shardNamer?: ShardNamer | null; /** How often to run Shards rediscovery in normal circumstances. */ shardsDiscoverIntervalMs?: MaybeCallable<number>; /** Jitter for shardsDiscoverIntervalMs and reloadIslandsIntervalMs. */ shardsDiscoverIntervalJitter?: MaybeCallable<number>; /** Used in the following situations: * 1. If we think that we know the Island of a particular Shard, but an * attempt to access it fails. This means that maybe the Shard is migrating * to another Island. So, we wait a bit and retry that many times. We * should not do it too many times though, because all DB requests will be * blocked waiting for the resolution. * 2. If we sent a WRITE request to a Client, but it appeared that this Client * is a replica, and the master moved to some other Client. In this case, * we wait a bit and ping all Clients of the Island to refresh, who is * master and who is replica. */ runOnShardErrorRetryCount?: MaybeCallable<number>; /** How much time to wait before we retry rediscovering the entire Cluster * after a Shard-to-Island resolution error. The time here should be just * enough to wait for switching the Shard from one Island to another * (typically quick). */ runOnShardErrorRediscoverClusterDelayMs?: MaybeCallable<number>; /** How much time to wait before sending discover requests to all Clients of * the Island trying to find the new master (or to reconnect). The time here * may reach several seconds, since some DBs shut down the old master and * promote some replica to it not simultaneously. */ runOnShardErrorRediscoverIslandDelayMs?: MaybeCallable<number>; } /** * A type of `ClusterOptions#islands` property. Represents the full list of * Islands and their corresponding Nodes (masters and replicas). */ export type ClusterIslands<TNode> = ReadonlyArray<{ no: number; nodes: readonly TNode[]; }>; /** * Cluster is a collection of Islands and an orchestration of shardNo -> Island * resolution. * * It's unknown beforehand, which Island some particular Shard belongs to; the * resolution is done asynchronously and lazily. * * Shard 0 is a special "global" Shard. */ export declare class Cluster<TClient extends Client, TNode = DesperateAny> { /** Default values for the constructor options. */ static readonly DEFAULT_OPTIONS: Required<PickPartial<ClusterOptions<Client, never>>>; /** The complete registry of all initialized Clients. Cluster nodes may change * at runtime, so once a new node appears, its Client is added to the * registry. Also, the Clients of disappeared nodes are eventually removed * from the registry on the next Shards discovery. */ private clientRegistry; /** The complete registry of all Islands ever created. If some Island changes * configuration, its old version is eventually removed from the registry * during the next Shards discovery. */ private islandRegistry; /** Represents the result of the recent successful call to * `options.islands()`. */ private islandsCache; /** Represents the result of the recent successful Shards discovery. */ private shardsDiscoverCache; /** Once set to true, Clients for newly appearing nodes will be pre-warmed. */ private prewarmEnabled; /** Cluster configuration options. */ readonly options: Required<ClusterOptions<TClient, TNode>>; /** * Initializes the Cluster, but doesn't send any queries yet, even discovery * queries (also, no implicit prewarming). */ constructor(options: ClusterOptions<TClient, TNode>); /** * Signals the Cluster to keep the Clients pre-warmed, e.g. open. (It's up to * the particular Client's implementation, what does a "pre-warmed Client" * mean; typically, it's keeping some minimal number of pooled connections.) * * Except when `randomizedDelayMs` is passed as 0, the actual prewarm (and * Islands discovery) queries will run with a randomized delay between N/2 and * N ms. It is better to operate in such mode: if multiple Node processes * start simultaneously in the cluster, then the randomization helps to avoid * new connections burst (new connections establishment is expensive for e.g. * pgbouncer or when DB is accessed over SSL). */ prewarm(randomizedDelayMs?: number, onInitialPrewarm?: (delayMs: number) => void): void; /** * Returns a global Shard of the Cluster. This method is made synchronous * intentionally, to defer the I/O and possible errors to the moment of the * actual query. */ globalShard(): Shard<TClient>; /** * Returns all currently known (discovered) non-global Shards in the Cluster. */ nonGlobalShards(): Promise<ReadonlyArray<Shard<TClient>>>; /** * Returns Shard of a particular id. This method is made synchronous * intentionally, to defer the I/O and possible errors to the moment of the * actual query. * * Why is it important? Because Shards may go up and down temporarily at * random moments of time. Imagine we made this method async and asserted that * the Shard is actually available at the moment when the method is called. * What would happen if the Shard object was stored somewhere as "successful" * by the caller, then the Island went down, and then a query is sent to the * Shard in, say, 20 seconds? We'd get an absolutely different exception, at * the moment of the query. We don't want this to happen: we want all of the * exceptions to be thrown with a consistent call stack (e.g. at the moment of * the query), no matter whether it was an immediate call or a deferred one. */ shard(id: string): Shard<TClient>; /** * Returns a Shard if we know its number. The idea: for each Shard number * (even for non-discovered yet Shards), we keep the corresponding Shard * object in a Memoize cache, so Shards with the same number always resolve * into the same Shard object. Then, an actual Island locating process happens * when the caller wants to get a Client of that Shard (and it throws if such * Shard hasn't been discovered actually). */ shardByNo(shardNo: number): Shard<TClient>; /** * Returns a random Shard among the ones which are currently known * (discovered) in the Cluster. */ randomShard(seed?: object): Promise<Shard<TClient>>; /** * Returns an Island by its number. */ island(islandNo: number): Promise<Island<TClient>>; /** * Returns all Islands in the Cluster. */ islands(): Promise<Array<Island<TClient>>>; /** * Triggers shards rediscovery and finishes as soon as it's done. To be used * in unit tests mostly, because in real life, it's enough to just modify the * cluster configuration. */ rediscover(what?: "islands" | "shards"): Promise<void>; /** * Runs the body function with retries. The Island injected into the body * function is located automatically by the Shard number. In case of an error * after any run attempt, calls onAttemptError(). */ private runOnShard; /** * Runs the whole-cluster rediscover after a delay, hoping that we'll load the * new Shards-to-Island mapping. * * Multiple concurrent calls to this method will be coalesced into one * (including the delay period): * 1. This protects against the burst of rediscover requests caused by * multiple failing concurrent queries. * 2. It also allows to keep the queries batched when they are retried (i.e. * the whole batch will be retried, not individual queries). */ private rediscoverCluster; /** * Runs Island#rediscover() after a delay. * * Multiple concurrent calls to this method will be coalesced into one * (including the delay period): * 1. This protects against the burst of rediscover requests caused by * multiple failing concurrent queries. * 2. It also allows to keep the queries batched when they are retried (i.e. * the whole batch will be retried, not individual queries). */ private rediscoverIsland; /** * Runs the actual Shards discovery queries over all Islands and updates the * mapping from each Shard number to an Island where it lives. These queries * may be expensive, so it's expected that the returned Promise is heavily * cached by the caller code. */ private shardsDiscoverExpensive; } //# sourceMappingURL=Cluster.d.ts.map