Nhảy tới nội dung

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 địnhKiểu
1int

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 địnhKiểu
10int

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ự:

  1. Còn resource idle trong channel → lấy ra dùng ngay
  2. Channel rỗng nhưng currentConnections < max_connections → tạo mới
  3. Đã đạ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 địnhKiểu
3.0float

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 địnhKiểu
-1float

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:
    1. Lấy từng resource idle ra khỏi channel
    2. Kiểm tra thời gian idle: nếu vượt max_idle_time → destroy
    3. Gọi ping(): nếu thất bại → destroy
    4. Nếu còn OK → trả lại channel
    5. 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 địnhKiểu
60.0float

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

OptionMặc địnhVai trò
min_connections1Số resource luôn giữ sẵn
max_connections10Giới hạn tối đa resource đồng thời
wait_timeout3.0Thời gian chờ khi pool đầy (giây)
heartbeat-1Chu kỳ kiểm tra sức khỏe resource (giây, -1 = tắt)
max_idle_time60.0Thờ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,
)