Object Pool — reusing expensive objects
Advanced5 min read·eng-02-006
performancecompare
Concept
Object Pool pattern maintains a pool of pre-initialized, reusable objects. Instead of creating and destroying objects repeatedly, objects are "checked out" from the pool, used, then returned for reuse.
When to use Object Pool:
- Object creation is expensive: database connections, network sockets, thread/process handles, large buffers.
- Objects can be reset to a clean state after use.
- High-frequency creation/destruction causes performance issues (GC pressure, connection overhead).
- Database connection pools are the canonical example — creating a new DB connection takes 50-100ms. A pool keeps N connections ready.
Pool mechanics:
- Pre-create a set of objects (or create lazily on first demand).
- On
acquire(): return an available object, or wait/create more if pool is empty. - On
release(): reset the object and return it to the pool.
PHP context: PHP is typically request-scoped — the process (and all objects) die at request end. A traditional in-process object pool has limited value since the process restarts. However:
- PDO connection pools: Not native in PHP, but
pg_pconnect()andPDO::ATTR_PERSISTENTprovide persistent connections via FastCGI. - Laravel Octane/Swoole: Long-running PHP processes where object pools make sense.
- External pools: Connection poolers like PgBouncer (PostgreSQL) or ProxySQL (MySQL) live outside PHP.
SplQueue for implementation: PHP's built-in SplQueue provides efficient FIFO access for pool management.
Code Example
php
<?php
// Simple object pool implementation
interface Poolable
{
public function reset(): void; // called when returning to pool
public function isHealthy(): bool; // can the pool reuse this object?
}
class DatabaseConnection implements Poolable
{
private ?\PDO $pdo = null;
public function __construct(private readonly string $dsn, private readonly string $user, private readonly string $pass) {}
public function connect(): void
{
$this->pdo = new \PDO($this->dsn, $this->user, $this->pass, [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
]);
}
public function query(string $sql, array $bindings = []): array
{
$stmt = $this->pdo->prepare($sql);
$stmt->execute($bindings);
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
public function reset(): void
{
// For DB connections, ensure no open transactions
if ($this->pdo->inTransaction()) {
$this->pdo->rollBack();
}
}
public function isHealthy(): bool
{
try {
$this->pdo?->query('SELECT 1');
return true;
} catch (\PDOException) {
return false;
}
}
}
class ObjectPool
{
private \SplQueue $available;
private array $inUse = [];
private int $size = 0;
public function __construct(
private readonly int $maxSize,
private readonly callable $factory,
) {
$this->available = new \SplQueue();
}
public function acquire(): mixed
{
if (!$this->available->isEmpty()) {
$obj = $this->available->dequeue();
$this->inUse[spl_object_id($obj)] = $obj;
return $obj;
}
if ($this->size < $this->maxSize) {
$obj = ($this->factory)();
$this->size++;
$this->inUse[spl_object_id($obj)] = $obj;
return $obj;
}
throw new \RuntimeException("Object pool exhausted (max: {$this->maxSize}).");
}
public function release(mixed $obj): void
{
$id = spl_object_id($obj);
unset($this->inUse[$id]);
if ($obj instanceof Poolable && $obj->isHealthy()) {
$obj->reset();
$this->available->enqueue($obj);
} else {
$this->size--; // discard unhealthy object
}
}
public function availableCount(): int { return $this->available->count(); }
public function inUseCount(): int { return count($this->inUse); }
}
// Real-world note: for production PHP, use external connection poolers:
// PostgreSQL: PgBouncer (pgbouncer.org)
// MySQL: ProxySQL (proxysql.com)
// Laravel Octane + Swoole: built-in connection reuse
## Interview Q&A
**Q: Does Object Pool make sense in traditional PHP request/response PHP?**
A: Rarely. Traditional PHP (FPM) creates a new process per request — all objects die at request end, so pooling within a request provides little benefit. The exception is when you need multiple connections within a single request. External poolers (PgBouncer, ProxySQL) are more appropriate. Object Pool makes more sense with Swoole/Octane where PHP processes are long-lived and handle multiple requests.