perf: Speed up dev server startup (two flavors: parallelism and streaming)#739
Open
davidbrackbill wants to merge 2 commits into
Open
perf: Speed up dev server startup (two flavors: parallelism and streaming)#739davidbrackbill wants to merge 2 commits into
davidbrackbill wants to merge 2 commits into
Conversation
The dev server paginated the flag list serially (following next-links at 100/page), taking up to ~50s in CI on large projects. Fetch pages concurrently instead: pull page 0, and while pages come back full, fetch the rest in bounded concurrent batches until a short page ends the list. - New internal.FetchPagesConcurrently paginator (with tests), replacing the serial GetPaginatedItems. It never trusts the API's optional total count (which reads back as 0 when absent and would truncate large projects) - a short page is the only end-of-list signal. - Retry429s: guard against a nil response before dereferencing StatusCode, so a transport-level failure returns an error instead of panicking. Api.GetAllFlags keeps its signature, so nothing downstream changes. Claude <claude@anthropic.com>
5b3ba6e to
1636b3f
Compare
1636b3f to
c0ed5dd
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c0ed5dd. Configure here.
Opt-in mode that reports healthy in ~1s on large projects: load flag state off the SDK stream and resolve variation display names from REST in the background, instead of blocking startup on the full flag fetch. Default behavior is unchanged when the flag is off. The background fill reuses the concurrent GetAllFlags, so there's no separate pagination, no placeholder ids, and no stream value capture - it just defers the fetch that already exists. - refreshExternalState: in streaming mode, skip the blocking fetch and keep the stored variations, then schedule the fill after overrides are applied. - FillVariations: detached, panic-recovered, per-project singleflight, bounded retry/backoff; replaces variations via SetAvailableVariationsForProject. - UpdateProject: prune overrides by flag_state, not available_variations, so a resync during the fill window can't wipe live overrides. - variationsFromFlags: guard the nil variation id. - UI polls until variations arrive; a no-op in the default mode. Claude <claude@anthropic.com>
c0ed5dd to
7cd06ee
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Context
Some valid concerns were brought up around breaking changes. I've changed the code to use parallel, blocking boot by default and added the streaming endpoint as an opt-in. Respectively, startup becomes:
Streaming demo
streaming_sync.mov
Summary
On large projects the dev server took ~50s to pass its health check because it walked the flag list one page at a time. It now fetches the pages concurrently (~12s for 3,125 flags), and a new
--stream-flag-startupflag drops that to ~1s by serving flag state off the SDK stream and filling in variation display names afterward, in the background.1) Parallelize the blocking flag GET startup (non-breaking)
This reduces the startup time by about ~40s and is behavior-preserving: the flag list used to be paginated one page at a time, and now it's fetched concurrently. The only thing that really needs scrutiny is the paginator (
FetchPagesConcurrentlyand its tests).2) Use streaming to start up the server
Uses the streaming endpoint via
--stream-flag-startupmode. This fills flag values and backfills variation names using the parallel load in the background. The parts worth reading aremodel/fill_variations.goand the streaming branch inmodel/project.go.Blast radius: dev server startup only. We could update our CLI calls internally to use this if we want, without any breaking changes for customers.
Testing
FetchPagesConcurrently: single page, batch boundaries, ordering, a missing total count, and error propagation.Retry429s: a 429 returned with a non-nil error still retries, and a nil response returns without panicking.FillVariationsand the streaming sync path: the fill replaces variations from REST; streaming sync defers the fetch and schedules the fill after overrides; a resync with empty variations keeps live overrides.defaultproject (3,125 flags): default mode syncs in ~12s with names present; resync ~11.8s. Full Go suite, UI tests, lint, and dist build pass.--stream-flag-startup; default is a behavior-preserving speedupRetry429sresponse)recover()in the fill, nil-id fallback, and theRetry429snil-response guarderralonetotalCounttruncates large projectsApi.GetFlag, duplicated upsert loop, unsharedpending-constant, redundantoffset := offsetflag_state, notavailable_variationsSupersedes #733 and #738.
Note
Medium Risk
Changes dev-server sync, REST pagination, and override pruning; default path is a behavior-preserving speedup, while streaming mode defers variation metadata and relies on background fills and updated prune logic.
Overview
Dev server startup is faster on large projects by paging the LaunchDarkly REST flag list with bounded concurrency instead of walking
nextlinks one page at a time, plus a small fix so 429 retries still run when the API client returns an error alongside the response.Optional
--stream-flag-startuploads flag values from the SDK stream before listen (health ~1s on huge projects) and fills variation display names in the background viaFillVariations/SetAvailableVariationsForProject. Sync and add/patch project paths schedule that fill when the mode is on;refreshExternalStateskips blockingGetAllFlagsand keeps stored variations until the fill completes.Override pruning on resync now keys off
flag_state(viajson_each) instead ofavailable_variations, so overrides are not dropped while variations are still empty or lagging in streaming mode. Nil variation IDs from REST get a safe per-index fallback when building stored variations.Reviewed by Cursor Bugbot for commit 7cd06ee. Bugbot is set up for automated code reviews on this repo. Configure here.