Pool
Pool là gì?
Pool là một module đa mục đích — nó quản lý và tái sử dụng bất kỳ loại resource nào tốn chi phí khởi tạo cao: database connection, HTTP client, gRPC client, hay bất kỳ object nào bạn muốn.
Không có pool:
Coroutine 1 → tạo resource → dùng → destroy
Coroutine 2 → tạo resource → dùng → destroy
Coroutine 3 → tạo resource → dùng → destroy
Có pool:
Khởi động → tạo sẵn N resource
Coroutine 1 → mượn resource → dùng resource → trả về pool
Coroutine 2 → mượn resource → dùng resource → trả về pool
Coroutine 3 → mượn resource → dùng resource → trả về pool
Trong môi trường Swoole/coroutine, hàng nghìn coroutine có thể chạy đồng thời. Pool kiểm soát số lượng resource tối đa, cho các coroutine hàng đợi và tái sử dụng thay vì tạo không giới hạn.
Tự tích hợp Pool
Để pool quản lý resource của bạn, implement ConnectionFactoryInterface:
use Vietiso\Core\Pool\ConnectionFactoryInterface;
class GrpcClientFactory implements ConnectionFactoryInterface
{
public function __construct(private array $config) {}
// Tạo một resource mới
public function create(): object
{
return new \Grpc\Channel(
$this->config['host'] . ':' . $this->config['port'],
['credentials' => \Grpc\ChannelCredentials::createInsecure()]
);
}
// Destroy resource (gọi khi idle quá lâu hoặc ping thất bại)
public function destroy(object $client): void
{
$client->close();
}
// Kiểm tra resource còn sống không (dùng trong heartbeat)
public function ping(object $client): bool
{
return $client->getConnectivityState() !== \Grpc\Channel::SHUTDOWN;
}
// Reset trạng thái trước khi trả về pool
public function reset(object $client): void
{
// gRPC channel không cần reset
}
// Thời gian (giây) kể từ lần dùng cuối — dùng để kiểm tra max_idle_time
public function getLastQueryTime(object $client): int
{
return time() - $client->getLastUsedTime();
}
}
Sau đó đăng ký pool mới
use Vietiso\Core\Pool\PoolManager;
use Vietiso\Core\Pool\PoolOption;
$manager = app('pool');
$manager->register(
name: 'grpc:user-service',
connectionFactory: new GrpcClientFactory($config),
options: new PoolOption(
minConnections: 2,
maxConnections: 20,
waitTimeout: 3.0,
heartbeat: 30,
maxIdleTime: 60.0,
),
);
// Lấy resource
$channel = $manager->get('grpc:user-service')->get();
// Trả về pool sau khi dùng xong
$manager->get('grpc:user-service')->release($channel);
Các Option
min_connections
| Mặc định | Kiểu |
|---|---|
1 | int |
Số resource tối thiểu pool luôn duy trì, kể cả khi không có coroutine nào đang dùng.
Sau mỗi chu kỳ heartbeat dọn dẹp các resource chết, pool tự tạo lại để đảm bảo số lượng không xuống dưới ngưỡng này.
minConnections: 2,
// Pool luôn giữ ít nhất 2 resource sẵn sàng
Khi nào tăng lên? Khi ứng dụng thường xuyên nhận request ngay từ khi khởi động và muốn tránh độ trễ khởi tạo resource.
max_connections
| Mặc định | Kiểu |
|---|---|
10 | int |
Số resource tối đa pool được phép tạo ra. Đây là giới hạn cứng.
Khi một coroutine cần resource, pool xử lý theo thứ tự:
- Còn resource idle trong channel → lấy ra dùng ngay
- Channel rỗng nhưng
currentConnections < max_connections→ tạo mới - Đã đạt max → chờ cho đến khi có resource được trả về hoặc hết
wait_timeout
maxConnections: 20,
// Tối đa 20 resource đồng thời
Lưu ý khi dùng nhiều worker process: Tổng max_connections của tất cả worker không được vượt quá giới hạn của service phía sau. Ví dụ MySQL cho phép 200 connections, 4 workers → mỗi worker đặt max là 50.
wait_timeout
| Mặc định | Kiểu |
|---|---|
3.0 | float |
Thời gian tối đa (giây) một coroutine chờ lấy resource khi pool đã đầy.
waitTimeout: 3.0,
// Chờ tối đa 3 giây, nếu vẫn không có → throw RuntimeException
Nếu hết thời gian mà không có resource nào được trả về:
RuntimeException: Connection pool exhausted. Cannot establish new connection before wait_timeout.
Khi nào tăng lên? Khi tác vụ thường chạy lâu và bạn muốn coroutine chờ thêm thay vì bị lỗi ngay. Tuy nhiên, nếu thường xuyên chạm wait_timeout thì vấn đề thực sự là max_connections quá thấp.
heartbeat
| Mặc định | Kiểu |
|---|---|
-1 | float |
Chu kỳ (giây) pool tự kiểm tra và làm mới các resource đang idle.
-1→ tắt heartbeat, pool không tự kiểm tra gì> 0(ví dụ30) → cứ mỗi 30 giây, pool thực hiện:- Lấy từng resource idle ra khỏi channel
- Kiểm tra thời gian idle: nếu vượt
max_idle_time→ destroy - Gọi
ping(): nếu thất bại → destroy - Nếu còn OK → trả lại channel
- Nếu số lượng còn lại <
min_connections→ tạo thêm mới
heartbeat: 30,
// Cứ mỗi 30 giây kiểm tra sức khỏe các resource idle
Tại sao cần heartbeat? Nhiều service (MySQL, Redis...) có cơ chế tự đóng connection không hoạt động sau một thời gian. Heartbeat phát hiện và loại bỏ các resource đã chết trước khi coroutine lấy ra dùng và gặp lỗi.
max_idle_time
| Mặc định | Kiểu |
|---|---|
60.0 | float |
Thời gian tối đa (giây) một resource được phép nằm idle trong pool mà không được sử dụng.
Pool kiểm tra giá trị này thông qua factory->getLastQueryTime() trong mỗi chu kỳ heartbeat. Nếu vượt quá → destroy ngay cả khi ping() vẫn OK.
maxIdleTime: 60.0,
// Resource idle quá 60 giây sẽ bị đóng và xóa khỏi pool
Lưu ý: Option này chỉ có tác dụng khi heartbeat > 0.
Tóm tắt
| Option | Mặc định | Vai trò |
|---|---|---|
min_connections | 1 | Số resource luôn giữ sẵn |
max_connections | 10 | Giới hạn tối đa resource đồng thời |
wait_timeout | 3.0 | Thời gian chờ khi pool đầy (giây) |
heartbeat | -1 | Chu kỳ kiểm tra sức khỏe resource (giây, -1 = tắt) |
max_idle_time | 60.0 | Thời gian idle tối đa trước khi resource bị destroy |
Cấu hình gợi ý
Server nhỏ (1-2 workers, tải thấp):
new PoolOption(
minConnections: 1,
maxConnections: 10,
waitTimeout: 3.0,
heartbeat: 60,
maxIdleTime: 300.0,
)
Server production (4+ workers, tải cao):
new PoolOption(
minConnections: 5,
maxConnections: 50,
waitTimeout: 5.0,
heartbeat: 30,
maxIdleTime: 120.0,
)