Module eysql_topology

Host selection, with no processes and no I/O.

Description

Host selection, with no processes and no I/O.

This is the part of a YugabyteDB smart driver that decides where the next connection goes: filter servers by node type and by topology preference, then take the least-loaded one. It also parses topology keys, computes the back-off for hosts that failed, and decides whether servers are reached at their host or their public IP.

Placement names match without regard to case, as the JDBC driver's CloudPlacement compares them with equalsIgnoreCase. They are case-folded once, when keys are parsed and when servers are discovered (casefold_placement/1), so that a comparison is a plain match.

Data Types

address()

address() = inet:ip_address() | term()

address_basis()

address_basis() = decided | all_public | unresolved_public | unknown

column()

column() = host | public_ip

decision()

decision() = column() | undecided

key()

key() = {Host::binary(), Port::inet:port_number()}

load_balance()

load_balance() = false | true | any | only_primary | only_rr | prefer_primary | prefer_rr

node_type()

node_type() = primary | read_replica

resolved()

resolved() = {ok, address()} | error

server()

server() = #{host := binary(), port := inet:port_number(), node_type := node_type(), cloud := binary(), region := binary(), zone := binary(), public_ip => binary()}

topology_key()

topology_key() = {binary(), binary(), binary() | '*', 1..10}

Function Index

address_column/3Which of the servers' two addresses to connect to, decided as the JDBC driver's LoadBalanceService.refresh decides it, from addresses resolved beforehand: Answered, the address of the name the discovery dialled (the driver's getConnectedInetAddress), and for each server, in the order yb_servers() gave them, its host's address and its public IP's (none for a server with no public IP).
allowed/5The servers a pool may keep connections on.
backoff/3Milliseconds a host stays out after Failures consecutive failures.
candidates/5The servers a new connection may go to.
casefold_placement/1The servers with their cloud, region and zone case-folded, as parse_keys/1 folds the keys' names.
choose/2Take the least-loaded candidate.
key/1
matches/2Whether a server is in the placement a topology key names.
parse_keys/1Parse topology keys in the smart drivers' syntax, for example "gcp.us-east1.us-east1-b:1,gcp.us-east1.*:2".
seed/2A server for a configured seed host, before discovery.
use_public_ip/1The servers with their public IP as the host, where they have one.

Function Details

address_column/3

address_column(Column::decision(), Answered::address(), Servers::[{resolved(), resolved() | none}]) -> {decision(), column(), address_basis()}

Which of the servers' two addresses to connect to, decided as the JDBC driver's LoadBalanceService.refresh decides it, from addresses resolved beforehand: Answered, the address of the name the discovery dialled (the driver's getConnectedInetAddress), and for each server, in the order yb_servers() gave them, its host's address and its public IP's (none for a server with no public IP). A server's name that did not resolve is error, as the driver takes an UnknownHostException for null there.

Returns the decision to keep, the column to use now, and why:

allowed/5

allowed(Servers::[server()], Available::fun((server()) -> boolean()), LoadBalance::load_balance(), Keys::[topology_key()], FallbackOnly::boolean()) -> [server()]

The servers a pool may keep connections on.

Available is the servers that are up. eysql_cluster passes every server that has not failed since a connection to it last opened, whatever its back-off, so a failure alone never moves a connection. A server is allowed while it is in either of two levels (node type, then topology preference), each taken whole, failed servers included:

So while a preferred server is down, it keeps its connections and the fallback level keeps those opened there meanwhile. Once it is up again the two levels are the same, and connections on the fallback level are no longer allowed. With no server up, only the second level counts.

backoff/3

backoff(Failures::pos_integer(), Base::non_neg_integer(), Max::pos_integer() | undefined) -> non_neg_integer()

Milliseconds a host stays out after Failures consecutive failures. With Max undefined, Base every time, as the smart drivers do; else Base doubled per failure, capped at Max. A Base of 0 stays 0: the host is due at the next refresh, however often it fails.

The doubling stops after 32 failures, by when it is past any cap: bsl would otherwise build an integer as many bits long as the failure count, and for a host that has failed some million times, raise system_limit.

candidates/5

candidates(Servers::[server()], Available::fun((server()) -> boolean()), LoadBalance::load_balance(), Keys::[topology_key()], FallbackOnly::boolean()) -> [server()]

The servers a new connection may go to.

Available excludes hosts that failed and have not been found back since; eysql_cluster lets every host through only for its last resort, the seeds. Node type is applied first, then topology: the lowest preference level with any server wins. With no match at any level, all typed servers are candidates, unless FallbackOnly is set, in which case there are none.

prefer_primary and prefer_rr ignore FallbackOnly, as the smart drivers do. They apply the topology levels to the preferred node type, then take that type anywhere in the cluster, and only while no server of that type is available do they take the other type, anywhere in the cluster.

casefold_placement/1

casefold_placement(Servers::[server()]) -> [server()]

The servers with their cloud, region and zone case-folded, as parse_keys/1 folds the keys' names. eysql_cluster stores discovered servers this way, so matches/2 compares them as they are.

choose/2

choose(Candidates::[server()], Load::fun((server()) -> non_neg_integer())) -> {ok, server()} | {error, no_server_available}

Take the least-loaded candidate. Load counts this client's connections to a server. A tie goes to one of the tied servers at random, as the JDBC driver's getLeastLoadedServer draws one from its minConnectionsHostList.

key/1

key(X1::server()) -> key()

matches/2

matches(X1::topology_key(), X2::server()) -> boolean()

Whether a server is in the placement a topology key names. Both are compared as they are: the server's placement must be case-folded, as casefold_placement/1 leaves it, for the match to ignore case.

parse_keys/1

parse_keys(Keys::term()) -> {ok, [topology_key()]} | {error, {invalid_topology_key, term()}}

Parse topology keys in the smart drivers' syntax, for example "gcp.us-east1.us-east1-b:1,gcp.us-east1.*:2". A key without :N has preference 1.

Keys can also be given parsed, as {Cloud, Region, Zone, Preference} tuples. The names may be atoms or non-empty text; zone *, in any of those forms, matches every zone; the preference is 1 to 10.

Text is Unicode, by the rule every text option follows: a string, a UTF-8 binary, or a mix of both. Anything else, such as an atom or a binary that is not UTF-8, is {invalid_topology_key, Keys}.

The names come back case-folded with string:casefold/1, so that "GCP.US-East1.*" matches the servers of gcp.us-east1, as in the JDBC driver.

seed/2

seed(Host::binary(), Port::inet:port_number()) -> server()

A server for a configured seed host, before discovery. Its node type and empty placement only fill the fields: eysql_cluster takes the seeds whole, as the smart drivers make a plain connection to the hosts in their URL, and never filters them by type or zone.

use_public_ip/1

use_public_ip(Servers::[server()]) -> [server()]

The servers with their public IP as the host, where they have one.


Generated by EDoc