6.4 KiB
Connection Pool
sqlgen provides a thread-safe connection pool implementation that efficiently manages database connections. The pool automatically handles connection lifecycle, ensures thread safety, and provides RAII-based session management.
Configuration
The connection pool can be configured using ConnectionPoolConfig:
using namespace sqlgen;
ConnectionPoolConfig config{
.size = 4, // Number of connections in the pool
.num_attempts = 10, // Number of retry attempts when acquiring a connection
.wait_time_in_seconds = 1 // Wait time between retry attempts
};
// Create a pool with the specified configuration
const auto pool = make_connection_pool<postgres::Connection>(
config,
postgres::Credentials{
.user = "postgres",
.password = "password",
.host = "localhost",
.dbname = "postgres"
}
);
Configuration Parameters
size: The number of connections to maintain in the poolnum_attempts: Maximum number of attempts to acquire a connection before giving upwait_time_in_seconds: Time to wait between retry attempts when no connections are available
Basic Usage
Creating a Connection Pool
Create a connection pool with a specified number of connections:
using namespace sqlgen;
const auto pool = make_connection_pool<postgres::Connection>(
config, // ConnectionPoolConfig
credentials // Variables necessary to create the connections
);
if (!pool) {
// Handle error
std::cerr << "Failed to create pool: " << pool.error() << std::endl;
return;
}
Acquiring Sessions
Sessions are acquired from the pool using the session() function:
using namespace sqlgen;
// Acquire a session from the pool
const auto session_result = session(pool);
if (!session_result) {
// Handle error (e.g., no available connections)
std::cerr << "Failed to acquire session: " << session_result.error() << std::endl;
return;
}
Chaining Operations
Sessions can be used to chain database operations:
using namespace sqlgen;
const auto result = session(pool)
.and_then(drop<Person> | if_exists)
.and_then(write(std::ref(people)))
.and_then(read<std::vector<Person>>);
if (!result) {
// Handle error
std::cerr << "Operation failed: " << result.error() << std::endl;
return;
}
const auto& people = result.value();
Thread Safety
The connection pool is designed to be thread-safe:
- Each connection is protected by an atomic flag
- Sessions are automatically released when they go out of scope
- The pool uses atomic operations for connection management
- Multiple threads can safely acquire and release sessions
Example of thread-safe usage with monadic style:
using namespace sqlgen;
using namespace sqlgen::literals;
// Create pool
const auto pool = make_connection_pool<postgres::Connection>(config, credentials);
std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i) {
threads.emplace_back([&]() {
session(pool)
.and_then(update<Person>("age"_c.set(46) | where("id"_c == i)));
});
}
for (auto& thread : threads) {
thread.join();
}
Session Management
Sessions are managed through RAII (Resource Acquisition Is Initialization) and support monadic operations:
- Sessions automatically release their connections when destroyed
- Connections are returned to the pool when sessions go out of scope
- No explicit connection release is required
using namespace sqlgen;
using namespace sqlgen::literals;
// Using monadic style for session management with exec
session(pool)
.and_then(begin_transaction)
.and_then(update<Person>("age"_c.set(46)))
.and_then(commit);
Pool Statistics
The connection pool provides methods to monitor its state:
using namespace sqlgen;
const auto pool = make_connection_pool<postgres::Connection>(config, credentials);
// Get total number of connections
const size_t total_connections = pool.value().size(); // Returns 4
// Get number of available connections
const size_t available_connections = pool.value().available(); // Returns 4 initially
Connection Acquisition
The pool implements a retry mechanism when acquiring connections:
- If no connection is available, the pool will retry up to
num_attemptstimes - Between each attempt, it waits for
wait_time_in_seconds - If all attempts fail, an error is returned
- This helps handle temporary connection unavailability
Example of handling connection acquisition with retries:
using namespace sqlgen;
// Configure pool with retry behavior
ConnectionPoolConfig config{
.size = 4,
.num_attempts = 5,
.wait_time_in_seconds = 2
};
const auto pool = make_connection_pool<postgres::Connection>(config, credentials);
// Acquire a session - will retry if no connections are available
const auto session_result = session(pool);
Best Practices
-
Pool Size: Choose an appropriate pool size based on your application's needs:
- Too small: May cause connection wait times
- Too large: May waste resources
- Rule of thumb: Start with (2 * number of CPU cores)
-
Retry Configuration: Configure retry behavior based on your use case:
- For high-availability systems: Use more retry attempts with shorter wait times
- For resource-constrained systems: Use fewer retries with longer wait times
- Consider your application's latency requirements when setting wait times
-
Session Lifetime: Keep sessions as short as possible:
// Good: Session is released immediately after use session(pool).and_then(execute_query).value(); // Bad: Session is held longer than necessary const auto sess = session(pool).value(); // ... other operations ... sess.execute_query(); -
Transaction Management: Use transactions within sessions for atomic operations:
session(pool) .and_then(begin_transaction) .and_then(update<Person>("age"_c.set(46))) .and_then(commit) .value();
Notes
- The connection pool is template-based and works with any sqlgen connection type
- Sessions are move-only types (cannot be copied)
- The pool automatically cleans up connections when destroyed
- All operations return
Resulttypes for error handling - The pool is designed to be efficient and minimize contention
- Connection acquisition is non-blocking (returns error if no connections available)