Skip to content

Conversation

@itowlson
Copy link
Collaborator

@itowlson itowlson commented Mar 10, 2025

Fixes #3030.

Draft for now. Needs more testing and some tidying but I want to see if tests pass, and to verify with factors experts if I have the pools in the right place (AppState).

@itowlson itowlson force-pushed the pg-connection-pool branch 2 times, most recently from 7dca161 to 9f9bdce Compare March 18, 2025 01:09
@itowlson itowlson force-pushed the pg-connection-pool branch 2 times, most recently from 7cfa3c2 to edc36eb Compare July 14, 2025 22:17
@itowlson itowlson marked this pull request as ready for review July 14, 2025 22:18
@itowlson itowlson requested review from kate-goldenring and lann July 14, 2025 22:18
@itowlson
Copy link
Collaborator Author

See #3030 for local performance characteristics.

@itowlson
Copy link
Collaborator Author

There are now conformance tests for Postgres which will make it easier to regression-test this, but the conformance test PR is probably blocked on the data types PR, which is probably blocked on the conformance test PR.

It would be good to get some reviews on this little knot of PRs and figure out how to land them.

async fn build_client(address: &str) -> Result<Self>
where
Self: Sized;
pub trait ClientFactory: Send + Sync {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🏭

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be safe to add + 'static here, which would let us remove all the related Send + Sync + 'static constraints elsewhere.

type RuntimeConfig = ();
type AppState = ();
type InstanceBuilder = InstanceState<C>;
type AppState = Arc<RwLock<CF>>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend leaving interior mutability up to the trait implementation if possible; some implementations might not need to lock.

Suggested change
type AppState = Arc<RwLock<CF>>;
type AppState = Arc<CF>;

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would require changing the trait to use immutable borrows which is fine, but it will complicate the implementation of the ClientFactory.

Copy link
Collaborator

@lann lann Aug 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have deleted several stream-of-consciousness comments about this to summarize here:

There is a performance hazard with the current impl: this lock is held for the duration of the async call to ClientFactory::build_client. If that async call doesn't complete immediately then calls to open_connection from other instances (of the same app) will be blocked.

I don't know how much this problem would apply to PooledTokioClientFactory; that would depend on the runtime behavior of Pool::get.

Pushing interior mutability down into the ClientFactory won't fix this on its own but it would allow a fancier impl like using a read lock to check for the entry before falling back to the write lock. Or using moka.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moka worked really nicely. Thanks for the pointer!

async fn build_client(address: &str) -> Result<Self>
where
Self: Sized;
pub trait ClientFactory: Send + Sync {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it didn't have doc comments before, but it would be awesome if it gained some.

type RuntimeConfig = ();
type AppState = ();
type InstanceBuilder = InstanceState<C>;
type AppState = Arc<RwLock<CF>>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would require changing the trait to use immutable borrows which is fine, but it will complicate the implementation of the ClientFactory.

Signed-off-by: itowlson <ivan.towlson@fermyon.com>
@itowlson itowlson force-pushed the pg-connection-pool branch from edc36eb to d951aae Compare August 3, 2025 23:52
@itowlson itowlson requested a review from lann August 3, 2025 23:54
impl Default for PooledTokioClientFactory {
fn default() -> Self {
Self {
pools: moka::sync::Cache::new(CONNECTION_POOL_CACHE_CAPACITY),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lann I'd value your input on whether we need to provide a way for hosts (e.g. multi-tenant environments) to configure cache (and pool) sizes and policies (e.g. idle time eviction). But also happy to punt that to a separate PR if that's not yet obvious.

Copy link
Collaborator

@lann lann Aug 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think punting on configurability is fine.


impl<C: Send + Sync + Client> spin_world::spin::postgres::postgres::HostConnection
for InstanceState<C>
impl<CF: ClientFactory + Send + Sync> spin_world::spin::postgres::postgres::HostConnection
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Some redundant generic constraints remain in this file.

/// Max connections in a given address' connection pool
const CONNECTION_POOL_SIZE: usize = 64;
/// Max addresses for which to keep pools in cache.
const CONNECTION_POOL_CACHE_CAPACITY: u64 = 1024;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should start with a smaller number here. I think its more likely for a large number of idle connections to cause problems than for someone to actually want this many connections open for one app, and you'd almost certainly want this to be configurable at that point anyway.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Picking a differently arbitrary number... 16?

Signed-off-by: itowlson <ivan.towlson@fermyon.com>
@itowlson itowlson force-pushed the pg-connection-pool branch from d951aae to 58b68da Compare August 4, 2025 20:39
@itowlson itowlson requested a review from lann August 4, 2025 20:40
@itowlson itowlson enabled auto-merge August 4, 2025 20:58
@itowlson itowlson merged commit 0a26f5d into spinframework:main Aug 4, 2025
17 checks passed
@macolso macolso added the spin-3.4 Label to track items to land in the release of Spin v3.4 label Aug 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

spin-3.4 Label to track items to land in the release of Spin v3.4

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Reuse connections between requests with Postgres

4 participants