diff --git a/arkindex/process/migrations/0061_workeractivity_updated_triggers.py b/arkindex/process/migrations/0061_workeractivity_updated_triggers.py new file mode 100644 index 0000000000000000000000000000000000000000..65b6dd3877cae2f1c04de85ba801f8b1ce40af07 --- /dev/null +++ b/arkindex/process/migrations/0061_workeractivity_updated_triggers.py @@ -0,0 +1,50 @@ +# Generated by Django 4.0.7 on 2022-10-19 16:05 + +from django.db import migrations, models + +import pgtrigger.compiler +import pgtrigger.migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('process', '0060_remove_repository_provider_name'), + ] + + operations = [ + migrations.AlterField( + model_name='workeractivity', + name='updated', + field=models.DateTimeField(auto_now_add=True), + ), + pgtrigger.migrations.AddTrigger( + model_name='workeractivity', + trigger=pgtrigger.compiler.Trigger( + name='update_workeractivity_updated', + sql=pgtrigger.compiler.UpsertTriggerSql( + func='NEW.updated = now(); RETURN NEW;', + hash='1e5a8fa0718f420e6cd4f2a31434cd39a9c9bc67', + operation='UPDATE', + pgid='pgtrigger_update_workeractivity_updated_f2812', + table='process_workeractivity', + when='BEFORE', + ) + ), + ), + pgtrigger.migrations.AddTrigger( + model_name='workeractivity', + trigger=pgtrigger.compiler.Trigger( + name='read_only_workeractivity_updated', + sql=pgtrigger.compiler.UpsertTriggerSql( + condition='WHEN (OLD."updated" IS DISTINCT FROM (NEW."updated"))', + func="RAISE EXCEPTION 'pgtrigger: Cannot update rows from % table', TG_TABLE_NAME;", + hash='6276c6971a1d2669659e407418e2db1fa7dc6965', + operation='UPDATE', + pgid='pgtrigger_read_only_workeractivity_updated_a80ab', + table='process_workeractivity', + when='BEFORE', + ) + ), + ), + ] diff --git a/arkindex/process/models.py b/arkindex/process/models.py index 7e49841faabad9e1ebd9288c89dc7424cd7f5379..31f0e008016e00c40cfdc999ded944c3a1a297ba 100644 --- a/arkindex/process/models.py +++ b/arkindex/process/models.py @@ -14,6 +14,7 @@ from django.utils.functional import cached_property from enumfields import Enum, EnumField from rest_framework.exceptions import ValidationError +import pgtrigger from arkindex.documents.models import Element from arkindex.images.models import ImageServer from arkindex.process.managers import ActivityManager, CorpusWorkerVersionManager @@ -867,13 +868,20 @@ class WorkerActivityState(Enum): Error = 'error' -class WorkerActivity(IndexableModel): +class WorkerActivity(models.Model): """ Many-to-many relationship between Element and WorkerVersion Used to track the activity of a worker version among multiple elements """ # Using an UUID helps to execute SQL raw INSERT id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + + created = models.DateTimeField(auto_now_add=True) + + # We will manually handle the timestamp update using a PostgreSQL trigger to avoid issues with bulk inserts/updates, + # and we still let Django handle setting the timestamp initially, so we use `auto_now_add` and not `auto_now`. + updated = models.DateTimeField(auto_now_add=True) + element = models.ForeignKey( 'documents.Element', on_delete=models.CASCADE, @@ -921,6 +929,18 @@ class WorkerActivity(IndexableModel): condition=Q(configuration__isnull=False), ) ] + triggers = [ + pgtrigger.Trigger( + name='update_workeractivity_updated', + operation=pgtrigger.Update, + when=pgtrigger.Before, + func='NEW.updated = now(); RETURN NEW;', + ), + pgtrigger.ReadOnly( + name='read_only_workeractivity_updated', + fields=['updated'], + ) + ] class CorpusWorkerVersion(models.Model): diff --git a/arkindex/process/tests/test_workeractivity.py b/arkindex/process/tests/test_workeractivity.py index 4eeba62d3ffd26f46a3b131c4cade6a2d35dfa83..e24f1ccde81ec18f683597afb513ecedd5c7a64b 100644 --- a/arkindex/process/tests/test_workeractivity.py +++ b/arkindex/process/tests/test_workeractivity.py @@ -124,7 +124,7 @@ class TestWorkerActivity(FixtureTestCase): ) for user, status_code, requests_count in cases: self.activity.state = WorkerActivityState.Queued - self.activity.save() + self.activity.save(update_fields=['state']) if user: self.client.force_login(user) with self.assertNumQueries(requests_count): diff --git a/arkindex/project/settings.py b/arkindex/project/settings.py index 677b5c56d6c522f8c6efe70c8774b3b84231f7ff..7e79e251b4a23183f62293041f7cd2ed852fe789 100644 --- a/arkindex/project/settings.py +++ b/arkindex/project/settings.py @@ -109,6 +109,7 @@ INSTALLED_APPS = [ 'corsheaders', 'django_rq', 'drf_spectacular', + 'pgtrigger', 'ponos', # Our apps diff --git a/requirements.txt b/requirements.txt index ff2bc33dff3078595baba9de3f6f1e81371fd5c0..072b799b5a92ccec42ac8925b836807a4423653c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ bleach==5.0.1 django-admin-hstore-widget==1.2.0 django-cors-headers==3.13.0 django-enumfields==2.1.1 +django-pgtrigger==4.6.0 django-rq==2.5.1 djangorestframework==3.12.4 drf-spectacular==0.18.2