vbc-unmute: reduce calls to the expensive check

as done in the previous commit for project quota
This commit is contained in:
Klaas van Schelven
2024-07-17 15:33:15 +02:00
parent 51a53c09a4
commit 65ea181f37
3 changed files with 45 additions and 11 deletions

View File

@@ -290,6 +290,8 @@ class BaseIngestAPIView(View):
# nothing to check (otherwise) or update on project in this case, also abort further event-processing
return False
project.digested_event_count += 1
if project.digested_event_count >= project.next_quota_check:
# check_for_thresholds is relatively expensive (SQL group by); we do it as little as possible
@@ -314,33 +316,41 @@ class BaseIngestAPIView(View):
# next_quota_check (the if-statement) and the setting (the statement below) we're good.
project.next_quota_check = project.digested_event_count + check_again_after
project.digested_event_count += 1
project.save()
return True
@classmethod
def count_issue_periods_and_act_on_it(cls, issue, event, timestamp):
# See the project-version for various off-by-one notes (not reproduced here).
#
# We just have "unmute" as a purpose here, not "quota". I thought I'd have per-issue quota earlier (which would
# ensure some kind of fairness within a project) but:
#
# * that doesn't quite work, because to determine the issue, you'd have to incur almost all of the digest cost.
# * quota are expected to be set "high enough" anyway, i.e. only as a last line of defense against run-away
# clients
# * "even if" you'd get this to work there'd be scenarios where it's useless, e.g. misbehaving groupers.
thresholds = IssueStateManager.get_unmute_thresholds(issue)
states = check_for_thresholds(Event.objects.filter(issue=issue), timestamp, thresholds)
if thresholds and issue.digested_event_count >= issue.next_unmute_check:
states = check_for_thresholds(Event.objects.filter(issue=issue), timestamp, thresholds)
for (state, until, _, vbc_dict) in states:
if not state:
continue
check_again_after = max(1, min([check_after for (_, _, check_after, _) in states], default=1))
IssueStateManager.unmute(issue, triggering_event=event, unmute_metadata={"mute_until": vbc_dict})
issue.next_unmute_check = issue.digested_event_count + check_again_after
# In the (in the current UI impossible, and generally unlikely) case that multiple unmute conditions are met
# simultaneously, we arbitrarily break after the first. (this makes it so that a single TurningPoint is
# created and that the detail that there was also another reason to unmute doesn't show us, but that's
# perfectly fine); it also matches what we do elsewhere (i.e. 'if is_muted` in `IssueStateManager.unmute`)
break
for (state, until, _, vbc_dict) in states:
if not state:
continue
IssueStateManager.unmute(issue, triggering_event=event, unmute_metadata={"mute_until": vbc_dict})
# In the (in the current UI impossible, and generally unlikely) case that multiple unmute conditions are
# met simultaneously, we arbitrarily break after the first. (this makes it so that a single TurningPoint
# is created and that the detail that there was also another reason to unmute doesn't show us, but
# that's perfectly fine); it also matches what we do elsewhere (i.e. `IssueStateManager.unmute` where we
# have `if is_muted`)
break
class IngestEventAPIView(BaseIngestAPIView):

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.13 on 2024-07-17 13:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('issues', '0005_rename_ingest_order_issue_digest_order_and_more'),
]
operations = [
migrations.AddField(
model_name='issue',
name='next_unmute_check',
field=models.PositiveIntegerField(default=0),
),
]

View File

@@ -58,6 +58,7 @@ class Issue(models.Model):
is_muted = models.BooleanField(default=False)
unmute_on_volume_based_conditions = models.TextField(blank=False, null=False, default="[]") # json string
unmute_after = models.DateTimeField(blank=True, null=True)
next_unmute_check = models.PositiveIntegerField(null=False, default=0)
def save(self, *args, **kwargs):
if self.digest_order is None:
@@ -199,6 +200,10 @@ class IssueStateManager(object):
issue.is_muted = True
issue.unmute_on_volume_based_conditions = unmute_on_volume_based_conditions
# 0 is "incorrect" but works just fine; it simply means that the first (real, but expensive) check is done
# on-digest. However, to calculate the correct value we'd need to do that work right now, so postponing is
# actually better. Setting to 0 is still needed to ensure the check is done when there was already a value.
issue.next_unmute_check = 0
if unmute_after is not None:
issue.unmute_after = unmute_after
@@ -333,6 +338,7 @@ class IssueQuerysetStateManager(object):
issue_qs.update(
is_muted=True,
unmute_on_volume_based_conditions=unmute_on_volume_based_conditions,
next_unmute_check=0,
)
if unmute_after is not None: