Skip to content

WIP: fix(features): preserve multivariate bucketing salt across feature state recreation#7914

Draft
gagantrivedi wants to merge 1 commit into
mainfrom
fix/7913-mv-hashing-salt
Draft

WIP: fix(features): preserve multivariate bucketing salt across feature state recreation#7914
gagantrivedi wants to merge 1 commit into
mainfrom
fix/7913-mv-hashing-salt

Conversation

@gagantrivedi

Copy link
Copy Markdown
Member

Thanks for submitting a PR! Please check the boxes below:

  • I have read the Contributing Guide.
  • I have added information to docs/ if required so people know about the feature.
  • I have filled in the "Changes" section below.
  • I have filled in the "How did you test this code" section below.

Changes

Closes #7913

⚠️ WIP — opening early for review; full test suite still being validated.

Multivariate variant bucketing is salted on the feature state id
(FeatureState.get_multivariate_feature_state_value hashes [self.id, identity_hash_key]).
Any flow that recreates a feature state — publishing a new version or editing
multivariate weights under v2 versioning — produced a new id, which changed the
salt and re-bucketed already-enrolled identities (the regression behind the
experiment rollout reshuffle in #7912).

This adds a stable seed that survives recreation:

  • FeatureState.mv_hashing_salt (nullable IntegerField, migration 0067).
    get_multivariate_feature_state_value now seeds on self.mv_hashing_salt or self.id.
  • clone() sets clone.mv_hashing_salt = self.mv_hashing_salt or self.id,
    capturing the source id the first time and carrying it down every subsequent
    clone. No backfill needed: existing rows fall back to their id (buckets don't
    move on deploy); the value only materialises at the moment of a clone — exactly
    when the id would otherwise drift.
  • Engine mapper sets django_id = feature_state.mv_hashing_salt or feature_state.pk,
    so the engine document, the context-mapper key, and local-eval SDKs all bucket
    on the salt — no engine model or environment document schema change.
  • Guard — a BEFORE_CREATE hook raises if a v2 feature state is recreated
    outside clone() (the previous version already had a state for this
    environment/feature/segment but the new row carries no salt). Identity
    overrides are skipped (they are never versioned or cloned). This is a tripwire
    to catch future code paths that bypass clone().

An audit confirmed clone() is currently the only path that recreates an
existing feature state — every direct FeatureState.objects.create is a
genuinely new lineage — so the chokepoint assumption holds today.

How did you test this code?

Unit tests added/updated in tests/unit/features/test_unit_features_models.py
and tests/unit/util/mappers/test_unit_mappers_engine.py:

  • bucketing seed uses the salt when set and falls back to the id when not;
  • cloning a multivariate feature state keeps 50 identities in the same variant;
  • an existing salt is preserved across clone;
  • the engine mapper uses the salt as django_id;
  • the guard raises when an override is recreated directly but allows a genuinely
    new override.

make lint, make typecheck and the affected unit/integration test files pass
locally. Full suite run is in progress.

…ate recreation

Multivariate variant bucketing is salted on the feature state id, so any flow
that recreates a feature state (publishing a new v2 version, editing
multivariate weights) changed the salt and re-bucketed already-enrolled
identities.

Add an mv_hashing_salt field that get_multivariate_feature_state_value seeds on
(falling back to the id), preserved across clone() so recreation keeps the
original seed. The engine mapper feeds the salt through django_id, so edge and
local evaluation stay stable without an engine document schema change.

Also add a BEFORE_CREATE guard that raises when a v2 feature state is recreated
outside clone() (the previous version already had it but the new row carries no
salt), to catch future regressions in tests.

Closes #7913
@vercel

vercel Bot commented Jun 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

3 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs Ignored Ignored Jun 30, 2026 9:04am
flagsmith-frontend-preview Ignored Ignored Jun 30, 2026 9:04am
flagsmith-frontend-staging Ignored Ignored Jun 30, 2026 9:04am

Request Review

@github-actions github-actions Bot added the api Issue related to the REST API label Jun 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Issue related to the REST API

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MV variant bucketing reshuffles when feature state is recreated (v2 versioning)

1 participant