Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(feature-flags): Enable experience continuity with feature flags #10196

Closed
wants to merge 22 commits into from

Conversation

neilkakkar
Copy link
Collaborator

Problem

Have a look here: #9547 for context. We're going with option 3!

The code in posthog/models/feature_flag.py might seem a bit wrangly, but there's a good reason for it: My goal here was to make sure:

  1. When there are no special cased feature flags (experience_continuity enabled), there's no slowdown. decide endpoint takes exactly the same number of queries as before.
  2. When there are special cased feature flags, the increase in querying is minimal, and the response is sufficiently quick.

There's some cases we still don't handle well, specially to do with ingestion delays, but I don't think there's a simple way around that. We're borked anyway in that case, since person properties filtering won't work, which means the feature flag, even if it had experience continuity, wouldn't have worked.

Changes

👉 Stay up-to-date with PostHog coding conventions for a smoother review.

How did you test this code?

@clarkus
Copy link
Contributor

clarkus commented Jun 9, 2022

I put together an idea in figma for how we can communicate this functionality to users. https://www.figma.com/file/gQBj9YnNgD8YW4nBwCVLZf?node-id=5400:35869#208945086

Screen Shot 2022-06-09 at 12 12 19 PM

"posthog_featureflagoverride"."override_value",
"posthog_featureflag"."id",
"posthog_featureflag"."key"
FROM "posthog_featureflagoverride"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this cruft from existing overrides makes me sad.

Specially, since this applies to a very small minority of users: Those who have a posthog account, while being called for every user coming to the client's website.

At minimum, we should just check if this distinct ID belongs to a posthog user, and then use the value inside this query. Hmm, but then, if featureflagoverride table is small, not sure it makes a difference.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we kill this then? This won't affect any real operations and we could if we wanted to.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I think killing this will remove the toolbar feature flags functionality. Don't think it's used anywhere else, is it? (cc: @mariusandra @rcmarron )

At the very least, I suppose we could go down this code path only when asking for /my_flags or /decide has a parameter used by the toolbar to make it happen.

This way, we can pretty much get the best of both worlds, by keeping this functionality, and removing cruft completely for most users.

Although I do question if the toolbar override functionality is used at all, @marcushyett-ph ?

@neilkakkar neilkakkar marked this pull request as ready for review June 10, 2022 13:23
@neilkakkar
Copy link
Collaborator Author

neilkakkar commented Jun 10, 2022

Asking for ingestion side reviews as well, since there's an update when merging two persons together. I think it's fine to do as I have, since we're in a transaction, but lmk if I'm missing something, @tiina303 , @macobo .

Edit: Also, If I'm not mistaken, the deploy will ensure that migrations run before django & plugin server updates, right? Or do I need to split out the plugin-server part to a separate PR?

@@ -1475,6 +1476,184 @@ test('distinct with anonymous_id which was already created', async () => {
expect(person.is_identified).toEqual(true)
})

test('distinct with anonymous_id which was already created triggers foreign key updates', async () => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, should add a test in django-land for where old user is identified, which means we won't reach this code path, and no updates would occur. Will the override django code handle that?


// For FeatureFlagHashKeyOverrides
const allOverrides = await this.db.postgresQuery(
'SELECT id, person_id, feature_flag_key FROM posthog_featureflaghashkeyoverride WHERE team_id = $1 AND person_id = ANY($2)',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(didn't look further into this PR): Why do we select + do post-processing + update instead of a single update query? We're introducing a lot of new failure modes here.

Copy link
Contributor

@macobo macobo Jun 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A https://stackoverflow.com/questions/5874453/postgresql-way-to-insert-row-with-on-conflict-clause-semantics might work better if you're looking for UPDATE but avoids conflicts case. - just add ON CONFLICT DO NOTHING to the INSERT.

If you go this route, please also move this query to db.ts and give it proper unit tests - integration tests are nice but also really frustrating/can leave gaps.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooh, that's clever!

from django.db import migrations, models


class Migration(migrations.Migration):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Migration to a separate PR - otherwise this is unrevertable.

@@ -150,7 +150,9 @@ def get_decide(request: HttpRequest):
team = user.teams.get(id=project_id)

if team:
feature_flags = get_overridden_feature_flags(team.pk, data["distinct_id"], data.get("groups", {}))
feature_flags = get_overridden_feature_flags(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're touching /decide endpoint, that should be a separate PR to ensure easy control if something goes wrong. This single endpoint breaking can bring down all of posthog-js ingestion in nasty ways.

@@ -798,4 +792,62 @@ export class EventsProcessor {
)
}
}

private async handleTablesDependingOnPersonID(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This should be a separate PR given the risk of accidentally breaking ingestion.

) -> Dict[str, Union[bool, str, None]]:
feature_flags = get_active_feature_flags(team_id, distinct_id, groups)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this feature flag still used anywhere? If so, it's a sign that maybe it shouldn't be?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure I get this. Do you mean overrides only applying to active feature flags? Or something else?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean get_active_feature_flags as a function

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, lovely! Good shout

except IndexError:
person_id = None

if person_id is None and hash_key_override is not None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: Can we move if hash_key_override to a top level if and make the person_id if/else more obvious. Was going back and forth when looking at this sequence of checks

# existing person. If, because of race conditions, a person merge is called for later,
# then https://github.com/PostHog/posthog/blob/master/plugin-server/src/worker/ingestion/process-event.ts#L480
# will take care of it^.
try:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

posthog/models/feature_flag.py Show resolved Hide resolved
Copy link
Member

@EDsCODE EDsCODE left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I have not worked with the flag matching logic in a while so I may not be 100% aware of the potholes in all the related areas that are being affected here. But, rolling out according to @macobo's should help a lot

Good work!

@posthog-bot
Copy link
Contributor

This PR hasn't seen activity in a week! Should it be merged, closed, or further worked on? If you want to keep it open, post a comment or remove the stale label – otherwise this will be closed in another week.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants