Skip to content

Commit 9f69850

Browse files
authored
feat(conn-pool): tweak connection pool options and expose min/max conn (#851)
* feat(conn-pool): tweak connection pool options and expose min/max conn * docs(conn-pool): new options about min/max connections
1 parent d10cd7b commit 9f69850

File tree

4 files changed

+78
-13
lines changed

4 files changed

+78
-13
lines changed

docs/docs/core/settings.mdx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,24 +77,33 @@ If not set, all flows are in a default unnamed namespace.
7777

7878
`DatabaseConnectionSpec` configures the connection to a database. Only Postgres is supported for now. It has the following fields:
7979

80-
* `url` (type: `str`, required): The URL of the Postgres database to use as the internal storage, e.g. `postgres://cocoindex:cocoindex@localhost/cocoindex`.
80+
* `url` (type: `str`): The URL of the Postgres database to use as the internal storage, e.g. `postgres://cocoindex:cocoindex@localhost/cocoindex`.
8181

8282
*Environment variable* for `Settings.database.url`: `COCOINDEX_DATABASE_URL`
8383

84-
* `user` (type: `str`, optional): The username for the Postgres database. If not provided, username will come from `url`.
84+
* `user` (type: `str | None`, default: `None`): The username for the Postgres database. If not provided, username will come from `url`.
8585

8686
*Environment variable* for `Settings.database.user`: `COCOINDEX_DATABASE_USER`
8787

88-
* `password` (type: `str`, optional): The password for the Postgres database. If not provided, password will come from `url`.
88+
* `password` (type: `str | None`, default: `None`): The password for the Postgres database. If not provided, password will come from `url`.
8989

9090
*Environment variable* for `Settings.database.password`: `COCOINDEX_DATABASE_PASSWORD`
9191

92-
:::tip
92+
:::tip
9393

94-
Please be careful that all values in `url` needs to be url-encoded if they contain special characters.
95-
For this reason, prefer to use the separated `user` and `password` fields for username and password.
94+
Please be careful that all values in `url` needs to be url-encoded if they contain special characters.
95+
For this reason, prefer to use the separated `user` and `password` fields for username and password.
96+
97+
:::
98+
99+
* `max_connections` (type: `int`, default: `64`): The maximum number of connections to keep in the pool.
100+
101+
*Environment variable* for `Settings.database.max_connections`: `COCOINDEX_DATABASE_MAX_CONNECTIONS`
102+
103+
* `min_connections` (type: `int`, default: `16`): The minimum number of connections to keep in the pool.
104+
105+
*Environment variable* for `Settings.database.min_connections`: `COCOINDEX_DATABASE_MIN_CONNECTIONS`
96106

97-
:::
98107

99108
:::info
100109

@@ -125,5 +134,7 @@ This is the list of environment variables, each of which has a corresponding fie
125134
| `COCOINDEX_DATABASE_URL` | `database.url` | Yes |
126135
| `COCOINDEX_DATABASE_USER` | `database.user` | No |
127136
| `COCOINDEX_DATABASE_PASSWORD` | `database.password` | No |
137+
| `COCOINDEX_DATABASE_MAX_CONNECTIONS` | `database.max_connections` | No (default: `64`) |
138+
| `COCOINDEX_DATABASE_MIN_CONNECTIONS` | `database.min_connections` | No (default: `16`) |
128139
| `COCOINDEX_SOURCE_MAX_INFLIGHT_ROWS` | `global_execution_options.source_max_inflight_rows` | No (default: `256`) |
129140
| `COCOINDEX_SOURCE_MAX_INFLIGHT_BYTES` | `global_execution_options.source_max_inflight_bytes` | No |

python/cocoindex/setting.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ class DatabaseConnectionSpec:
4444
url: str
4545
user: str | None = None
4646
password: str | None = None
47+
max_connections: int = 64
48+
min_connections: int = 16
4749

4850

4951
@dataclass
@@ -92,10 +94,22 @@ def from_env(cls) -> Self:
9294

9395
database_url = os.getenv("COCOINDEX_DATABASE_URL")
9496
if database_url is not None:
95-
db_kwargs: dict[str, str] = dict()
97+
db_kwargs: dict[str, Any] = dict()
9698
_load_field(db_kwargs, "url", "COCOINDEX_DATABASE_URL", required=True)
9799
_load_field(db_kwargs, "user", "COCOINDEX_DATABASE_USER")
98100
_load_field(db_kwargs, "password", "COCOINDEX_DATABASE_PASSWORD")
101+
_load_field(
102+
db_kwargs,
103+
"max_connections",
104+
"COCOINDEX_DATABASE_MAX_CONNECTIONS",
105+
parse=int,
106+
)
107+
_load_field(
108+
db_kwargs,
109+
"min_connections",
110+
"COCOINDEX_DATABASE_MIN_CONNECTIONS",
111+
parse=int,
112+
)
99113
database = DatabaseConnectionSpec(**db_kwargs)
100114
else:
101115
database = None

src/lib_context.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::time::Duration;
2+
13
use crate::prelude::*;
24

35
use crate::builder::AnalyzedFlow;
@@ -7,7 +9,7 @@ use crate::settings;
79
use crate::setup::ObjectSetupStatus;
810
use axum::http::StatusCode;
911
use sqlx::PgPool;
10-
use sqlx::postgres::PgConnectOptions;
12+
use sqlx::postgres::{PgConnectOptions, PgPoolOptions};
1113
use tokio::runtime::Runtime;
1214

1315
pub struct FlowExecutionContext {
@@ -176,7 +178,29 @@ impl DbPools {
176178
if let Some(password) = &conn_spec.password {
177179
pg_options = pg_options.password(password);
178180
}
179-
let pool = PgPool::connect_with(pg_options)
181+
182+
// Try to connect to the database with a low timeout first.
183+
{
184+
let pool_options = PgPoolOptions::new()
185+
.max_connections(1)
186+
.min_connections(1)
187+
.acquire_timeout(Duration::from_secs(30));
188+
let pool = pool_options
189+
.connect_with(pg_options.clone())
190+
.await
191+
.context(format!("Failed to connect to database {}", conn_spec.url))?;
192+
let _ = pool.acquire().await?;
193+
}
194+
195+
// Now create the actual pool.
196+
let pool_options = PgPoolOptions::new()
197+
.max_connections(conn_spec.max_connections)
198+
.min_connections(conn_spec.min_connections)
199+
.acquire_timeout(Duration::from_secs(5 * 60))
200+
.idle_timeout(Duration::from_secs(10 * 60))
201+
.max_lifetime(Duration::from_secs(60 * 60));
202+
let pool = pool_options
203+
.connect_with(pg_options)
180204
.await
181205
.context("Failed to connect to database")?;
182206
anyhow::Ok(pool)
@@ -330,6 +354,8 @@ mod tests {
330354
url: "postgresql://test".to_string(),
331355
user: None,
332356
password: None,
357+
max_connections: 10,
358+
min_connections: 1,
333359
}),
334360
..Default::default()
335361
};

src/settings.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ pub struct DatabaseConnectionSpec {
55
pub url: String,
66
pub user: Option<String>,
77
pub password: Option<String>,
8+
pub max_connections: u32,
9+
pub min_connections: u32,
810
}
911

1012
#[derive(Deserialize, Debug, Default)]
@@ -34,7 +36,9 @@ mod tests {
3436
"database": {
3537
"url": "postgresql://localhost:5432/test",
3638
"user": "testuser",
37-
"password": "testpass"
39+
"password": "testpass",
40+
"min_connections": 1,
41+
"max_connections": 10
3842
},
3943
"app_namespace": "test_app"
4044
}"#;
@@ -46,6 +50,8 @@ mod tests {
4650
assert_eq!(db.url, "postgresql://localhost:5432/test");
4751
assert_eq!(db.user, Some("testuser".to_string()));
4852
assert_eq!(db.password, Some("testpass".to_string()));
53+
assert_eq!(db.min_connections, 1);
54+
assert_eq!(db.max_connections, 10);
4955
assert_eq!(settings.app_namespace, "test_app");
5056
}
5157

@@ -75,7 +81,9 @@ mod tests {
7581
fn test_settings_deserialize_database_without_user_password() {
7682
let json = r#"{
7783
"database": {
78-
"url": "postgresql://localhost:5432/test"
84+
"url": "postgresql://localhost:5432/test",
85+
"min_connections": 1,
86+
"max_connections": 10
7987
}
8088
}"#;
8189

@@ -86,6 +94,8 @@ mod tests {
8694
assert_eq!(db.url, "postgresql://localhost:5432/test");
8795
assert_eq!(db.user, None);
8896
assert_eq!(db.password, None);
97+
assert_eq!(db.min_connections, 1);
98+
assert_eq!(db.max_connections, 10);
8999
assert_eq!(settings.app_namespace, "");
90100
}
91101

@@ -94,13 +104,17 @@ mod tests {
94104
let json = r#"{
95105
"url": "postgresql://localhost:5432/test",
96106
"user": "testuser",
97-
"password": "testpass"
107+
"password": "testpass",
108+
"min_connections": 1,
109+
"max_connections": 10
98110
}"#;
99111

100112
let db_spec: DatabaseConnectionSpec = serde_json::from_str(json).unwrap();
101113

102114
assert_eq!(db_spec.url, "postgresql://localhost:5432/test");
103115
assert_eq!(db_spec.user, Some("testuser".to_string()));
104116
assert_eq!(db_spec.password, Some("testpass".to_string()));
117+
assert_eq!(db_spec.min_connections, 1);
118+
assert_eq!(db_spec.max_connections, 10);
105119
}
106120
}

0 commit comments

Comments
 (0)