Skip to content

Commit 8efd87b

Browse files
authored
fix: add index to row history table (baserow#4991)
1 parent c4664bb commit 8efd87b

4 files changed

Lines changed: 58 additions & 6 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Generated by Django 5.2.12 on 2026-03-17 09:16
2+
3+
from django.db import migrations, models
4+
from django.contrib.postgres.operations import AddIndexConcurrently
5+
6+
7+
class Migration(migrations.Migration):
8+
atomic = False
9+
10+
dependencies = [
11+
('database', '0205_formvieweditrowfield'),
12+
]
13+
14+
operations = [
15+
AddIndexConcurrently(
16+
model_name='rowhistory',
17+
index=models.Index(fields=['action_timestamp'], name='database_ro_action__6ea699_idx'),
18+
),
19+
]

backend/src/baserow/contrib/database/rows/history.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from itertools import groupby
33

44
from django.conf import settings
5-
from django.db import router
5+
from django.db import connection
66
from django.db.models import QuerySet
77
from django.dispatch import receiver
88

@@ -18,6 +18,7 @@
1818
from baserow.contrib.database.rows.types import ActionData
1919
from baserow.core.action.signals import action_done
2020
from baserow.core.models import Workspace
21+
from baserow.core.psycopg import sql
2122
from baserow.core.telemetry.utils import baserow_trace
2223
from baserow.core.types import AnyUser
2324

@@ -68,15 +69,33 @@ def list_row_history(
6869
return queryset
6970

7071
@classmethod
71-
def delete_entries_older_than(cls, cutoff: datetime):
72+
def delete_entries_older_than(cls, cutoff: datetime, batch_size: int = 20_000):
7273
"""
73-
Deletes all row history entries that are older than the given cutoff date.
74+
Deletes all row history entries that are older than the given cutoff date
75+
in batches to avoid long-running transactions.
7476
7577
:param cutoff: The date and time before which all entries will be deleted.
78+
:param batch_size: The number of rows to delete per batch.
7679
"""
7780

78-
delete_qs = RowHistory.objects.filter(action_timestamp__lt=cutoff)
79-
delete_qs._raw_delete(using=router.db_for_write(delete_qs.model))
81+
table = sql.Identifier(RowHistory._meta.db_table)
82+
query = sql.SQL(
83+
"""
84+
WITH to_delete AS (
85+
SELECT id FROM {table}
86+
WHERE action_timestamp < %s
87+
LIMIT %s
88+
)
89+
DELETE FROM {table}
90+
USING to_delete
91+
WHERE {table}.id = to_delete.id
92+
"""
93+
).format(table=table)
94+
while True:
95+
with connection.cursor() as cursor:
96+
cursor.execute(query, [cutoff, batch_size])
97+
if cursor.rowcount == 0:
98+
break
8099

81100

82101
@receiver(action_done)

backend/src/baserow/contrib/database/rows/models.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,9 @@ class RowHistory(models.Model):
5959

6060
class Meta:
6161
ordering = ("-action_timestamp", "-id")
62-
indexes = [models.Index(fields=["table", "row_id", "-action_timestamp", "-id"])]
62+
indexes = [
63+
# For deleting history entries by action timestamp.
64+
models.Index(fields=["action_timestamp"]),
65+
# For listing the history of a row.
66+
models.Index(fields=["table", "row_id", "-action_timestamp", "-id"]),
67+
]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "bug",
3+
"message": "Fixed slow cleanup of row history table by adding a database index.",
4+
"issue_origin": "github",
5+
"issue_number": null,
6+
"domain": "database",
7+
"bullet_points": [],
8+
"created_at": "2026-03-17"
9+
}

0 commit comments

Comments
 (0)