mirror of
https://github.com/zulip/zulip.git
synced 2025-11-03 13:33:24 +00:00
retention: Add flag to ArchiveTransaction to prevent automatic deletion.
This adds an index non-concurrently, but the table should be small enough for this to be fine.
This commit is contained in:
committed by
Tim Abbott
parent
bec0326776
commit
a352d35660
@@ -680,12 +680,20 @@ def clean_archived_data() -> None:
|
|||||||
# Associated archived objects will get deleted through the on_delete=CASCADE property:
|
# Associated archived objects will get deleted through the on_delete=CASCADE property:
|
||||||
count = 0
|
count = 0
|
||||||
transaction_ids = list(
|
transaction_ids = list(
|
||||||
ArchiveTransaction.objects.filter(timestamp__lt=check_date).values_list("id", flat=True)
|
ArchiveTransaction.objects.filter(
|
||||||
|
timestamp__lt=check_date, protect_from_deletion=False
|
||||||
|
).values_list("id", flat=True)
|
||||||
)
|
)
|
||||||
while len(transaction_ids) > 0:
|
while len(transaction_ids) > 0:
|
||||||
transaction_block = transaction_ids[0:TRANSACTION_DELETION_BATCH_SIZE]
|
transaction_block = transaction_ids[0:TRANSACTION_DELETION_BATCH_SIZE]
|
||||||
transaction_ids = transaction_ids[TRANSACTION_DELETION_BATCH_SIZE:]
|
transaction_ids = transaction_ids[TRANSACTION_DELETION_BATCH_SIZE:]
|
||||||
ArchiveTransaction.objects.filter(id__in=transaction_block).delete()
|
|
||||||
|
ArchiveTransaction.objects.filter(
|
||||||
|
# The protect_from_deletion=False condition is redundant at this point, but can act
|
||||||
|
# as an extra safeguard against future bugs.
|
||||||
|
id__in=transaction_block,
|
||||||
|
protect_from_deletion=False,
|
||||||
|
).delete()
|
||||||
count += len(transaction_block)
|
count += len(transaction_block)
|
||||||
|
|
||||||
logger.info("Deleted %s old ArchiveTransactions.", count)
|
logger.info("Deleted %s old ArchiveTransactions.", count)
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 5.0.10 on 2025-01-28 06:10
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("zerver", "0660_add_imageattachment_content_type"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="archivetransaction",
|
||||||
|
name="protect_from_deletion",
|
||||||
|
field=models.BooleanField(db_index=True, default=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -112,6 +112,10 @@ class ArchiveTransaction(models.Model):
|
|||||||
restored = models.BooleanField(default=False, db_index=True)
|
restored = models.BooleanField(default=False, db_index=True)
|
||||||
restored_timestamp = models.DateTimeField(null=True, db_index=True)
|
restored_timestamp = models.DateTimeField(null=True, db_index=True)
|
||||||
|
|
||||||
|
# ArchiveTransaction objects are regularly deleted. This flag allows tagging
|
||||||
|
# an ArchiveTransaction as protected from such automated deletion.
|
||||||
|
protect_from_deletion = models.BooleanField(default=False, db_index=True)
|
||||||
|
|
||||||
type = models.PositiveSmallIntegerField(db_index=True)
|
type = models.PositiveSmallIntegerField(db_index=True)
|
||||||
# Valid types:
|
# Valid types:
|
||||||
RETENTION_POLICY_BASED = 1 # Archiving was executed due to automated retention policies
|
RETENTION_POLICY_BASED = 1 # Archiving was executed due to automated retention policies
|
||||||
|
|||||||
@@ -952,24 +952,30 @@ class TestCleaningArchive(ArchiveMessagesTestingBase):
|
|||||||
self._make_expired_zulip_messages(7)
|
self._make_expired_zulip_messages(7)
|
||||||
archive_messages(chunk_size=2) # Small chunk size to have multiple transactions
|
archive_messages(chunk_size=2) # Small chunk size to have multiple transactions
|
||||||
|
|
||||||
transactions = list(ArchiveTransaction.objects.all())
|
transactions = list(ArchiveTransaction.objects.all().order_by("id"))
|
||||||
for transaction in transactions[0:-1]:
|
for transaction in transactions[0:-1]:
|
||||||
transaction.timestamp = timezone_now() - timedelta(
|
transaction.timestamp = timezone_now() - timedelta(
|
||||||
days=settings.ARCHIVED_DATA_VACUUMING_DELAY_DAYS + 1
|
days=settings.ARCHIVED_DATA_VACUUMING_DELAY_DAYS + 1
|
||||||
)
|
)
|
||||||
transaction.save()
|
transaction.save()
|
||||||
|
|
||||||
|
# This transaction would up for deletion, but we enable the flag preventing
|
||||||
|
# it from automatic deletion:
|
||||||
|
transactions[-2].protect_from_deletion = True
|
||||||
|
transactions[-2].save()
|
||||||
|
|
||||||
message_ids_to_clean = list(
|
message_ids_to_clean = list(
|
||||||
ArchivedMessage.objects.filter(archive_transaction__in=transactions[0:-1]).values_list(
|
ArchivedMessage.objects.filter(archive_transaction__in=transactions[0:-2]).values_list(
|
||||||
"id", flat=True
|
"id", flat=True
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
clean_archived_data()
|
clean_archived_data()
|
||||||
remaining_transactions = list(ArchiveTransaction.objects.all())
|
remaining_transactions = list(ArchiveTransaction.objects.order_by("-id"))
|
||||||
self.assert_length(remaining_transactions, 1)
|
self.assert_length(remaining_transactions, 2)
|
||||||
# All transactions except the last one were deleted:
|
# All transactions except the last two were deleted:
|
||||||
self.assertEqual(remaining_transactions[0].id, transactions[-1].id)
|
self.assertEqual(remaining_transactions[0].id, transactions[-1].id)
|
||||||
|
self.assertEqual(remaining_transactions[1].id, transactions[-2].id)
|
||||||
# And corresponding ArchivedMessages should have been deleted:
|
# And corresponding ArchivedMessages should have been deleted:
|
||||||
self.assertFalse(ArchivedMessage.objects.filter(id__in=message_ids_to_clean).exists())
|
self.assertFalse(ArchivedMessage.objects.filter(id__in=message_ids_to_clean).exists())
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
@@ -977,7 +983,10 @@ class TestCleaningArchive(ArchiveMessagesTestingBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
for message in ArchivedMessage.objects.all():
|
for message in ArchivedMessage.objects.all():
|
||||||
self.assertEqual(message.archive_transaction_id, remaining_transactions[0].id)
|
self.assertIn(
|
||||||
|
message.archive_transaction_id,
|
||||||
|
[remaining_transactions[0].id, remaining_transactions[1].id],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestGetRealmAndStreamsForArchiving(ZulipTestCase):
|
class TestGetRealmAndStreamsForArchiving(ZulipTestCase):
|
||||||
|
|||||||
Reference in New Issue
Block a user