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.
address() = inet:ip_address() | term()
address_basis() = decided | all_public | unresolved_public | unknown
column() = host | public_ip
decision() = column() | undecided
key() = {Host::binary(), Port::inet:port_number()}
load_balance() = false | true | any | only_primary | only_rr | prefer_primary | prefer_rr
node_type() = primary | read_replica
resolved() = {ok, address()} | error
server() = #{host := binary(), port := inet:port_number(), node_type := node_type(), cloud := binary(), region := binary(), zone := binary(), public_ip => binary()}
topology_key() = {binary(), binary(), binary() | '*', 1..10}
| address_column/3 | 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). |
| allowed/5 | The servers a pool may keep connections on. |
| backoff/3 | Milliseconds a host stays out after Failures consecutive failures. |
| candidates/5 | The servers a new connection may go to. |
| casefold_placement/1 | The servers with their cloud, region and zone case-folded, as
parse_keys/1 folds the keys' names. |
| choose/2 | Take the least-loaded candidate. |
| key/1 | |
| matches/2 | Whether a server is in the placement a topology key names. |
| parse_keys/1 | Parse topology keys in the smart drivers' syntax, for example
"gcp.us-east1.us-east1-b:1,gcp.us-east1.*:2". |
| seed/2 | A server for a configured seed host, before discovery. |
| use_public_ip/1 | The servers with their public IP as the host, where they have one. |
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:
decided): the driver keeps
its useHostColumn for the cluster once it is set, whatever later
refreshes find.host; one whose
public IP is means public_ip, since a client outside the cluster's
network reaches it at a public IP; and one whose host and public IP
are the same address means host.public_ip when every server has a public IP and every one
resolves (all_public), host when every server has one but some
do not resolve (unresolved_public), and host otherwise
(unknown).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:
candidates/5 takes its candidates from;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.
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(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.
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(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.
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(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}.
string:casefold/1, so that
"GCP.US-East1.*" matches the servers of gcp.us-east1, as in the JDBC
driver.
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.
The servers with their public IP as the host, where they have one.
Generated by EDoc