ydb-platform / ydb-platform/ydb-php-sdk
dev: make discovery failover atomic and observable
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 39
- Forks
- 19
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 2
Description
Summary
Harden endpoint discovery and transport recovery for long-running PHP processes. A successful discovery refresh currently updates the endpoint pool, but it does not migrate existing Table/Session service clients. After a transport failure, the SDK may also construct a new stub that reuses the same persistent PHP gRPC channel.
This issue is about making the recovery contract deterministic and observable. It does not prescribe force_new on every request or claim that client behavior is the only possible cause of an outage.
Confirmed behavior in v1.16.3
A successful refresh does not update existing service clients
Ydb::discover() applies the response to Cluster and updates the current endpoint value:
Ydb::discover()andapplyDiscoveryResult()Cluster::sync()removes endpoints absent from the latest response
However, Table creates one ServiceClient for the endpoint selected at construction time, and each Session copies that client:
Therefore, a successful scheduled refresh does not by itself move existing Table/Session clients away from an endpoint removed by the latest discovery response.
Error recovery can reuse the same persistent underlying channel
For every non-zero gRPC status, RequestTrait::handleGrpcStatus() performs discovery, randomly picks an endpoint, and constructs a new service stub without force_new:
In PHP gRPC 1.73.0, channels with the same target and channel arguments are persistent. Without force_new, constructing a new stub may return the previously stored underlying channel:
The bootstrap discovery implementation already handles this explicitly after a failed attempt: it closes the old client and creates a new one with force_new=true:
There is no equivalent explicit policy for Table/Session transport recovery. If random selection returns the same hostname, a new PHP stub does not guarantee a new underlying channel or a fresh DNS resolution.
Discovery scheduling is per service/session object
lastDiscovery and lastDiscoveryAttempt are fields of RequestTrait, which is used independently by Table, Session, Scheme, Scripting, Operations, and other objects:
A newly created Session starts with zero-valued timers, so its first operation may trigger another ListEndpoints. Non-zero gRPC statuses also call discovery immediately outside the scheduled-refresh gate. Under many sessions/processes and correlated transport failures, this can amplify discovery traffic.
Current logs cannot reconstruct the recovery decision
The SDK does not log, in a structured way:
- the endpoint list returned by each refresh;
- the endpoint selected for a service client and why;
- a discovery/channel generation;
- whether a new underlying channel was forced;
- whether the selected endpoint had just disappeared from the latest discovery result.
This makes it difficult to distinguish a stale recursive resolver, a persistent gRPC channel, an old service client, and a valid selection of a temporarily unhealthy backend.
The documented contract says that discovery keeps the list fresh, balances subsequent requests, and re-discovers when a node becomes unavailable:
Expected behavior
For a long-running process with discovery => true:
- Removing an endpoint from a successful discovery result should cause existing service clients to stop using it within a bounded time.
- Recovery after a transport failure should not immediately reuse a just-failed endpoint/channel unless that is an explicit, tested policy.
- Service and Session objects belonging to one
Ydbinstance should share or deduplicate discovery refresh state. - Diagnostic logs should show the chain
bootstrap target -> discovery result -> selected endpoint -> channel generation/recreation -> RPC result, without credentials or other secrets. - Recovery should work with normal DNS TTL behavior and with a delayed recursive resolver.
The implementation may use generation-aware clients, explicit channel invalidation/close, temporary endpoint quarantine, a shared discovery coordinator, or another design. The observable contract and tests are more important than prescribing one mechanism.
Acceptance tests
- A long-lived Session continues after its endpoint disappears from the next successful
ListEndpointsresult. - The A record of the same hostname changes after a transport failure; recovery does not remain indefinitely on the old persistent channel.
- Of two endpoints, one shuts down and the other becomes ready; requests converge on the working endpoint without restarting the PHP process.
- Correlated gRPC failures from multiple Session objects produce a bounded number of
ListEndpointscalls rather than an independent refresh per object/request. - Debug logs expose the returned endpoint set, selected endpoint, and channel-generation/recreation decision without exposing authentication data.
Related issues
- #261
- #83
- #61
This client-side hardening cannot provide availability when no published backend is healthy; server-side rollout overlap remains a separate requirement.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with Ydb::discover(), Cluster::sync(), Table::__construct(), Session::__construct(), RequestTrait::handleGrpcStatus(), and checkDiscovery(); compare their behavior with Internal\Discovery::runRetryLoop() and recreateClient(). Use the five acceptance tests as the definition of done: clients recover from removed endpoints, channels are recreated when needed, refreshes are bounded, and debug logs expose decisions without credentials.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- grpc, php
- Domain
- backend-api-design, networking
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100