Skip to content

A v2 versioning write costs queries proportional to the flag's segment overrides #8312

Description

@khvn26

Every write to a flag in a v2-versioned environment opens a new EnvironmentFeatureVersion and publishes it. Both halves of that cost grow with the number of feature states the flag has, so a flag with many segment overrides pays a query count proportional to its overrides.

Measured on main with CaptureQueriesContext around EnvironmentFeatureVersion.objects.create(...) and version.publish(...), for a flag with 1 and with 3 segment overrides (2 and 4 feature states):

Step 1 override 3 overrides Per extra state
EnvironmentFeatureVersion.objects.create 26 54 ~8
version.publish 45 61 ~5

Extrapolated, a flag with 50 segment overrides costs roughly 400 queries to open a version and 250 to publish it. Every v2 write path pays this: the versioning serializers, change request commits, and the experimental update-flag endpoints alike.

Creating a version copies row by row

FeatureState.clone (api/features/models.py:661) is a deepcopy plus save() per state, and FeatureSegment.clone (api/features/models.py:328) does the same. Per cloned state the create issues, roughly:

  • SELECT features_feature
  • SELECT features_featurestatevalue
  • INSERT features_featurestate and INSERT features_historicalfeaturestate
  • INSERT features_featurestatevalue and INSERT features_historicalfeaturestatevalue
  • a SAVEPOINT / RELEASE pair

Some copying is inherent to immutable versions, but the row-at-a-time shape is not: bulk_create for the states and their values, with the history rows batched alongside, would collapse most of it.

Publishing re-fetches relations per state

This half looks like a plain N+1 rather than inherent work. Between 1 and 3 overrides, publish goes from 45 to 61 queries, and the growth is all in per-state relation lookups:

Query 1 override 3 overrides
SELECT multivariate_multivariatefeaturestatevalue 6 10
SELECT features_feature 4 8
SELECT features_featurestatevalue 4 8
SELECT features_featuresegment 3 7

Whatever the publish path and its signal handlers iterate should be able to hydrate these with select_related / prefetch_related in one pass.

Suggested scope

  1. Flatten the publish-side N+1 first — it is the cheaper fix and is not intrinsic to versioning.
  2. Then consider bulk-creating the cloned states, values and multivariate values when a version is created.

Worth pinning either fix with a query-count test at the service level.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions