Skip to content

Commit ed023d7

Browse files
committed
ConnectionManager に probeReplicas() を追加 (Phase 5-1 サブ C-5)
cron からの能動的 health probe を v0.1 でカバーするため、全 read replica を順次 PDO 接続 + DO 1 ping で検査し、host:port → healthy のマップを返す public メソッドを追加した。失敗時は connection() と同じ規則で dead cache に記録する (auth → markPoolDead、それ以外 → markServerDead)。 設計判断: - dead-cache 状態に関わらず全 replica を probe する。プローブの目的は実時点 の健全性把握であり、既に dead 化された replica の現在状態も計測対象に 含める方が運用上有用なため - healthy probe で既存の dead mark をクリアしない。DeadReplicaCache 抽象は mark-alive を持たず、recovery は TTL 自然減衰に委ねる設計 - health_check 設定に関わらず常に DO 1 を発行する。プローブの目的が PDO 接続確認 + サーバー応答確認の両方を含むため - probe 用 Connection はインスタンスにキャッシュしない。リクエスト経路で 使われる replica 選択ロジックを probe 側が汚染しない構造を維持 テスト 14 件追加 (空 read / 全 healthy / 混在 / connect 失敗 / auth 失敗 / ping 失敗 / dead-cache 透過 + 成功時 mark 保持 / 失敗時 TTL リフレッシュ / probe 後の selection キャッシュ非汚染 / 既定 pool / 明示 pool 名 / undefined pool / バリデーション伝播 / port 反映)。
1 parent 4183761 commit ed023d7

2 files changed

Lines changed: 460 additions & 0 deletions

File tree

src/Sloop/Database/ConnectionManager.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@
3232
*
3333
* Empty `read` list collapses replica routing to the primary so single-pool
3434
* setups keep working without reconfiguration.
35+
*
36+
* probeReplicas() complements the per-request lazy detection by actively
37+
* connecting to every replica and refreshing the dead cache for failures.
38+
* Healthy probes never clear existing dead marks (recovery is bound by the
39+
* cache TTL), so it is best run from cron to warm the negative cache ahead
40+
* of request traffic rather than as a recovery mechanism.
3541
*/
3642
final class ConnectionManager
3743
{
@@ -189,6 +195,45 @@ private function getReplicaConnection(string $name): Connection
189195
);
190196
}
191197

198+
/**
199+
* Actively connect to every replica in a pool and report each result.
200+
*
201+
* Each replica is built via the ConnectionFactory and verified with
202+
* Connection::ping(); failures at either step are recorded in the dead
203+
* cache identically to a request-time miss (auth → markPoolDead, anything
204+
* else → markServerDead). Existing dead marks are not cleared on a healthy
205+
* probe — recovery still depends on the cache TTL — and the dead-cache
206+
* filter is intentionally bypassed so operators can see when a previously
207+
* marked replica has recovered. Probe connections are not cached on the
208+
* manager: each Connection is destroyed when its iteration ends so that
209+
* subsequent connection(writable: false) calls run normal selection.
210+
*
211+
* @param string|null $name Pool to probe; defaults to the configured default pool
212+
* @return array<string, bool> host:port → true (probe succeeded) | false (probe failed)
213+
* @throws InvalidConfigException When the pool is undefined or its config is malformed
214+
*/
215+
public function probeReplicas(?string $name = null): array
216+
{
217+
$poolName = $name ?? $this->defaultName;
218+
$pool = $this->resolvePool($poolName);
219+
$results = [];
220+
221+
foreach ($pool->replicas as $replica) {
222+
$key = $replica->host . ':' . ($replica->port ?? 0);
223+
224+
try {
225+
$connection = $this->factory->make($replica, $poolName);
226+
$connection->ping();
227+
$results[$key] = true;
228+
} catch (DatabaseException $e) {
229+
$this->recordReplicaFailure($pool, $replica, $e);
230+
$results[$key] = false;
231+
}
232+
}
233+
234+
return $results;
235+
}
236+
192237
/**
193238
* Resolve and validate the pool config for the given name.
194239
*

0 commit comments

Comments
 (0)