Skip to content

Segment membership inspection index core #7406

@khvn26

Description

@khvn26

Scope

Core data model

  • segment_membership Django app around the AtomBitmap model.
  • PK-as-ordinal: Identity.id is the bitmap ord.
  • One blob per atom (no sharding).
  • Atom canonical key: (environment_id, kind, property, operator, operand_canonical, segment_key). segment_key is populated only for atoms whose operator is % Split (the engine salts that operator by segment id).

Maintenance dispatch (self-hosted core envs)

All listed signals are gated, in order, by:

  • settings.SEGMENT_MEMBERSHIP_ENABLED (Django global).
  • segment_membership_index Flagsmith on Flagsmith flag (per-organisation; defaults true in self-hosted).

If either gate is off, the signal handler returns early without enqueuing a task.

Signal source Task enqueued Task body
Identity.post_save(created=True) process_identity_created(identity_id, environment_id) Set bits at identity.id in every "identifier" / "identity_key" atom in the env. One-shot per identity (those properties are immutable).
Trait.post_save (single-row write) process_traits_changed(identity_id, environment_id, changed_keys=[trait_key]) For each "trait" atom whose property is in changed_keys, re-evaluate against the identity's current trait state and flip the bit at identity.id.
Trait.post_delete (single-row delete) process_traits_changed(identity_id, environment_id, changed_keys=[trait_key]) Same as above; absence of the trait flips operators like is_set and equals.
Custom traits_changed signal emitted by Identity.update_traits (the bulk path) process_traits_changed(identity_id, environment_id, changed_keys=[…]) Same as above with every key touched by the bulk write.
Identity.post_delete process_identity_deleted(identity_id, environment_id) Clear the bit at identity.id from every atom in the env.
Segment.post_save, gated on version_of_id == id (canonical rows only — non-canonical revision snapshots created by the segment serialiser are skipped) process_segment_canonical_changed(segment_id) ensure_atoms for the segment, then backfill any new atoms across every env in the project.

Tasks are idempotent: each reads current state and recomputes the affected bits. Out-of-order delivery and at-least-once retries converge.

Other

  • Async maintenance through flagsmith-task-processor.
  • Management command backfill_segment_membership for ops use (full-env or single-segment rebuilds).
  • Tests covering the operator vocabulary with a differential check against get_evaluation_result.

Acceptance criteria

Gating

  • With SEGMENT_MEMBERSHIP_ENABLED=False, no signal handler enqueues a task.
  • With segment_membership_index=False for an organisation, no signal handler enqueues a task for projects in that organisation.
  • With both gates on, signals enqueue tasks per the dispatch table.

Per-signal behaviour (asserted via integration tests against a fresh env with one or more atoms backfilled)

  • Creating an Identity enqueues process_identity_created. After drain, the identity's PK is set in every "identifier" and "identity_key" atom for the env.
  • Creating a Trait enqueues process_traits_changed with changed_keys=[trait_key]. After drain, every "trait" atom whose property equals trait_key agrees with get_evaluation_result at that identity's PK.
  • Deleting a Trait enqueues process_traits_changed. After drain, the bit reflects the trait's absence (e.g. is_set=false flips true, equals flips false).
  • Identity.update_traits with N changed keys enqueues exactly one process_traits_changed task whose changed_keys is the union of those keys. After drain, every affected atom is correct.
  • Deleting an Identity enqueues process_identity_deleted. After drain, no bit remains set at that PK in any atom for the env.
  • Saving live Segment version enqueues process_segment_canonical_changed. Saving a non-live version does not.
  • After process_segment_canonical_changed drains, every atom newly required by the segment has a bitmap that matches get_evaluation_result for every identity in every env in the project.

Idempotency

  • Re-running any task with the same arguments leaves the bitmap unchanged (no double-flips, no resurrection of cleared bits).
  • Tasks enqueued out of order (e.g. a delete observed before its create due to retry) converge to the same bitmap as in-order delivery once both have drained.

Differential correctness

  • backfill_segment_membership --environment <id> --segment <id|all> produces a bitmap that matches get_evaluation_result for every identity over the operator vocabulary.

Metadata

Metadata

Assignees

No one assigned

    Labels

    apiIssue related to the REST API

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions