Commit 50bf53b
feat: rolling_deploy_adapter for seeding previous bundle hashes (#3173)
## Summary
Adds a pluggable **`config.rolling_deploy_adapter`** protocol that
eliminates 410→retry cold-start round-trips for _previously-deployed_
bundle hashes during rolling deploys — complementing the current-hash
seeding already handled by PRs #3124 + #3167.
Stacked on #3167 (PR B). Target base is
\`jg/3122-unify-renderer-cache-staging\`; will rebase onto \`master\`
once PRs A + B merge. Refs #3122. Design doc previously shared: see
earlier session context.
## Why
PR A seeded the current hash. PR B unified copy/symlink staging. But
during a rolling deploy:
- Old Rails instances (hash \`abc\`) still drain traffic.
- New Rails instances (hash \`def\`) serve new traffic.
- New renderer instances receive requests for **both** hashes.
Pre-seeding only the current hash leaves \`abc\` cold on new renderers →
every draining-version request hits the 410 retry path. This PR closes
that gap.
## Protocol
```ruby
module MyRollingDeployAdapter
def self.previous_bundle_hashes # Array<String>
def self.fetch(bundle_hash) # Hash(:bundle, :assets) | nil
def self.upload(bundle_hash, bundle:, assets:)
end
ReactOnRailsPro.configure do |config|
config.rolling_deploy_adapter = MyRollingDeployAdapter
end
```
Parallel in shape to \`remote_bundle_cache_adapter\` — same duck-typed
module pattern, same validation at configure time.
## The loadable-stats wrinkle
Each bundle hash must carry its **own** companion
\`loadable-stats.json\` and RSC manifests (built in lockstep with that
bundle). Otherwise the renderer emits HTML referencing chunk URLs for
the wrong build → client-side hydration breakage. The \`fetch\`
signature returns bundle + assets _together_ to force callers to think
about this; see \`docs/pro/rolling-deploy-adapters.md\` for the full
discussion.
## Integration points
1. **\`PreSeedRendererCache.call\`** (from PR B) now invokes
\`RollingDeployCacheStager\` after staging the current hash(es).
Deduplicates against current hashes.
2. **\`AssetsPrecompile.call\`** auto-invokes
\`adapter.upload(current_hash, ...)\` after precompile in
production-like environments, closing the publish side so the next
deploy can fetch.
3. **\`PREVIOUS_BUNDLE_HASHES\`** env var (comma-separated) overrides
\`previous_bundle_hashes\` discovery for CI / testing.
## Error handling — degrades gracefully
Runtime 410-retry remains the fallback for any seeding failure, so
rolling-deploy seeding can never cause a correctness regression:
| Scenario | Behavior |
| --------------------------------- | ---------------------------------
|
| Adapter not configured | No-op. |
| \`previous_bundle_hashes\` raises | Warn, skip previous-hash seeding.
|
| \`fetch\` returns nil or raises | Warn, skip that hash. |
| \`upload\` raises in precompile | Warn, don't fail precompile. |
| Returned hash matches current | Deduped. |
## Doctor (\`react_on_rails:doctor\`) probes
- ✅ Protocol conformance (all three required methods).
- ✅ \`previous_bundle_hashes\` probe with 10s timeout — reports latency
and count.
- 1 parent 03b048f commit 50bf53b
20 files changed
Lines changed: 3123 additions & 98 deletions
File tree
- docs
- pro
- react_on_rails_pro
- lib
- react_on_rails_pro
- spec
- dummy/spec
- react_on_rails_pro
- react_on_rails
- lib
- generators/react_on_rails/generator_messages
- react_on_rails
- spec/lib/react_on_rails
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
121 | 121 | | |
122 | 122 | | |
123 | 123 | | |
| 124 | + | |
124 | 125 | | |
125 | 126 | | |
126 | 127 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
| 41 | + | |
| 42 | + | |
41 | 43 | | |
42 | 44 | | |
| 45 | + | |
43 | 46 | | |
44 | 47 | | |
45 | 48 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
171 | 171 | | |
172 | 172 | | |
173 | 173 | | |
174 | | - | |
| 174 | + | |
175 | 175 | | |
176 | | - | |
| 176 | + | |
177 | 177 | | |
178 | | - | |
179 | | - | |
180 | | - | |
181 | | - | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
182 | 181 | | |
183 | | - | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
184 | 192 | | |
185 | 193 | | |
186 | 194 | | |
| 195 | + | |
187 | 196 | | |
188 | 197 | | |
189 | 198 | | |
| |||
0 commit comments