Repository: getsentry/sentry

Issue:
IntegrityError: IntegrityError('duplicate key value violates unique constraint "sentry_featureadoption_organizati...
https://sentry.io/sentry/sentry/issues/299179840/

```
IntegrityError: IntegrityError('duplicate key value violates unique constraint "sentry_featureadoption_organization_id_78451b8747a9e638_uniq"\nDETAIL:  Key (organization_id, feature_id)=(****, 0) already exists.\n',)
SQL: INSERT INTO "sentry_featureadoption" ("organization_id", "feature_id", "date_completed", "complete", "applicable", "data") VALUES (%s, %s, %s, %s, %s, %s), (%s, %s, %s, %s, %s, %s)
(12 additional frame(s) were not displayed)
...
  File "sentry/db/postgres/decorators.py", line 74, in inner
    return func(self, *args, **kwargs)
  File "sentry/db/postgres/decorators.py", line 21, in inner
    return func(self, *args, **kwargs)
  File "sentry/db/postgres/decorators.py", line 96, in inner
    six.reraise(exc_info[0], exc_info[0](msg), exc_info[2])
  File "sentry/db/postgres/decorators.py", line 89, in inner
    return func(self, sql, *args, **kwargs)
  File "sentry/db/postgres/base.py", line 39, in execute
    return self.cursor.execute(sql, params)

IntegrityError('duplicate key value violates unique constraint "sentry_featureadoption_organization_id_78451b8747a9e638_uniq"\nDETAIL:  Key (organization_id, feature_id)=(44991, 0) already exists.\n',)
SQL: INSERT INTO "sentry_featureadoption" ("organization_id", "feature_id", "date_completed", "complete", "applicable", "data") VALUES (%s, %s, %s, %s, %s, %s), (%s, %s, %s, %s, %s, %s)
```


Discussion:
Looking at [this event](https://sentry.io/sentry/sentry/issues/299179840/events/6256359780/).

The feature_id's `3` and `41` were somehow not in the redis cache when we attempted to insert. However, the database shows that the feature_id's `3` and `41` were successfully inserted around this exactly same time (to the millisecond). Spot checking a few others, I'm seeing the same situation—we hit an IntegrityError but somehow the values are being correctly inserted at the exact millisecond.
Update: this is happening because multiple events are being processed in parallel. We haven't added these features to the redis cache (hence them showing up in `incomplete_feature_ids`), but we've already inserted them into the database.

This is enough of an edge case that it's not massively detrimental (few organizations send multiple events in a single millisecond and those who do, we can catch the lost feature adoption signals on the next event).

Resolving patch:
diff --git a/src/sentry/models/featureadoption.py b/src/sentry/models/featureadoption.py
--- a/src/sentry/models/featureadoption.py
+++ b/src/sentry/models/featureadoption.py
@@ -175,10 +175,12 @@ def bulk_record(self, organization_id, feature_slugs, **kwargs):
                 self.bulk_create(features)
                 return True
 
-        except IntegrityError as e:
+        except IntegrityError:
             # This can occur if redis somehow loses the set of complete features and
             # we attempt to insert duplicate (org_id, feature_id) rows
-            logger.exception(e)
+            # This also will happen if we get parallel processes running `bulk_record` and
+            # `get_all_cache` returns in the second process before the first process
+            # can `bulk_set_cache`.
             return False
         finally:
             return self.bulk_set_cache(organization_id, *incomplete_feature_ids)


Test patch: