def migrate_subscriptions(apps, schema_editor): QuerySubscription = apps.get_model("sentry", "QuerySubscription") for subscription in RangeQuerySetWrapperWithProgressBar( QuerySubscription.objects.select_related("snuba_query").all()): if subscription.subscription_id is not None: subscription_id = None try: subscription_id = _create_in_snuba(subscription) except Exception as e: logging.exception( f"failed to recreate {subscription.subscription_id}: {e}") continue try: _delete_from_snuba( QueryDatasets(subscription.snuba_query.dataset), subscription.subscription_id, ) except Exception as e: try: # Delete the subscription we just created to avoid orphans _delete_from_snuba( QueryDatasets(subscription.snuba_query.dataset), subscription_id, ) except Exception as oe: logging.exception( f"failed to delete orphan {subscription_id}: {oe}") logging.exception( f"failed to delete {subscription.subscription_id}: {e}") continue subscription.update(subscription_id=subscription_id)
def migrate_subscriptions(apps, schema_editor): QuerySubscription = apps.get_model("sentry", "QuerySubscription") AppSnubaQueryEventType = apps.get_model("sentry", "SnubaQueryEventType") for subscription in RangeQuerySetWrapperWithProgressBar( QuerySubscription.objects.select_related("snuba_query").all()): if subscription.subscription_id is not None: # The migration apps don't build this property, so manually set it. raw_event_types = AppSnubaQueryEventType.objects.filter( snuba_query=subscription.snuba_query).all() event_types = [ SnubaQueryEventType.EventType(ev.type) for ev in raw_event_types ] setattr(subscription.snuba_query, "event_types", event_types) subscription_id = None try: subscription_id = _create_in_snuba(subscription) except Exception as e: logging.exception( f"failed to recreate {subscription.subscription_id}: {e}") continue try: _delete_from_snuba( QueryDatasets(subscription.snuba_query.dataset), subscription.subscription_id, ) except Exception as e: try: # Delete the subscription we just created to avoid orphans _delete_from_snuba( QueryDatasets(subscription.snuba_query.dataset), subscription_id, ) except Exception as oe: logging.exception( f"failed to delete orphan {subscription_id}: {oe}") logging.exception( f"failed to delete {subscription.subscription_id}: {e}") continue QuerySubscription.objects.filter(id=subscription.id).update( subscription_id=subscription_id)
def handle_message(self, message): """ Parses the value from Kafka, and if valid passes the payload to the callback defined by the subscription. If the subscription has been removed, or no longer has a valid callback then just log metrics/errors and continue. :param message: :return: """ with sentry_sdk.push_scope() as scope: try: with metrics.timer( "snuba_query_subscriber.parse_message_value"): contents = self.parse_message_value(message.value()) except InvalidMessageError: # If the message is in an invalid format, just log the error # and continue logger.exception( "Subscription update could not be parsed", extra={ "offset": message.offset(), "partition": message.partition(), "value": message.value(), }, ) return scope.set_tag("query_subscription_id", contents["subscription_id"]) try: with metrics.timer( "snuba_query_subscriber.fetch_subscription"): subscription = QuerySubscription.objects.get_from_cache( subscription_id=contents["subscription_id"]) if subscription.status != QuerySubscription.Status.ACTIVE.value: metrics.incr( "snuba_query_subscriber.subscription_inactive") return except QuerySubscription.DoesNotExist: metrics.incr( "snuba_query_subscriber.subscription_doesnt_exist") logger.error( "Received subscription update, but subscription does not exist", extra={ "offset": message.offset(), "partition": message.partition(), "value": message.value(), }, ) try: _delete_from_snuba(self.topic_to_dataset[message.topic()], contents["subscription_id"]) except Exception: logger.exception( "Failed to delete unused subscription from snuba.") return if subscription.type not in subscriber_registry: metrics.incr( "snuba_query_subscriber.subscription_type_not_registered") logger.error( "Received subscription update, but no subscription handler registered", extra={ "offset": message.offset(), "partition": message.partition(), "value": message.value(), }, ) return logger.debug( "query-subscription-consumer.handle_message", extra={ "timestamp": contents["timestamp"], "query_subscription_id": contents["subscription_id"], "project_id": subscription.project_id, "subscription_dataset": subscription.snuba_query.dataset, "subscription_query": subscription.snuba_query.query, "subscription_aggregation": subscription.snuba_query.aggregate, "subscription_time_window": subscription.snuba_query.time_window, "subscription_resolution": subscription.snuba_query.resolution, "offset": message.offset(), "partition": message.partition(), "value": message.value(), }, ) callback = subscriber_registry[subscription.type] with sentry_sdk.start_span( op="process_message") as span, metrics.timer( "snuba_query_subscriber.callback.duration", instance=subscription.type): span.set_data("payload", contents) callback(contents, subscription)
def handle_message(self, message: Message) -> None: """ Parses the value from Kafka, and if valid passes the payload to the callback defined by the subscription. If the subscription has been removed, or no longer has a valid callback then just log metrics/errors and continue. :param message: :return: """ # set a commit time deadline only after the first message for this batch is seen if not self.__batch_deadline: self.__batch_deadline = self.commit_batch_timeout_ms / 1000.0 + time.time() with sentry_sdk.push_scope() as scope: try: with metrics.timer("snuba_query_subscriber.parse_message_value"): contents = self.parse_message_value(message.value()) except InvalidMessageError: # If the message is in an invalid format, just log the error # and continue logger.exception( "Subscription update could not be parsed", extra={ "offset": message.offset(), "partition": message.partition(), "value": message.value(), }, ) return scope.set_tag("query_subscription_id", contents["subscription_id"]) try: with metrics.timer("snuba_query_subscriber.fetch_subscription"): subscription: QuerySubscription = QuerySubscription.objects.get_from_cache( subscription_id=contents["subscription_id"] ) if subscription.status != QuerySubscription.Status.ACTIVE.value: metrics.incr("snuba_query_subscriber.subscription_inactive") return except QuerySubscription.DoesNotExist: metrics.incr("snuba_query_subscriber.subscription_doesnt_exist") logger.error( "Received subscription update, but subscription does not exist", extra={ "offset": message.offset(), "partition": message.partition(), "value": message.value(), }, ) try: if "entity" in contents: entity_key = contents["entity"] else: # XXX(ahmed): Remove this logic. This was kept here as backwards compat # for subscription updates with schema version `2`. However schema version 3 # sends the "entity" in the payload entity_regex = r"^(MATCH|match)[ ]*\(([^)]+)\)" entity_match = re.match(entity_regex, contents["request"]["query"]) if not entity_match: raise InvalidMessageError( "Unable to fetch entity from query in message" ) entity_key = entity_match.group(2) _delete_from_snuba( self.topic_to_dataset[message.topic()], contents["subscription_id"], EntityKey(entity_key), ) except InvalidMessageError as e: logger.exception(e) except Exception: logger.exception("Failed to delete unused subscription from snuba.") return if subscription.type not in subscriber_registry: metrics.incr("snuba_query_subscriber.subscription_type_not_registered") logger.error( "Received subscription update, but no subscription handler registered", extra={ "offset": message.offset(), "partition": message.partition(), "value": message.value(), }, ) return sentry_sdk.set_tag("project_id", subscription.project_id) sentry_sdk.set_tag("query_subscription_id", contents["subscription_id"]) callback = subscriber_registry[subscription.type] with sentry_sdk.start_span(op="process_message") as span, metrics.timer( "snuba_query_subscriber.callback.duration", instance=subscription.type ): span.set_data("payload", contents) span.set_data("subscription_dataset", subscription.snuba_query.dataset) span.set_data("subscription_query", subscription.snuba_query.query) span.set_data("subscription_aggregation", subscription.snuba_query.aggregate) span.set_data("subscription_time_window", subscription.snuba_query.time_window) span.set_data("subscription_resolution", subscription.snuba_query.resolution) span.set_data("message_offset", message.offset()) span.set_data("message_partition", message.partition()) span.set_data("message_value", message.value()) callback(contents, subscription)