2022-03-10 01:33:29 -08:00
|
|
|
/// Parse the configuration file.
|
2022-06-24 14:52:38 -07:00
|
|
|
use arc_swap::ArcSwap;
|
2022-02-20 22:47:08 -08:00
|
|
|
use log::{error, info};
|
2022-02-19 13:57:35 -08:00
|
|
|
use once_cell::sync::Lazy;
|
2023-02-15 15:19:16 -06:00
|
|
|
use regex::Regex;
|
2022-07-28 17:42:04 -05:00
|
|
|
use serde_derive::{Deserialize, Serialize};
|
2023-02-21 21:53:10 -06:00
|
|
|
use std::collections::hash_map::DefaultHasher;
|
2022-09-23 12:06:07 -07:00
|
|
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
2023-02-21 21:53:10 -06:00
|
|
|
use std::hash::{Hash, Hasher};
|
2022-06-27 17:01:40 -07:00
|
|
|
use std::path::Path;
|
2022-03-10 01:33:29 -08:00
|
|
|
use std::sync::Arc;
|
2022-02-08 09:25:59 -08:00
|
|
|
use tokio::fs::File;
|
|
|
|
|
use tokio::io::AsyncReadExt;
|
|
|
|
|
|
|
|
|
|
use crate::errors::Error;
|
2022-09-20 21:47:32 -04:00
|
|
|
use crate::pool::{ClientServerMap, ConnectionPool};
|
2022-09-28 09:50:14 -04:00
|
|
|
use crate::sharding::ShardingFunction;
|
2022-06-27 17:01:14 -07:00
|
|
|
use crate::tls::{load_certs, load_keys};
|
2022-02-08 09:25:59 -08:00
|
|
|
|
2022-07-27 21:47:55 -05:00
|
|
|
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
|
|
|
|
2022-03-10 01:33:29 -08:00
|
|
|
/// Globally available configuration.
|
2022-02-19 13:57:35 -08:00
|
|
|
static CONFIG: Lazy<ArcSwap<Config>> = Lazy::new(|| ArcSwap::from_pointee(Config::default()));
|
|
|
|
|
|
2022-03-10 01:33:29 -08:00
|
|
|
/// Server role: primary or replica.
|
2022-07-28 17:42:04 -05:00
|
|
|
#[derive(Clone, PartialEq, Serialize, Deserialize, Hash, std::cmp::Eq, Debug, Copy)]
|
2022-02-09 20:02:20 -08:00
|
|
|
pub enum Role {
|
2022-09-22 13:07:02 -04:00
|
|
|
#[serde(alias = "primary", alias = "Primary")]
|
2022-02-09 20:02:20 -08:00
|
|
|
Primary,
|
2022-09-22 13:07:02 -04:00
|
|
|
#[serde(alias = "replica", alias = "Replica")]
|
2022-02-09 20:02:20 -08:00
|
|
|
Replica,
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-28 17:22:28 -08:00
|
|
|
impl ToString for Role {
|
|
|
|
|
fn to_string(&self) -> String {
|
|
|
|
|
match *self {
|
|
|
|
|
Role::Primary => "primary".to_string(),
|
|
|
|
|
Role::Replica => "replica".to_string(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-18 07:10:18 -08:00
|
|
|
impl PartialEq<Option<Role>> for Role {
|
|
|
|
|
fn eq(&self, other: &Option<Role>) -> bool {
|
|
|
|
|
match other {
|
|
|
|
|
None => true,
|
|
|
|
|
Some(role) => *self == *role,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PartialEq<Role> for Option<Role> {
|
|
|
|
|
fn eq(&self, other: &Role) -> bool {
|
|
|
|
|
match *self {
|
|
|
|
|
None => true,
|
|
|
|
|
Some(role) => role == *other,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-10 01:33:29 -08:00
|
|
|
/// Address identifying a PostgreSQL server uniquely.
|
2022-02-05 13:15:53 -08:00
|
|
|
#[derive(Clone, PartialEq, Hash, std::cmp::Eq, Debug)]
|
2022-02-05 10:02:13 -08:00
|
|
|
pub struct Address {
|
2022-08-25 06:40:56 -07:00
|
|
|
/// Unique ID per addressable Postgres server.
|
2022-03-04 17:04:27 -08:00
|
|
|
pub id: usize,
|
2022-08-25 06:40:56 -07:00
|
|
|
|
|
|
|
|
/// Server host.
|
2022-02-05 10:02:13 -08:00
|
|
|
pub host: String,
|
2022-08-25 06:40:56 -07:00
|
|
|
|
|
|
|
|
/// Server port.
|
|
|
|
|
pub port: u16,
|
|
|
|
|
|
|
|
|
|
/// Shard number of this Postgres server.
|
2022-02-15 08:18:01 -08:00
|
|
|
pub shard: usize,
|
2022-08-25 06:40:56 -07:00
|
|
|
|
|
|
|
|
/// The name of the Postgres database.
|
2022-07-27 21:47:55 -05:00
|
|
|
pub database: String,
|
2022-08-25 06:40:56 -07:00
|
|
|
|
|
|
|
|
/// Server role: replica, primary.
|
2022-02-09 20:02:20 -08:00
|
|
|
pub role: Role,
|
2022-08-25 06:40:56 -07:00
|
|
|
|
|
|
|
|
/// If it's a replica, number it for reference and failover.
|
2022-08-21 22:40:49 -07:00
|
|
|
pub replica_number: usize,
|
2022-08-25 06:40:56 -07:00
|
|
|
|
|
|
|
|
/// Position of the server in the pool for failover.
|
2022-08-21 22:40:49 -07:00
|
|
|
pub address_index: usize,
|
2022-08-25 06:40:56 -07:00
|
|
|
|
|
|
|
|
/// The name of the user configured to use this pool.
|
2022-08-17 10:40:47 -05:00
|
|
|
pub username: String,
|
2022-08-25 06:40:56 -07:00
|
|
|
|
|
|
|
|
/// The name of this pool (i.e. database name visible to the client).
|
2022-08-21 22:40:49 -07:00
|
|
|
pub pool_name: String,
|
2022-02-05 10:02:13 -08:00
|
|
|
}
|
|
|
|
|
|
2022-02-19 13:57:35 -08:00
|
|
|
impl Default for Address {
|
|
|
|
|
fn default() -> Address {
|
|
|
|
|
Address {
|
2022-03-04 17:04:27 -08:00
|
|
|
id: 0,
|
2022-02-19 13:57:35 -08:00
|
|
|
host: String::from("127.0.0.1"),
|
2022-08-25 06:40:56 -07:00
|
|
|
port: 5432,
|
2022-02-19 13:57:35 -08:00
|
|
|
shard: 0,
|
2022-08-21 22:40:49 -07:00
|
|
|
address_index: 0,
|
|
|
|
|
replica_number: 0,
|
2022-07-27 21:47:55 -05:00
|
|
|
database: String::from("database"),
|
2022-02-19 13:57:35 -08:00
|
|
|
role: Role::Replica,
|
2022-08-17 10:40:47 -05:00
|
|
|
username: String::from("username"),
|
2022-08-21 22:40:49 -07:00
|
|
|
pool_name: String::from("pool_name"),
|
2022-02-19 13:57:35 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-01 22:49:43 -08:00
|
|
|
impl Address {
|
2022-03-10 01:33:29 -08:00
|
|
|
/// Address name (aka database) used in `SHOW STATS`, `SHOW DATABASES`, and `SHOW POOLS`.
|
2022-03-01 22:49:43 -08:00
|
|
|
pub fn name(&self) -> String {
|
|
|
|
|
match self.role {
|
2022-08-21 22:40:49 -07:00
|
|
|
Role::Primary => format!("{}_shard_{}_primary", self.pool_name, self.shard),
|
2022-03-01 22:49:43 -08:00
|
|
|
|
2022-07-27 21:47:55 -05:00
|
|
|
Role::Replica => format!(
|
|
|
|
|
"{}_shard_{}_replica_{}",
|
2022-08-21 22:40:49 -07:00
|
|
|
self.pool_name, self.shard, self.replica_number
|
2022-07-27 21:47:55 -05:00
|
|
|
),
|
2022-03-01 22:49:43 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-10 01:33:29 -08:00
|
|
|
/// PostgreSQL user.
|
2022-09-23 11:32:05 -07:00
|
|
|
#[derive(Clone, PartialEq, Hash, Eq, Serialize, Deserialize, Debug)]
|
2022-02-05 10:02:13 -08:00
|
|
|
pub struct User {
|
2022-07-27 21:47:55 -05:00
|
|
|
pub username: String,
|
2022-02-05 10:02:13 -08:00
|
|
|
pub password: String,
|
2022-07-27 21:47:55 -05:00
|
|
|
pub pool_size: u32,
|
2022-09-23 02:00:46 -04:00
|
|
|
#[serde(default)] // 0
|
2022-08-13 13:45:58 -07:00
|
|
|
pub statement_timeout: u64,
|
2022-02-05 10:02:13 -08:00
|
|
|
}
|
|
|
|
|
|
2022-02-19 13:57:35 -08:00
|
|
|
impl Default for User {
|
|
|
|
|
fn default() -> User {
|
|
|
|
|
User {
|
2022-07-27 21:47:55 -05:00
|
|
|
username: String::from("postgres"),
|
2022-02-19 13:57:35 -08:00
|
|
|
password: String::new(),
|
2022-07-27 21:47:55 -05:00
|
|
|
pool_size: 15,
|
2022-08-13 13:45:58 -07:00
|
|
|
statement_timeout: 0,
|
2022-02-19 13:57:35 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-10 01:33:29 -08:00
|
|
|
/// General configuration.
|
2022-07-28 17:42:04 -05:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
2022-02-08 09:25:59 -08:00
|
|
|
pub struct General {
|
2022-09-23 02:00:46 -04:00
|
|
|
#[serde(default = "General::default_host")]
|
2022-02-08 09:25:59 -08:00
|
|
|
pub host: String,
|
2022-09-23 02:00:46 -04:00
|
|
|
|
|
|
|
|
#[serde(default = "General::default_port")]
|
2022-02-08 09:25:59 -08:00
|
|
|
pub port: i16,
|
2022-09-23 02:00:46 -04:00
|
|
|
|
2022-08-09 15:19:11 -04:00
|
|
|
pub enable_prometheus_exporter: Option<bool>,
|
2022-08-14 01:25:14 +08:00
|
|
|
pub prometheus_exporter_port: i16,
|
2022-09-23 02:00:46 -04:00
|
|
|
|
|
|
|
|
#[serde(default = "General::default_connect_timeout")]
|
2022-02-08 09:25:59 -08:00
|
|
|
pub connect_timeout: u64,
|
2022-09-23 02:00:46 -04:00
|
|
|
|
Allow setting `idle_timeout` for server connections. (#257)
In postgres, you can specify an `idle_session_timeout` which will close
sessions idling for that amount of time. If a session is closed because
of a timeout, PgCat will erroneously mark the server as unhealthy as the next
health check will return an error because the connection was drop, if no
health check is to be executed, it will simply fail trying to send the query
to the server for the same reason, the conn was drop.
Given that bb8 allows configuring an idle_timeout for pools, it would be
nice to allow setting this parameter in the config file, this way you can
set it to something shorter than the server one. Also, server pool will be kept
smaller in moments of less traffic. Actually, currently this value is set as its
default in bb8, which is 10 minutes.
This changes allows setting the parameter using the config file. It can be set both
globally and per pool. When creating the pool, if the pool don't have it defined, global
value is used.
2022-12-16 17:01:00 +01:00
|
|
|
#[serde(default = "General::default_idle_timeout")]
|
|
|
|
|
pub idle_timeout: u64,
|
|
|
|
|
|
2023-02-08 11:35:38 -06:00
|
|
|
#[serde(default = "General::default_tcp_keepalives_idle")]
|
|
|
|
|
pub tcp_keepalives_idle: u64,
|
|
|
|
|
#[serde(default = "General::default_tcp_keepalives_count")]
|
|
|
|
|
pub tcp_keepalives_count: u32,
|
|
|
|
|
#[serde(default = "General::default_tcp_keepalives_interval")]
|
|
|
|
|
pub tcp_keepalives_interval: u64,
|
|
|
|
|
|
2022-11-16 22:15:47 -08:00
|
|
|
#[serde(default)] // False
|
|
|
|
|
pub log_client_connections: bool,
|
|
|
|
|
|
|
|
|
|
#[serde(default)] // False
|
|
|
|
|
pub log_client_disconnections: bool,
|
|
|
|
|
|
2022-09-23 02:00:46 -04:00
|
|
|
#[serde(default = "General::default_shutdown_timeout")]
|
2022-08-08 19:01:24 -04:00
|
|
|
pub shutdown_timeout: u64,
|
2022-09-23 02:00:46 -04:00
|
|
|
|
|
|
|
|
#[serde(default = "General::default_healthcheck_timeout")]
|
|
|
|
|
pub healthcheck_timeout: u64,
|
|
|
|
|
|
|
|
|
|
#[serde(default = "General::default_healthcheck_delay")]
|
2022-08-11 17:42:40 -04:00
|
|
|
pub healthcheck_delay: u64,
|
2022-09-23 02:00:46 -04:00
|
|
|
|
|
|
|
|
#[serde(default = "General::default_ban_time")]
|
2022-02-08 09:25:59 -08:00
|
|
|
pub ban_time: i64,
|
2022-09-23 02:00:46 -04:00
|
|
|
|
2022-12-16 20:13:13 +01:00
|
|
|
#[serde(default = "General::default_worker_threads")]
|
|
|
|
|
pub worker_threads: usize,
|
|
|
|
|
|
2022-09-23 02:00:46 -04:00
|
|
|
#[serde(default)] // False
|
2022-06-25 11:46:20 -07:00
|
|
|
pub autoreload: bool,
|
2022-09-23 02:00:46 -04:00
|
|
|
|
2022-06-27 09:46:33 -07:00
|
|
|
pub tls_certificate: Option<String>,
|
|
|
|
|
pub tls_private_key: Option<String>,
|
2022-07-27 21:47:55 -05:00
|
|
|
pub admin_username: String,
|
|
|
|
|
pub admin_password: String,
|
2022-02-19 13:57:35 -08:00
|
|
|
}
|
|
|
|
|
|
2022-09-23 02:00:46 -04:00
|
|
|
impl General {
|
2022-09-28 09:50:14 -04:00
|
|
|
pub fn default_host() -> String {
|
2022-09-23 02:00:46 -04:00
|
|
|
"0.0.0.0".into()
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 09:50:14 -04:00
|
|
|
pub fn default_port() -> i16 {
|
2022-09-23 02:00:46 -04:00
|
|
|
5432
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 09:50:14 -04:00
|
|
|
pub fn default_connect_timeout() -> u64 {
|
2022-09-23 02:00:46 -04:00
|
|
|
1000
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-08 11:35:38 -06:00
|
|
|
// These keepalive defaults should detect a dead connection within 30 seconds.
|
|
|
|
|
// Tokio defaults to disabling keepalives which keeps dead connections around indefinitely.
|
|
|
|
|
// This can lead to permenant server pool exhaustion
|
|
|
|
|
pub fn default_tcp_keepalives_idle() -> u64 {
|
|
|
|
|
5 // 5 seconds
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn default_tcp_keepalives_count() -> u32 {
|
|
|
|
|
5 // 5 time
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn default_tcp_keepalives_interval() -> u64 {
|
|
|
|
|
5 // 5 seconds
|
|
|
|
|
}
|
|
|
|
|
|
Allow setting `idle_timeout` for server connections. (#257)
In postgres, you can specify an `idle_session_timeout` which will close
sessions idling for that amount of time. If a session is closed because
of a timeout, PgCat will erroneously mark the server as unhealthy as the next
health check will return an error because the connection was drop, if no
health check is to be executed, it will simply fail trying to send the query
to the server for the same reason, the conn was drop.
Given that bb8 allows configuring an idle_timeout for pools, it would be
nice to allow setting this parameter in the config file, this way you can
set it to something shorter than the server one. Also, server pool will be kept
smaller in moments of less traffic. Actually, currently this value is set as its
default in bb8, which is 10 minutes.
This changes allows setting the parameter using the config file. It can be set both
globally and per pool. When creating the pool, if the pool don't have it defined, global
value is used.
2022-12-16 17:01:00 +01:00
|
|
|
pub fn default_idle_timeout() -> u64 {
|
|
|
|
|
60000 // 10 minutes
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 09:50:14 -04:00
|
|
|
pub fn default_shutdown_timeout() -> u64 {
|
2022-09-23 02:00:46 -04:00
|
|
|
60000
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 09:50:14 -04:00
|
|
|
pub fn default_healthcheck_timeout() -> u64 {
|
2022-09-23 02:00:46 -04:00
|
|
|
1000
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 09:50:14 -04:00
|
|
|
pub fn default_healthcheck_delay() -> u64 {
|
2022-09-23 02:00:46 -04:00
|
|
|
30000
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 09:50:14 -04:00
|
|
|
pub fn default_ban_time() -> i64 {
|
2022-09-23 02:00:46 -04:00
|
|
|
60
|
|
|
|
|
}
|
2022-12-16 20:13:13 +01:00
|
|
|
|
|
|
|
|
pub fn default_worker_threads() -> usize {
|
|
|
|
|
4
|
|
|
|
|
}
|
2022-09-23 02:00:46 -04:00
|
|
|
}
|
|
|
|
|
|
2022-02-19 13:57:35 -08:00
|
|
|
impl Default for General {
|
|
|
|
|
fn default() -> General {
|
|
|
|
|
General {
|
2022-09-28 09:50:14 -04:00
|
|
|
host: Self::default_host(),
|
|
|
|
|
port: Self::default_port(),
|
2022-08-09 15:19:11 -04:00
|
|
|
enable_prometheus_exporter: Some(false),
|
2022-08-14 01:25:14 +08:00
|
|
|
prometheus_exporter_port: 9930,
|
2022-09-23 02:00:46 -04:00
|
|
|
connect_timeout: General::default_connect_timeout(),
|
Allow setting `idle_timeout` for server connections. (#257)
In postgres, you can specify an `idle_session_timeout` which will close
sessions idling for that amount of time. If a session is closed because
of a timeout, PgCat will erroneously mark the server as unhealthy as the next
health check will return an error because the connection was drop, if no
health check is to be executed, it will simply fail trying to send the query
to the server for the same reason, the conn was drop.
Given that bb8 allows configuring an idle_timeout for pools, it would be
nice to allow setting this parameter in the config file, this way you can
set it to something shorter than the server one. Also, server pool will be kept
smaller in moments of less traffic. Actually, currently this value is set as its
default in bb8, which is 10 minutes.
This changes allows setting the parameter using the config file. It can be set both
globally and per pool. When creating the pool, if the pool don't have it defined, global
value is used.
2022-12-16 17:01:00 +01:00
|
|
|
idle_timeout: General::default_idle_timeout(),
|
2022-09-28 09:50:14 -04:00
|
|
|
shutdown_timeout: Self::default_shutdown_timeout(),
|
|
|
|
|
healthcheck_timeout: Self::default_healthcheck_timeout(),
|
|
|
|
|
healthcheck_delay: Self::default_healthcheck_delay(),
|
|
|
|
|
ban_time: Self::default_ban_time(),
|
2022-12-16 20:13:13 +01:00
|
|
|
worker_threads: Self::default_worker_threads(),
|
2023-02-08 11:35:38 -06:00
|
|
|
tcp_keepalives_idle: Self::default_tcp_keepalives_idle(),
|
|
|
|
|
tcp_keepalives_count: Self::default_tcp_keepalives_count(),
|
|
|
|
|
tcp_keepalives_interval: Self::default_tcp_keepalives_interval(),
|
2022-11-16 22:15:47 -08:00
|
|
|
log_client_connections: false,
|
|
|
|
|
log_client_disconnections: false,
|
2022-06-25 11:46:20 -07:00
|
|
|
autoreload: false,
|
2022-06-27 09:46:33 -07:00
|
|
|
tls_certificate: None,
|
|
|
|
|
tls_private_key: None,
|
2022-07-27 21:47:55 -05:00
|
|
|
admin_username: String::from("admin"),
|
|
|
|
|
admin_password: String::from("admin"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-09-23 02:00:46 -04:00
|
|
|
|
|
|
|
|
/// Pool mode:
|
|
|
|
|
/// - transaction: server serves one transaction,
|
|
|
|
|
/// - session: server is attached to the client.
|
2022-09-23 11:32:05 -07:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Copy, Hash)]
|
2022-09-23 02:00:46 -04:00
|
|
|
pub enum PoolMode {
|
|
|
|
|
#[serde(alias = "transaction", alias = "Transaction")]
|
|
|
|
|
Transaction,
|
|
|
|
|
|
|
|
|
|
#[serde(alias = "session", alias = "Session")]
|
|
|
|
|
Session,
|
|
|
|
|
}
|
|
|
|
|
impl ToString for PoolMode {
|
|
|
|
|
fn to_string(&self) -> String {
|
|
|
|
|
match *self {
|
|
|
|
|
PoolMode::Transaction => "transaction".to_string(),
|
|
|
|
|
PoolMode::Session => "session".to_string(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-17 06:52:18 -06:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Copy, Hash)]
|
|
|
|
|
pub enum LoadBalancingMode {
|
|
|
|
|
#[serde(alias = "random", alias = "Random")]
|
|
|
|
|
Random,
|
|
|
|
|
|
|
|
|
|
#[serde(alias = "loc", alias = "LOC", alias = "least_outstanding_connections")]
|
|
|
|
|
LeastOutstandingConnections,
|
|
|
|
|
}
|
|
|
|
|
impl ToString for LoadBalancingMode {
|
|
|
|
|
fn to_string(&self) -> String {
|
|
|
|
|
match *self {
|
|
|
|
|
LoadBalancingMode::Random => "random".to_string(),
|
|
|
|
|
LoadBalancingMode::LeastOutstandingConnections => {
|
|
|
|
|
"least_outstanding_connections".to_string()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-23 12:06:07 -07:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
|
2022-07-27 21:47:55 -05:00
|
|
|
pub struct Pool {
|
2022-09-23 02:00:46 -04:00
|
|
|
#[serde(default = "Pool::default_pool_mode")]
|
|
|
|
|
pub pool_mode: PoolMode,
|
|
|
|
|
|
2023-01-17 06:52:18 -06:00
|
|
|
#[serde(default = "Pool::default_load_balancing_mode")]
|
|
|
|
|
pub load_balancing_mode: LoadBalancingMode,
|
|
|
|
|
|
2022-07-27 21:47:55 -05:00
|
|
|
pub default_role: String,
|
2022-09-23 02:00:46 -04:00
|
|
|
|
|
|
|
|
#[serde(default)] // False
|
2022-07-27 21:47:55 -05:00
|
|
|
pub query_parser_enabled: bool,
|
2022-09-23 02:00:46 -04:00
|
|
|
|
|
|
|
|
#[serde(default)] // False
|
2022-07-27 21:47:55 -05:00
|
|
|
pub primary_reads_enabled: bool,
|
2022-09-23 02:00:46 -04:00
|
|
|
|
2022-09-28 09:50:14 -04:00
|
|
|
pub connect_timeout: Option<u64>,
|
2022-09-23 11:32:05 -07:00
|
|
|
|
Allow setting `idle_timeout` for server connections. (#257)
In postgres, you can specify an `idle_session_timeout` which will close
sessions idling for that amount of time. If a session is closed because
of a timeout, PgCat will erroneously mark the server as unhealthy as the next
health check will return an error because the connection was drop, if no
health check is to be executed, it will simply fail trying to send the query
to the server for the same reason, the conn was drop.
Given that bb8 allows configuring an idle_timeout for pools, it would be
nice to allow setting this parameter in the config file, this way you can
set it to something shorter than the server one. Also, server pool will be kept
smaller in moments of less traffic. Actually, currently this value is set as its
default in bb8, which is 10 minutes.
This changes allows setting the parameter using the config file. It can be set both
globally and per pool. When creating the pool, if the pool don't have it defined, global
value is used.
2022-12-16 17:01:00 +01:00
|
|
|
pub idle_timeout: Option<u64>,
|
|
|
|
|
|
2022-09-28 09:50:14 -04:00
|
|
|
pub sharding_function: ShardingFunction,
|
2022-10-25 11:47:41 -07:00
|
|
|
|
|
|
|
|
#[serde(default = "Pool::default_automatic_sharding_key")]
|
|
|
|
|
pub automatic_sharding_key: Option<String>,
|
|
|
|
|
|
2023-02-15 15:19:16 -06:00
|
|
|
pub sharding_key_regex: Option<String>,
|
|
|
|
|
pub shard_id_regex: Option<String>,
|
|
|
|
|
pub regex_search_limit: Option<usize>,
|
|
|
|
|
|
2022-09-23 12:06:07 -07:00
|
|
|
pub shards: BTreeMap<String, Shard>,
|
|
|
|
|
pub users: BTreeMap<String, User>,
|
2023-02-15 15:19:16 -06:00
|
|
|
// Note, don't put simple fields below these configs. There's a compatability issue with TOML that makes it
|
|
|
|
|
// incompatible to have simple fields in TOML after complex objects. See
|
|
|
|
|
// https://users.rust-lang.org/t/why-toml-to-string-get-error-valueaftertable/85903
|
2022-09-23 11:32:05 -07:00
|
|
|
}
|
|
|
|
|
|
2022-09-23 02:00:46 -04:00
|
|
|
impl Pool {
|
2023-02-21 21:53:10 -06:00
|
|
|
pub fn hash_value(&self) -> u64 {
|
|
|
|
|
let mut s = DefaultHasher::new();
|
|
|
|
|
self.hash(&mut s);
|
|
|
|
|
s.finish()
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 09:50:14 -04:00
|
|
|
pub fn default_pool_mode() -> PoolMode {
|
2022-09-23 02:00:46 -04:00
|
|
|
PoolMode::Transaction
|
|
|
|
|
}
|
2022-09-28 09:50:14 -04:00
|
|
|
|
2023-01-17 06:52:18 -06:00
|
|
|
pub fn default_load_balancing_mode() -> LoadBalancingMode {
|
|
|
|
|
LoadBalancingMode::Random
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-25 11:47:41 -07:00
|
|
|
pub fn default_automatic_sharding_key() -> Option<String> {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 09:50:14 -04:00
|
|
|
pub fn validate(&self) -> Result<(), Error> {
|
|
|
|
|
match self.default_role.as_ref() {
|
|
|
|
|
"any" => (),
|
|
|
|
|
"primary" => (),
|
|
|
|
|
"replica" => (),
|
|
|
|
|
other => {
|
|
|
|
|
error!(
|
|
|
|
|
"Query router default_role must be 'primary', 'replica', or 'any', got: '{}'",
|
|
|
|
|
other
|
|
|
|
|
);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (shard_idx, shard) in &self.shards {
|
|
|
|
|
match shard_idx.parse::<usize>() {
|
|
|
|
|
Ok(_) => (),
|
|
|
|
|
Err(_) => {
|
|
|
|
|
error!(
|
|
|
|
|
"Shard '{}' is not a valid number, shards must be numbered starting at 0",
|
|
|
|
|
shard_idx
|
|
|
|
|
);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
shard.validate()?;
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-15 15:19:16 -06:00
|
|
|
for (option, name) in [
|
|
|
|
|
(&self.shard_id_regex, "shard_id_regex"),
|
|
|
|
|
(&self.sharding_key_regex, "sharding_key_regex"),
|
|
|
|
|
] {
|
|
|
|
|
if let Some(regex) = option {
|
|
|
|
|
if let Err(parse_err) = Regex::new(regex.as_str()) {
|
|
|
|
|
error!("{} is not a valid Regex: {}", name, parse_err);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 09:50:14 -04:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2022-09-23 02:00:46 -04:00
|
|
|
}
|
|
|
|
|
|
2022-07-27 21:47:55 -05:00
|
|
|
impl Default for Pool {
|
|
|
|
|
fn default() -> Pool {
|
|
|
|
|
Pool {
|
2022-09-28 09:50:14 -04:00
|
|
|
pool_mode: Self::default_pool_mode(),
|
2023-01-17 06:52:18 -06:00
|
|
|
load_balancing_mode: Self::default_load_balancing_mode(),
|
2022-09-23 12:06:07 -07:00
|
|
|
shards: BTreeMap::from([(String::from("1"), Shard::default())]),
|
|
|
|
|
users: BTreeMap::default(),
|
2022-07-27 21:47:55 -05:00
|
|
|
default_role: String::from("any"),
|
|
|
|
|
query_parser_enabled: false,
|
2022-09-23 02:00:46 -04:00
|
|
|
primary_reads_enabled: false,
|
2022-09-28 09:50:14 -04:00
|
|
|
sharding_function: ShardingFunction::PgBigintHash,
|
2022-10-25 11:47:41 -07:00
|
|
|
automatic_sharding_key: None,
|
2022-09-28 09:50:14 -04:00
|
|
|
connect_timeout: None,
|
Allow setting `idle_timeout` for server connections. (#257)
In postgres, you can specify an `idle_session_timeout` which will close
sessions idling for that amount of time. If a session is closed because
of a timeout, PgCat will erroneously mark the server as unhealthy as the next
health check will return an error because the connection was drop, if no
health check is to be executed, it will simply fail trying to send the query
to the server for the same reason, the conn was drop.
Given that bb8 allows configuring an idle_timeout for pools, it would be
nice to allow setting this parameter in the config file, this way you can
set it to something shorter than the server one. Also, server pool will be kept
smaller in moments of less traffic. Actually, currently this value is set as its
default in bb8, which is 10 minutes.
This changes allows setting the parameter using the config file. It can be set both
globally and per pool. When creating the pool, if the pool don't have it defined, global
value is used.
2022-12-16 17:01:00 +01:00
|
|
|
idle_timeout: None,
|
2023-02-15 15:19:16 -06:00
|
|
|
sharding_key_regex: None,
|
|
|
|
|
shard_id_regex: None,
|
|
|
|
|
regex_search_limit: Some(1000),
|
2022-02-19 13:57:35 -08:00
|
|
|
}
|
|
|
|
|
}
|
2022-02-08 09:25:59 -08:00
|
|
|
}
|
|
|
|
|
|
2022-09-22 13:07:02 -04:00
|
|
|
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug, Hash, Eq)]
|
|
|
|
|
pub struct ServerConfig {
|
|
|
|
|
pub host: String,
|
|
|
|
|
pub port: u16,
|
|
|
|
|
pub role: Role,
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-10 01:33:29 -08:00
|
|
|
/// Shard configuration.
|
2022-09-23 11:32:05 -07:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Hash, Eq)]
|
2022-02-08 09:25:59 -08:00
|
|
|
pub struct Shard {
|
|
|
|
|
pub database: String,
|
2022-09-22 13:07:02 -04:00
|
|
|
pub servers: Vec<ServerConfig>,
|
2022-02-08 09:25:59 -08:00
|
|
|
}
|
|
|
|
|
|
2022-09-28 09:50:14 -04:00
|
|
|
impl Shard {
|
|
|
|
|
pub fn validate(&self) -> Result<(), Error> {
|
|
|
|
|
// We use addresses as unique identifiers,
|
|
|
|
|
// let's make sure they are unique in the config as well.
|
|
|
|
|
let mut dup_check = HashSet::new();
|
|
|
|
|
let mut primary_count = 0;
|
|
|
|
|
|
2022-11-10 02:04:31 +08:00
|
|
|
if self.servers.is_empty() {
|
2022-09-28 09:50:14 -04:00
|
|
|
error!("Shard {} has no servers configured", self.database);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for server in &self.servers {
|
|
|
|
|
dup_check.insert(server);
|
|
|
|
|
|
|
|
|
|
// Check that we define only zero or one primary.
|
2022-11-10 02:04:31 +08:00
|
|
|
if server.role == Role::Primary {
|
|
|
|
|
primary_count += 1
|
|
|
|
|
}
|
2022-09-28 09:50:14 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if primary_count > 1 {
|
|
|
|
|
error!(
|
|
|
|
|
"Shard {} has more than on primary configured",
|
|
|
|
|
self.database
|
|
|
|
|
);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if dup_check.len() != self.servers.len() {
|
|
|
|
|
error!("Shard {} contains duplicate server configs", self.database);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-19 13:57:35 -08:00
|
|
|
impl Default for Shard {
|
|
|
|
|
fn default() -> Shard {
|
|
|
|
|
Shard {
|
2022-09-22 13:07:02 -04:00
|
|
|
servers: vec![ServerConfig {
|
|
|
|
|
host: String::from("localhost"),
|
|
|
|
|
port: 5432,
|
|
|
|
|
role: Role::Primary,
|
|
|
|
|
}],
|
2022-02-19 13:57:35 -08:00
|
|
|
database: String::from("postgres"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-10 01:33:29 -08:00
|
|
|
/// Configuration wrapper.
|
2022-07-28 17:42:04 -05:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
2022-02-08 09:25:59 -08:00
|
|
|
pub struct Config {
|
2022-07-30 18:12:02 -05:00
|
|
|
// Serializer maintains the order of fields in the struct
|
|
|
|
|
// so we should always put simple fields before nested fields
|
|
|
|
|
// in all serializable structs to avoid ValueAfterTable errors
|
|
|
|
|
// These errors occur when the toml serializer is about to produce
|
2022-09-28 09:50:14 -04:00
|
|
|
// ambiguous toml structure like the one below
|
2022-07-30 18:12:02 -05:00
|
|
|
// [main]
|
|
|
|
|
// field1_under_main = 1
|
|
|
|
|
// field2_under_main = 2
|
|
|
|
|
// [main.subconf]
|
|
|
|
|
// field1_under_subconf = 1
|
|
|
|
|
// field3_under_main = 3 # This field will be interpreted as being under subconf and not under main
|
2022-09-23 02:00:46 -04:00
|
|
|
#[serde(default = "Config::default_path")]
|
2022-06-24 14:52:38 -07:00
|
|
|
pub path: String,
|
|
|
|
|
|
2022-02-08 09:25:59 -08:00
|
|
|
pub general: General,
|
2022-07-27 21:47:55 -05:00
|
|
|
pub pools: HashMap<String, Pool>,
|
2022-02-08 09:25:59 -08:00
|
|
|
}
|
|
|
|
|
|
2022-09-23 02:00:46 -04:00
|
|
|
impl Config {
|
2022-09-28 09:50:14 -04:00
|
|
|
pub fn default_path() -> String {
|
2022-09-23 02:00:46 -04:00
|
|
|
String::from("pgcat.toml")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-19 13:57:35 -08:00
|
|
|
impl Default for Config {
|
|
|
|
|
fn default() -> Config {
|
|
|
|
|
Config {
|
2022-09-28 09:50:14 -04:00
|
|
|
path: Self::default_path(),
|
2022-02-19 13:57:35 -08:00
|
|
|
general: General::default(),
|
2022-07-27 21:47:55 -05:00
|
|
|
pools: HashMap::default(),
|
2022-02-19 13:57:35 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-28 08:14:39 -08:00
|
|
|
impl From<&Config> for std::collections::HashMap<String, String> {
|
|
|
|
|
fn from(config: &Config) -> HashMap<String, String> {
|
2022-07-27 21:47:55 -05:00
|
|
|
let mut r: Vec<(String, String)> = config
|
|
|
|
|
.pools
|
|
|
|
|
.iter()
|
|
|
|
|
.flat_map(|(pool_name, pool)| {
|
|
|
|
|
[
|
|
|
|
|
(
|
|
|
|
|
format!("pools.{}.pool_mode", pool_name),
|
2022-09-23 02:00:46 -04:00
|
|
|
pool.pool_mode.to_string(),
|
2022-07-27 21:47:55 -05:00
|
|
|
),
|
2023-01-17 06:52:18 -06:00
|
|
|
(
|
|
|
|
|
format!("pools.{}.load_balancing_mode", pool_name),
|
|
|
|
|
pool.load_balancing_mode.to_string(),
|
|
|
|
|
),
|
2022-07-27 21:47:55 -05:00
|
|
|
(
|
|
|
|
|
format!("pools.{}.primary_reads_enabled", pool_name),
|
|
|
|
|
pool.primary_reads_enabled.to_string(),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
format!("pools.{}.query_parser_enabled", pool_name),
|
|
|
|
|
pool.query_parser_enabled.to_string(),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
format!("pools.{}.default_role", pool_name),
|
|
|
|
|
pool.default_role.clone(),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
format!("pools.{}.sharding_function", pool_name),
|
2022-09-28 09:50:14 -04:00
|
|
|
pool.sharding_function.to_string(),
|
2022-07-27 21:47:55 -05:00
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
format!("pools.{:?}.shard_count", pool_name),
|
|
|
|
|
pool.shards.len().to_string(),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
format!("pools.{:?}.users", pool_name),
|
|
|
|
|
pool.users
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|(_username, user)| &user.username)
|
|
|
|
|
.cloned()
|
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
|
.join(", "),
|
|
|
|
|
),
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
let mut static_settings = vec![
|
2022-02-28 08:14:39 -08:00
|
|
|
("host".to_string(), config.general.host.to_string()),
|
|
|
|
|
("port".to_string(), config.general.port.to_string()),
|
2022-08-14 01:25:14 +08:00
|
|
|
(
|
|
|
|
|
"prometheus_exporter_port".to_string(),
|
|
|
|
|
config.general.prometheus_exporter_port.to_string(),
|
|
|
|
|
),
|
2022-02-28 08:14:39 -08:00
|
|
|
(
|
|
|
|
|
"connect_timeout".to_string(),
|
|
|
|
|
config.general.connect_timeout.to_string(),
|
|
|
|
|
),
|
Allow setting `idle_timeout` for server connections. (#257)
In postgres, you can specify an `idle_session_timeout` which will close
sessions idling for that amount of time. If a session is closed because
of a timeout, PgCat will erroneously mark the server as unhealthy as the next
health check will return an error because the connection was drop, if no
health check is to be executed, it will simply fail trying to send the query
to the server for the same reason, the conn was drop.
Given that bb8 allows configuring an idle_timeout for pools, it would be
nice to allow setting this parameter in the config file, this way you can
set it to something shorter than the server one. Also, server pool will be kept
smaller in moments of less traffic. Actually, currently this value is set as its
default in bb8, which is 10 minutes.
This changes allows setting the parameter using the config file. It can be set both
globally and per pool. When creating the pool, if the pool don't have it defined, global
value is used.
2022-12-16 17:01:00 +01:00
|
|
|
(
|
|
|
|
|
"idle_timeout".to_string(),
|
|
|
|
|
config.general.idle_timeout.to_string(),
|
|
|
|
|
),
|
2022-02-28 08:14:39 -08:00
|
|
|
(
|
|
|
|
|
"healthcheck_timeout".to_string(),
|
|
|
|
|
config.general.healthcheck_timeout.to_string(),
|
|
|
|
|
),
|
2022-08-08 19:01:24 -04:00
|
|
|
(
|
|
|
|
|
"shutdown_timeout".to_string(),
|
|
|
|
|
config.general.shutdown_timeout.to_string(),
|
|
|
|
|
),
|
2022-08-11 17:42:40 -04:00
|
|
|
(
|
|
|
|
|
"healthcheck_delay".to_string(),
|
|
|
|
|
config.general.healthcheck_delay.to_string(),
|
|
|
|
|
),
|
2022-02-28 08:14:39 -08:00
|
|
|
("ban_time".to_string(), config.general.ban_time.to_string()),
|
2022-07-27 21:47:55 -05:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
r.append(&mut static_settings);
|
|
|
|
|
return r.iter().cloned().collect();
|
2022-02-28 08:14:39 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-19 13:57:35 -08:00
|
|
|
impl Config {
|
2022-03-10 01:33:29 -08:00
|
|
|
/// Print current configuration.
|
2022-02-19 13:57:35 -08:00
|
|
|
pub fn show(&self) {
|
2022-02-20 22:47:08 -08:00
|
|
|
info!("Ban time: {}s", self.general.ban_time);
|
2023-02-16 17:51:38 -05:00
|
|
|
info!("Worker threads: {}", self.general.worker_threads);
|
2022-02-20 22:47:08 -08:00
|
|
|
info!(
|
|
|
|
|
"Healthcheck timeout: {}ms",
|
2022-02-19 13:57:35 -08:00
|
|
|
self.general.healthcheck_timeout
|
|
|
|
|
);
|
2022-02-20 22:47:08 -08:00
|
|
|
info!("Connection timeout: {}ms", self.general.connect_timeout);
|
Allow setting `idle_timeout` for server connections. (#257)
In postgres, you can specify an `idle_session_timeout` which will close
sessions idling for that amount of time. If a session is closed because
of a timeout, PgCat will erroneously mark the server as unhealthy as the next
health check will return an error because the connection was drop, if no
health check is to be executed, it will simply fail trying to send the query
to the server for the same reason, the conn was drop.
Given that bb8 allows configuring an idle_timeout for pools, it would be
nice to allow setting this parameter in the config file, this way you can
set it to something shorter than the server one. Also, server pool will be kept
smaller in moments of less traffic. Actually, currently this value is set as its
default in bb8, which is 10 minutes.
This changes allows setting the parameter using the config file. It can be set both
globally and per pool. When creating the pool, if the pool don't have it defined, global
value is used.
2022-12-16 17:01:00 +01:00
|
|
|
info!("Idle timeout: {}ms", self.general.idle_timeout);
|
2022-11-16 22:15:47 -08:00
|
|
|
info!(
|
|
|
|
|
"Log client connections: {}",
|
|
|
|
|
self.general.log_client_connections
|
|
|
|
|
);
|
|
|
|
|
info!(
|
|
|
|
|
"Log client disconnections: {}",
|
|
|
|
|
self.general.log_client_disconnections
|
|
|
|
|
);
|
2022-08-08 19:01:24 -04:00
|
|
|
info!("Shutdown timeout: {}ms", self.general.shutdown_timeout);
|
2022-08-11 17:42:40 -04:00
|
|
|
info!("Healthcheck delay: {}ms", self.general.healthcheck_delay);
|
2022-06-27 17:01:14 -07:00
|
|
|
match self.general.tls_certificate.clone() {
|
|
|
|
|
Some(tls_certificate) => {
|
|
|
|
|
info!("TLS certificate: {}", tls_certificate);
|
|
|
|
|
|
|
|
|
|
match self.general.tls_private_key.clone() {
|
|
|
|
|
Some(tls_private_key) => {
|
|
|
|
|
info!("TLS private key: {}", tls_private_key);
|
|
|
|
|
info!("TLS support is enabled");
|
2022-06-27 17:01:40 -07:00
|
|
|
}
|
2022-06-27 17:01:14 -07:00
|
|
|
|
|
|
|
|
None => (),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None => {
|
|
|
|
|
info!("TLS support is disabled");
|
2022-06-27 17:01:40 -07:00
|
|
|
}
|
2022-06-27 17:01:14 -07:00
|
|
|
};
|
2022-07-27 21:47:55 -05:00
|
|
|
|
|
|
|
|
for (pool_name, pool_config) in &self.pools {
|
2022-08-13 13:45:58 -07:00
|
|
|
// TODO: Make this output prettier (maybe a table?)
|
2022-07-27 21:47:55 -05:00
|
|
|
info!(
|
2022-08-25 06:40:56 -07:00
|
|
|
"[pool: {}] Maximum user connections: {}",
|
|
|
|
|
pool_name,
|
2022-07-27 21:47:55 -05:00
|
|
|
pool_config
|
|
|
|
|
.users
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|(_, user_cfg)| user_cfg.pool_size)
|
|
|
|
|
.sum::<u32>()
|
|
|
|
|
.to_string()
|
|
|
|
|
);
|
2022-09-23 02:00:46 -04:00
|
|
|
info!(
|
|
|
|
|
"[pool: {}] Pool mode: {:?}",
|
|
|
|
|
pool_name, pool_config.pool_mode
|
|
|
|
|
);
|
2023-01-17 06:52:18 -06:00
|
|
|
info!(
|
|
|
|
|
"[pool: {}] Load Balancing mode: {:?}",
|
|
|
|
|
pool_name, pool_config.load_balancing_mode
|
|
|
|
|
);
|
2022-09-28 09:50:14 -04:00
|
|
|
let connect_timeout = match pool_config.connect_timeout {
|
|
|
|
|
Some(connect_timeout) => connect_timeout,
|
|
|
|
|
None => self.general.connect_timeout,
|
|
|
|
|
};
|
|
|
|
|
info!(
|
|
|
|
|
"[pool: {}] Connection timeout: {}ms",
|
|
|
|
|
pool_name, connect_timeout
|
|
|
|
|
);
|
Allow setting `idle_timeout` for server connections. (#257)
In postgres, you can specify an `idle_session_timeout` which will close
sessions idling for that amount of time. If a session is closed because
of a timeout, PgCat will erroneously mark the server as unhealthy as the next
health check will return an error because the connection was drop, if no
health check is to be executed, it will simply fail trying to send the query
to the server for the same reason, the conn was drop.
Given that bb8 allows configuring an idle_timeout for pools, it would be
nice to allow setting this parameter in the config file, this way you can
set it to something shorter than the server one. Also, server pool will be kept
smaller in moments of less traffic. Actually, currently this value is set as its
default in bb8, which is 10 minutes.
This changes allows setting the parameter using the config file. It can be set both
globally and per pool. When creating the pool, if the pool don't have it defined, global
value is used.
2022-12-16 17:01:00 +01:00
|
|
|
let idle_timeout = match pool_config.idle_timeout {
|
|
|
|
|
Some(idle_timeout) => idle_timeout,
|
|
|
|
|
None => self.general.idle_timeout,
|
|
|
|
|
};
|
|
|
|
|
info!("[pool: {}] Idle timeout: {}ms", pool_name, idle_timeout);
|
2022-08-25 06:40:56 -07:00
|
|
|
info!(
|
|
|
|
|
"[pool: {}] Sharding function: {}",
|
2022-09-28 09:50:14 -04:00
|
|
|
pool_name,
|
|
|
|
|
pool_config.sharding_function.to_string()
|
2022-08-25 06:40:56 -07:00
|
|
|
);
|
|
|
|
|
info!(
|
|
|
|
|
"[pool: {}] Primary reads: {}",
|
|
|
|
|
pool_name, pool_config.primary_reads_enabled
|
|
|
|
|
);
|
|
|
|
|
info!(
|
|
|
|
|
"[pool: {}] Query router: {}",
|
|
|
|
|
pool_name, pool_config.query_parser_enabled
|
|
|
|
|
);
|
|
|
|
|
info!(
|
|
|
|
|
"[pool: {}] Number of shards: {}",
|
|
|
|
|
pool_name,
|
|
|
|
|
pool_config.shards.len()
|
|
|
|
|
);
|
|
|
|
|
info!(
|
|
|
|
|
"[pool: {}] Number of users: {}",
|
|
|
|
|
pool_name,
|
|
|
|
|
pool_config.users.len()
|
|
|
|
|
);
|
2022-08-13 13:45:58 -07:00
|
|
|
|
|
|
|
|
for user in &pool_config.users {
|
|
|
|
|
info!(
|
2022-08-25 06:40:56 -07:00
|
|
|
"[pool: {}][user: {}] Pool size: {}",
|
|
|
|
|
pool_name, user.1.username, user.1.pool_size,
|
2022-08-13 13:45:58 -07:00
|
|
|
);
|
2022-08-25 06:40:56 -07:00
|
|
|
info!(
|
|
|
|
|
"[pool: {}][user: {}] Statement timeout: {}",
|
|
|
|
|
pool_name, user.1.username, user.1.statement_timeout
|
|
|
|
|
)
|
2022-08-13 13:45:58 -07:00
|
|
|
}
|
2022-07-27 21:47:55 -05:00
|
|
|
}
|
2022-02-19 13:57:35 -08:00
|
|
|
}
|
2022-09-28 09:50:14 -04:00
|
|
|
|
|
|
|
|
pub fn validate(&mut self) -> Result<(), Error> {
|
|
|
|
|
// Validate TLS!
|
|
|
|
|
match self.general.tls_certificate.clone() {
|
|
|
|
|
Some(tls_certificate) => {
|
2022-11-10 02:04:31 +08:00
|
|
|
match load_certs(Path::new(&tls_certificate)) {
|
2022-09-28 09:50:14 -04:00
|
|
|
Ok(_) => {
|
|
|
|
|
// Cert is okay, but what about the private key?
|
|
|
|
|
match self.general.tls_private_key.clone() {
|
2022-11-10 02:04:31 +08:00
|
|
|
Some(tls_private_key) => match load_keys(Path::new(&tls_private_key)) {
|
|
|
|
|
Ok(_) => (),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
error!("tls_private_key is incorrectly configured: {:?}", err);
|
|
|
|
|
return Err(Error::BadConfig);
|
2022-09-28 09:50:14 -04:00
|
|
|
}
|
2022-11-10 02:04:31 +08:00
|
|
|
},
|
2022-09-28 09:50:14 -04:00
|
|
|
|
|
|
|
|
None => {
|
|
|
|
|
error!("tls_certificate is set, but the tls_private_key is not");
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Err(err) => {
|
|
|
|
|
error!("tls_certificate is incorrectly configured: {:?}", err);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None => (),
|
|
|
|
|
};
|
|
|
|
|
|
2022-11-10 02:04:31 +08:00
|
|
|
for pool in self.pools.values_mut() {
|
2022-09-28 09:50:14 -04:00
|
|
|
pool.validate()?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2022-02-19 13:57:35 -08:00
|
|
|
}
|
|
|
|
|
|
2022-03-10 01:33:29 -08:00
|
|
|
/// Get a read-only instance of the configuration
|
|
|
|
|
/// from anywhere in the app.
|
|
|
|
|
/// ArcSwap makes this cheap and quick.
|
2022-06-24 14:52:38 -07:00
|
|
|
pub fn get_config() -> Config {
|
|
|
|
|
(*(*CONFIG.load())).clone()
|
2022-02-19 13:57:35 -08:00
|
|
|
}
|
|
|
|
|
|
2022-03-10 01:33:29 -08:00
|
|
|
/// Parse the configuration file located at the path.
|
2022-02-19 13:57:35 -08:00
|
|
|
pub async fn parse(path: &str) -> Result<(), Error> {
|
2022-02-08 09:25:59 -08:00
|
|
|
let mut contents = String::new();
|
|
|
|
|
let mut file = match File::open(path).await {
|
|
|
|
|
Ok(file) => file,
|
|
|
|
|
Err(err) => {
|
2022-02-21 20:41:32 -08:00
|
|
|
error!("Could not open '{}': {}", path, err.to_string());
|
2022-02-08 09:25:59 -08:00
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match file.read_to_string(&mut contents).await {
|
|
|
|
|
Ok(_) => (),
|
|
|
|
|
Err(err) => {
|
2022-02-21 20:41:32 -08:00
|
|
|
error!("Could not read config file: {}", err.to_string());
|
2022-02-08 09:25:59 -08:00
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2022-02-27 10:21:24 -08:00
|
|
|
let mut config: Config = match toml::from_str(&contents) {
|
2022-02-08 09:25:59 -08:00
|
|
|
Ok(config) => config,
|
|
|
|
|
Err(err) => {
|
2022-02-21 20:41:32 -08:00
|
|
|
error!("Could not parse config file: {}", err.to_string());
|
2022-02-08 09:25:59 -08:00
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2022-09-28 09:50:14 -04:00
|
|
|
config.validate()?;
|
2022-07-27 21:47:55 -05:00
|
|
|
|
2022-06-24 14:52:38 -07:00
|
|
|
config.path = path.to_string();
|
2022-02-27 10:21:24 -08:00
|
|
|
|
2022-03-10 01:33:29 -08:00
|
|
|
// Update the configuration globally.
|
2022-02-19 13:57:35 -08:00
|
|
|
CONFIG.store(Arc::new(config.clone()));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
2022-02-08 09:25:59 -08:00
|
|
|
}
|
2022-02-08 17:08:17 -08:00
|
|
|
|
2022-06-25 11:46:20 -07:00
|
|
|
pub async fn reload_config(client_server_map: ClientServerMap) -> Result<bool, Error> {
|
2022-06-24 14:52:38 -07:00
|
|
|
let old_config = get_config();
|
|
|
|
|
match parse(&old_config.path).await {
|
|
|
|
|
Ok(()) => (),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
error!("Config reload error: {:?}", err);
|
|
|
|
|
return Err(Error::BadConfig);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
let new_config = get_config();
|
|
|
|
|
|
2022-07-27 21:47:55 -05:00
|
|
|
if old_config.pools != new_config.pools {
|
2022-09-23 11:32:05 -07:00
|
|
|
info!("Pool configuration changed");
|
2022-06-25 11:46:20 -07:00
|
|
|
ConnectionPool::from_config(client_server_map).await?;
|
|
|
|
|
Ok(true)
|
|
|
|
|
} else if old_config != new_config {
|
|
|
|
|
Ok(true)
|
2022-06-24 14:52:38 -07:00
|
|
|
} else {
|
2022-06-25 11:46:20 -07:00
|
|
|
Ok(false)
|
2022-06-24 14:52:38 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-08 17:08:17 -08:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod test {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_config() {
|
2022-02-19 13:57:35 -08:00
|
|
|
parse("pgcat.toml").await.unwrap();
|
2022-07-27 21:47:55 -05:00
|
|
|
|
2022-06-24 14:52:38 -07:00
|
|
|
assert_eq!(get_config().path, "pgcat.toml".to_string());
|
2022-07-27 21:47:55 -05:00
|
|
|
|
|
|
|
|
assert_eq!(get_config().general.ban_time, 60);
|
Allow setting `idle_timeout` for server connections. (#257)
In postgres, you can specify an `idle_session_timeout` which will close
sessions idling for that amount of time. If a session is closed because
of a timeout, PgCat will erroneously mark the server as unhealthy as the next
health check will return an error because the connection was drop, if no
health check is to be executed, it will simply fail trying to send the query
to the server for the same reason, the conn was drop.
Given that bb8 allows configuring an idle_timeout for pools, it would be
nice to allow setting this parameter in the config file, this way you can
set it to something shorter than the server one. Also, server pool will be kept
smaller in moments of less traffic. Actually, currently this value is set as its
default in bb8, which is 10 minutes.
This changes allows setting the parameter using the config file. It can be set both
globally and per pool. When creating the pool, if the pool don't have it defined, global
value is used.
2022-12-16 17:01:00 +01:00
|
|
|
assert_eq!(get_config().general.idle_timeout, 30000);
|
2022-07-27 21:47:55 -05:00
|
|
|
assert_eq!(get_config().pools.len(), 2);
|
2022-08-08 15:15:48 -05:00
|
|
|
assert_eq!(get_config().pools["sharded_db"].shards.len(), 3);
|
Allow setting `idle_timeout` for server connections. (#257)
In postgres, you can specify an `idle_session_timeout` which will close
sessions idling for that amount of time. If a session is closed because
of a timeout, PgCat will erroneously mark the server as unhealthy as the next
health check will return an error because the connection was drop, if no
health check is to be executed, it will simply fail trying to send the query
to the server for the same reason, the conn was drop.
Given that bb8 allows configuring an idle_timeout for pools, it would be
nice to allow setting this parameter in the config file, this way you can
set it to something shorter than the server one. Also, server pool will be kept
smaller in moments of less traffic. Actually, currently this value is set as its
default in bb8, which is 10 minutes.
This changes allows setting the parameter using the config file. It can be set both
globally and per pool. When creating the pool, if the pool don't have it defined, global
value is used.
2022-12-16 17:01:00 +01:00
|
|
|
assert_eq!(get_config().pools["sharded_db"].idle_timeout, Some(40000));
|
2022-07-27 21:47:55 -05:00
|
|
|
assert_eq!(get_config().pools["simple_db"].shards.len(), 1);
|
2022-08-08 15:15:48 -05:00
|
|
|
assert_eq!(get_config().pools["sharded_db"].users.len(), 2);
|
2022-07-27 21:47:55 -05:00
|
|
|
assert_eq!(get_config().pools["simple_db"].users.len(), 1);
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
2022-09-22 13:07:02 -04:00
|
|
|
get_config().pools["sharded_db"].shards["0"].servers[0].host,
|
2022-07-27 21:47:55 -05:00
|
|
|
"127.0.0.1"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
2022-09-22 13:07:02 -04:00
|
|
|
get_config().pools["sharded_db"].shards["1"].servers[0].role,
|
|
|
|
|
Role::Primary
|
2022-07-27 21:47:55 -05:00
|
|
|
);
|
|
|
|
|
assert_eq!(
|
2022-08-08 15:15:48 -05:00
|
|
|
get_config().pools["sharded_db"].shards["1"].database,
|
|
|
|
|
"shard1"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
get_config().pools["sharded_db"].users["0"].username,
|
2022-07-27 21:47:55 -05:00
|
|
|
"sharding_user"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
2022-08-08 15:15:48 -05:00
|
|
|
get_config().pools["sharded_db"].users["1"].password,
|
2022-07-27 21:47:55 -05:00
|
|
|
"other_user"
|
|
|
|
|
);
|
2022-08-08 15:15:48 -05:00
|
|
|
assert_eq!(get_config().pools["sharded_db"].users["1"].pool_size, 21);
|
|
|
|
|
assert_eq!(get_config().pools["sharded_db"].default_role, "any");
|
2022-07-27 21:47:55 -05:00
|
|
|
|
|
|
|
|
assert_eq!(
|
2022-09-22 13:07:02 -04:00
|
|
|
get_config().pools["simple_db"].shards["0"].servers[0].host,
|
2022-07-27 21:47:55 -05:00
|
|
|
"127.0.0.1"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
2022-09-22 13:07:02 -04:00
|
|
|
get_config().pools["simple_db"].shards["0"].servers[0].port,
|
2022-07-27 21:47:55 -05:00
|
|
|
5432
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
get_config().pools["simple_db"].shards["0"].database,
|
|
|
|
|
"some_db"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(get_config().pools["simple_db"].default_role, "primary");
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
get_config().pools["simple_db"].users["0"].username,
|
|
|
|
|
"simple_user"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
get_config().pools["simple_db"].users["0"].password,
|
|
|
|
|
"simple_user"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(get_config().pools["simple_db"].users["0"].pool_size, 5);
|
2022-02-08 17:08:17 -08:00
|
|
|
}
|
2022-07-30 18:28:25 -05:00
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_serialize_configs() {
|
|
|
|
|
parse("pgcat.toml").await.unwrap();
|
|
|
|
|
print!("{}", toml::to_string(&get_config()).unwrap());
|
|
|
|
|
}
|
2022-02-08 17:08:17 -08:00
|
|
|
}
|