@@ -10,3 +10,63 @@ The [GraphQL](https://graphql.org/) component of the [API Platform](https://api-
1010> [ report issues] ( https://github.com/api-platform/core/issues ) and
1111> [ send Pull Requests] ( https://github.com/api-platform/core/pulls )
1212> in the [ core API Platform repository] ( https://github.com/api-platform/core ) .
13+
14+ ## Subscription state and publication
15+
16+ The subscription registry stores IDs and selected fields. Individual-item payloads
17+ are represented by SHA-256 fingerprints in separate cache entries; collection
18+ subscriptions do not retain payloads or fingerprints. IDs and Mercure topics are
19+ unchanged.
20+
21+ Symfony wires two cache pools:
22+
23+ - ` api_platform.graphql.cache.subscription ` : the registration registry;
24+ - ` api_platform.graphql.cache.subscription_fingerprint ` : fingerprints, inheriting
25+ the registry pool's adapter configuration by default.
26+
27+ ` SubscriptionStore ` receives these pools and a Symfony ` LockFactory ` through
28+ injection. The ` api_platform.graphql.subscription.lock_factory ` alias points to
29+ Symfony's ` lock.factory ` , configured through ` framework.lock ` . It can instead
30+ reference a named lock factory when subscriptions need their own lock backend.
31+ API Platform does not construct or select a lock store. Query-only applications
32+ can leave locking disabled; registering subscriptions requires a configured
33+ factory and fails explicitly if it is missing. There is no unlocked fallback.
34+
35+ When sharing subscription caches between servers, configure a shared lock backend
36+ (such as Redis) accessible to every registration and publication worker. Both cache
37+ pools must also use shared, coherent storage. Cache pools expose no portable way
38+ to derive their connection for the Lock component; configure the cache and lock
39+ backends together. Local locks are suitable only when all writers share the same
40+ local storage and lock scope.
41+
42+ Registration and deletion lock their registry bucket. Successful item publications
43+ update their fingerprint under a separate per-subscription lock. Normalization,
44+ hashing, and Mercure/Messenger calls occur outside those locks. Versioned
45+ comparison detects fingerprints changed in the meantime. Conflicting completions
46+ invalidate the fingerprint so the next event is not incorrectly suppressed.
47+ Fingerprint write failures are logged and the stale fingerprint is discarded on
48+ a best-effort basis so delivery continues to other subscribers. Delete cleanup
49+ failures are also logged without dropping the delete recipients. Cache failures
50+ can leave stale entries if invalidation also fails; restore cache health and clear
51+ subscription state when necessary.
52+
53+ Concurrent identical publications can still occur; this is change suppression,
54+ not an exactly-once delivery or ordering guarantee.
55+
56+ The Doctrine publisher iterates ` SubscriptionManagerInterface::getUpdates() ` ,
57+ publishes each prepared update, then explicitly calls ` acknowledge() ` for that update.
58+ The manager handles normalization; the store owns fingerprint comparison and
59+ acknowledgement. Preparing or iterating updates does not advance their fingerprints.
60+ A fingerprint advances only after successful hub acceptance for synchronous delivery
61+ or Messenger dispatch acceptance for asynchronous delivery. Worker retries remain
62+ Messenger's responsibility; acceptance does not imply client delivery.
63+
64+ Custom subscription managers must implement the complete ` SubscriptionManagerInterface ` :
65+ registration receives the subscription operation, ` getUpdates() ` prepares updates
66+ for that operation, and ` acknowledge() ` records successful publication. The previous
67+ ` getPushPayloads() ` contract is removed.
68+
69+ The cache layout is internal and has no migration path. When upgrading, stop old
70+ registration and publication workers, clear subscription state, and re-establish
71+ subscriptions. Old and new workers must not share this registry. Fingerprint cache
72+ eviction can cause an additional publication without losing registrations.
0 commit comments