Skip to content

Commit 4ff7796

Browse files
Added Smart driver features (yugabyte#1)
* Smart Driver Implementation * Smart Driver Implementation * Changes as per review comments * Changes as per review
1 parent 10edbcb commit 4ff7796

File tree

8 files changed

+906
-8
lines changed

8 files changed

+906
-8
lines changed

README.md

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,74 @@
1-
# Rust-Postgres
1+
# YugabyteDB Rust-Postgres Smart Driver
2+
3+
This is a Rust driver for YugabyteDB SQL. This driver is based on [sfackler/rust-postgres](https://github.com/sfackler/rust-postgres), with the following additional connection properties:
4+
5+
- `load_balance` - It expects **true/false** as its possible values.
6+
- `topology_keys` - It takes a comma separated geo-location values. A single geo-location can be given as 'cloud.region.zone'. Multiple geo-locations too can be specified, separated by comma (`,`).
7+
- `yb_servers_refresh_interval` - (default value: 300 seconds) Time interval, in seconds, between two attempts to refresh the information about cluster nodes. Valid values are integers between 0 and 600. Value 0 means refresh for each connection request. Any value outside this range is ignored and the default is used.
8+
- `fallback_to_topology_keys_only` : (default value: false) Applicable only for TopologyAware Load Balancing. When set to true, the smart driver does not attempt to connect to servers outside of primary and fallback placements specified via property. The default behaviour is to fallback to any available server in the entire cluster.
9+
- `failed_host_reconnect_delay_secs` - (default value: 5 seconds) The driver marks a server as failed with a timestamp, when it cannot connect to it. Later, whenever it refreshes the server list via yb_servers(), if it sees the failed server in the response, it marks the server as UP only if failed-host-reconnect-delay-secs time has elapsed.
10+
11+
## Connection load balancing
12+
13+
Users can use this feature in two configurations.
14+
15+
### Cluster-aware / Uniform connection load balancing
16+
17+
In the cluster-aware connection load balancing, connections are distributed across all the tservers in the cluster, irrespective of their placements.
18+
19+
To enable the cluster-aware connection load balancing, provide the parameter `load_balance` set to true as `load_balance=true` in the connection url.
20+
21+
```
22+
"postgresql://127.0.0.1:5433/yugabyte?user=yugabyte&password=yugabyte&load_balance=true"
23+
```
24+
25+
With this parameter specified in the url, the driver will fetch and maintain the list of tservers from the given endpoint (`127.0.0.1` in above example) available in the YugabyteDB cluster and distribute the connections equally across them.
26+
27+
This list is refreshed every 5 minutes, when a new connection request is received.
28+
29+
Application needs to use the same connection url to create every connection it needs, so that the distribution happens equally.
30+
31+
### Topology-aware connection load balancing
32+
33+
With topology-aware connnection load balancing, users can target tservers in specific zones by specifying these zones as `topology_keys` with values in the format `cloudname.regionname.zonename`. Multiple zones can be specified as comma separated values.
34+
35+
The connections will be distributed equally with the tservers in these zones.
36+
37+
Note that, you would still need to specify `load_balance=true` to enable the topology-aware connection load balancing.
38+
39+
```
40+
"postgresql://127.0.0.1:5433/yugabyte?user=yugabyte&password=yugabyte&load_balance=true&topology_keys=cloud1.datacenter1.rack1"
41+
```
42+
### Specifying fallback zones
43+
44+
For topology-aware load balancing, you can now specify fallback placements too. This is not applicable for cluster-aware load balancing.
45+
Each placement value can be suffixed with a colon (`:`) followed by a preference value between 1 and 10.
46+
A preference value of `:1` means it is a primary placement. A preference value of `:2` means it is the first fallback placement and so on. If no preference value is provided, it is considered to be a primary placement (equivalent to one with preference value `:1`). Example given below.
47+
48+
```
49+
"postgresql://127.0.0.1:5433/yugabyte?user=yugabyte&password=yugabyte&load_balance=true&topology_keys=cloud1.region1.zone1:1,cloud1.region1.zone2:2";
50+
```
51+
52+
You can also use `*` for specifying all the zones in a given region as shown below. This is not allowed for cloud or region values.
53+
54+
```
55+
"postgresql://127.0.0.1:5433/yugabyte?user=yugabyte&password=yugabyte&load_balance=true&topology_keys=cloud1.region1.*:1,cloud1.region2.*:2";
56+
```
57+
58+
The driver attempts to connect to a node in following order: the least loaded node in the 1) primary placement(s), else in the 2) first fallback if specified, else in the 3) second fallback if specified and so on.
59+
If no nodes are available either in primary placement(s) or in any of the fallback placements, then nodes in the rest of the cluster are attempted.
60+
And this repeats for each connection request.
61+
62+
## Specifying Refresh Interval
63+
64+
Users can specify Refresh Time Interval, in seconds. It is the time interval between two attempts to refresh the information about cluster nodes. Default is 300. Valid values are integers between 0 and 600. Value 0 means refresh for each connection request. Any value outside this range is ignored and the default is used.
65+
66+
To specify Refresh Interval, use the parameter `yb_servers_refresh_interval` in the connection url.
67+
```
68+
"postgresql://127.0.0.1:5433/yugabyte?user=yugabyte&password=yugabyte&yb_servers_refresh_interval=0&load_balance=true&topology_keys=cloud1.region1.*:1,cloud1.region2.*:2";
69+
```
70+
71+
For working examples which demonstrates both the configurations of connection load balancing using `tokio-postgres::connect()`, see the [driver-examples](https://github.com/yugabyte/driver-examples/tree/main/rust/rust_ysql_driver_examples) repository.
272

373
PostgreSQL support for Rust.
474

postgres/src/client.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::task::Poll;
77
use std::time::Duration;
88
use tokio_postgres::tls::{MakeTlsConnect, TlsConnect};
99
use tokio_postgres::types::{BorrowToSql, ToSql, Type};
10-
use tokio_postgres::{Error, Row, SimpleQueryMessage, Socket};
10+
use tokio_postgres::{Error, Row, SimpleQueryMessage, Socket, close};
1111

1212
/// A synchronous PostgreSQL client.
1313
pub struct Client {
@@ -17,6 +17,7 @@ pub struct Client {
1717

1818
impl Drop for Client {
1919
fn drop(&mut self) {
20+
close(&self.client);
2021
let _ = self.close_inner();
2122
}
2223
}

postgres/src/config.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use crate::connection::Connection;
44
use crate::Client;
55
use log::info;
6+
use std::collections::HashMap;
67
use std::fmt;
78
use std::net::IpAddr;
89
use std::path::Path;
@@ -81,6 +82,22 @@ use tokio_postgres::{Error, Socket};
8182
/// `disable`, hosts and addresses will be tried in the order provided. If set to `random`, hosts will be tried
8283
/// in a random order, and the IP addresses resolved from a hostname will also be tried in a random order. Defaults
8384
/// to `disable`.
85+
/// * `load_balance` - It expects true/false as its possible values. Default value is true.
86+
/// * `topology_keys` - It takes a comma separated geo-location values. A single geo-location can be given as 'cloud.region.zone'.
87+
/// Multiple geo-locations too can be specified, separated by comma (,). Each placement value can be suffixed with a colon (:)
88+
/// followed by a preference value between 1 and 10. A preference value of :1 means it is a primary placement. A preference
89+
/// value of :2 means it is the first fallback placement and so on. If no preference value is provided, it is considered to
90+
/// be a primary placement (equivalent to one with preference value :1).
91+
/// * `yb_servers_refresh_interval` - Time interval, in seconds, between two attempts to refresh the information about cluster nodes.
92+
/// Default is 300. Valid values are integers between 0 and 600. Value 0 means refresh for each connection request. Any value
93+
/// outside this range is ignored and the default is used.
94+
/// * `fallback_to_topology_keys_only` - (default value: false) Applicable only for TopologyAware Load Balancing. When set to true,
95+
/// the smart driver does not attempt to connect to servers outside of primary and fallback placements specified via property.
96+
/// The default behaviour is to fallback to any available server in the entire cluster.
97+
/// * `failed_host_reconnect_delay_secs` - (default value: 5 seconds) The driver marks a server as failed with a timestamp, when it cannot
98+
/// connect to it. Later, whenever it refreshes the server list via yb_servers(), if it sees the failed server in the response,
99+
/// it marks the server as UP only if failed-host-reconnect-delay-secs time has elapsed. (The yb_servers() function does not remove
100+
/// a failed server immediately from its result and retains it for a while.)
84101
///
85102
/// ## Examples
86103
///
@@ -414,6 +431,103 @@ impl Config {
414431
self.config.get_load_balance_hosts()
415432
}
416433

434+
/// YugabyteDB Specific.
435+
///
436+
/// Sets the load balance parameter.
437+
///
438+
/// Defaults to false.
439+
pub fn load_balance(&mut self, load_balance: bool) -> &mut Config {
440+
self.config.load_balance(load_balance);
441+
self
442+
}
443+
444+
/// YugabyteDB Specific.
445+
///
446+
/// Gets the load balance value
447+
pub fn get_load_balance(&self) -> bool {
448+
self.config.get_load_balance()
449+
}
450+
451+
/// YugabyteDB Specific.
452+
///
453+
/// Sets the topology key parameter.
454+
///
455+
/// Defaults to Hashmap::new().
456+
pub fn topology_keys(&mut self, topology_key: &str, priority: i64) -> &mut Config {
457+
self.config.topology_keys(topology_key, priority);
458+
self
459+
}
460+
461+
/// YugabyteDB Specific.
462+
///
463+
/// Gets the host topology keys value.
464+
pub fn get_topology_keys(&self) -> HashMap<i64, Vec<String>> {
465+
self.config.get_topology_keys()
466+
}
467+
468+
/// YugabyteDB Specific.
469+
///
470+
/// Sets the yb_servers_refresh_interval parameter.
471+
///
472+
/// Defaults to 300 sec.
473+
pub fn yb_servers_refresh_interval(
474+
&mut self,
475+
yb_servers_refresh_interval: Duration,
476+
) -> &mut Config {
477+
self.config
478+
.yb_servers_refresh_interval(yb_servers_refresh_interval);
479+
self
480+
}
481+
482+
/// YugabyteDB Specific.
483+
///
484+
/// Gets the yb_servers_refresh_interval value.
485+
pub fn get_yb_servers_refresh_interval(&self) -> Duration {
486+
self.config.get_yb_servers_refresh_interval()
487+
}
488+
489+
/// YugabyteDB Specific.
490+
///
491+
/// Sets the fallback_to_topology_keys_only parameter.
492+
///
493+
/// Defaults to false.
494+
pub fn fallback_to_topology_keys_only(
495+
&mut self,
496+
fallback_to_topology_keys_only: bool,
497+
) -> &mut Config {
498+
self.config
499+
.fallback_to_topology_keys_only(fallback_to_topology_keys_only);
500+
self
501+
}
502+
503+
/// YugabyteDB Specific.
504+
///
505+
/// Gets the fallback_to_topology_keys_only value.
506+
pub fn get_fallback_to_topology_keys_only(&self) -> bool {
507+
self.config.get_fallback_to_topology_keys_only()
508+
}
509+
510+
/// YugabyteDB Specific.
511+
///
512+
/// Sets the failed_host_reconnect_delay_secs parameter.
513+
///
514+
/// Defaults to 5 sec.
515+
pub fn failed_host_reconnect_delay_secs(
516+
&mut self,
517+
failed_host_reconnect_delay_secs: Duration,
518+
) -> &mut Config {
519+
self.config
520+
.failed_host_reconnect_delay_secs(failed_host_reconnect_delay_secs);
521+
self
522+
}
523+
524+
/// YugabyteDB Specific.
525+
///
526+
/// Gets the failed_host_reconnect_delay_secs value.
527+
pub fn get_failed_host_reconnect_delay_secs(&self) -> Duration {
528+
self.config.get_failed_host_reconnect_delay_secs()
529+
}
530+
417531
/// Sets the notice callback.
418532
///
419533
/// This callback will be invoked with the contents of every

tokio-postgres/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ tokio = { version = "1.27", features = ["io-util"] }
6060
tokio-util = { version = "0.7", features = ["codec"] }
6161
rand = "0.8.5"
6262
whoami = "1.4.1"
63+
lazy_static = "1.4.0"
64+
env_logger = "0.10.0"
6365

6466
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
6567
socket2 = { version = "0.5", features = ["all"] }

tokio-postgres/src/client.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,10 @@ impl Client {
214214
self.socket_config = Some(socket_config);
215215
}
216216

217+
pub(crate) fn get_socket_config(&self) -> Option<SocketConfig> {
218+
self.socket_config.clone()
219+
}
220+
217221
/// Creates a new prepared statement.
218222
///
219223
/// Prepared statements can be executed repeatedly, and may contain query parameters (indicated by `$1`, `$2`, etc),

0 commit comments

Comments
 (0)