Skip to content

Commit 2c079c0

Browse files
rtibblesclaude
andcommitted
feat(models): widen File.file_size to bigint, expand stage (studio#5974)
Expand stage of the zero-downtime int->bigint widening: - Add nullable file_size_bigint shadow column and its index (built CONCURRENTLY). - Mirror file_size into it via the change-guarded @mirror_field trigger. - Wire the online backfill as a commented deploy-migrate step. - Stage the cutover and contract steps as comments on the File model. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LfZvkigk8hdsKdEif3hzBi
1 parent 659cacc commit 2c079c0

3 files changed

Lines changed: 64 additions & 1 deletion

File tree

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ migrate:
3939
# 4) Remove the management command from this `deploy-migrate` recipe
4040
# 5) Repeat!
4141
deploy-migrate:
42-
echo "Nothing to do here!"
42+
# studio#5974: remove at cutover.
43+
python contentcuration/manage.py backfill_column --model contentcuration.File --source-field file_size --target-field file_size_bigint
4344

4445
contentnodegc:
4546
python contentcuration/manage.py garbage_collect
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Generated by Django 3.2.24 on 2026-06-23 05:56
2+
import pgtrigger.compiler
3+
import pgtrigger.migrations
4+
from django.db import migrations
5+
from django.db import models
6+
from django.db.models import Q
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
dependencies = [
12+
("contentcuration", "0166_add_usersubscription"),
13+
]
14+
15+
operations = [
16+
migrations.AddField(
17+
model_name="file",
18+
name="file_size_bigint",
19+
field=models.BigIntegerField(blank=True, null=True),
20+
),
21+
migrations.AddIndex(
22+
model_name="file",
23+
index=models.Index(
24+
fields=["checksum", "file_size_bigint"],
25+
name="file_checksum_fsizebig_idx",
26+
condition=Q(file_size_bigint__isnull=False),
27+
),
28+
),
29+
pgtrigger.migrations.AddTrigger(
30+
model_name="file",
31+
trigger=pgtrigger.compiler.Trigger(
32+
name="mirror_file_size_to_file_size_bigint",
33+
sql=pgtrigger.compiler.UpsertTriggerSql(
34+
func="IF NEW.file_size IS DISTINCT FROM OLD.file_size THEN NEW.file_size_bigint = NEW.file_size; END IF; RETURN NEW;",
35+
hash="051e321c4cdf91ea81f96b9f9a29e3b5015def67",
36+
operation="INSERT OR UPDATE",
37+
pgid="pgtrigger_mirror_file_size_to_file_size_bigint_54326",
38+
table="contentcuration_file",
39+
when="BEFORE",
40+
),
41+
),
42+
),
43+
]

contentcuration/contentcuration/models.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
from contentcuration.constants import feedback
8080
from contentcuration.constants import user_history
8181
from contentcuration.constants.contentnode import kind_activity_map
82+
from contentcuration.db.dual_write import mirror_field
8283
from contentcuration.db.models.expressions import Array
8384
from contentcuration.db.models.functions import ArrayRemove
8485
from contentcuration.db.models.functions import Unnest
@@ -3255,6 +3256,8 @@ class StagedFile(models.Model):
32553256

32563257

32573258
FILE_DISTINCT_INDEX_NAME = "file_checksum_file_size_idx"
3259+
# studio#5974: bigint shadow of FILE_DISTINCT_INDEX_NAME, for the file_size widening.
3260+
FILE_DISTINCT_BIGINT_INDEX_NAME = "file_checksum_fsizebig_idx"
32583261
FILE_MODIFIED_DESC_INDEX_NAME = "file_modified_desc_idx"
32593262
FILE_DURATION_CONSTRAINT = "file_media_duration_int"
32603263
MEDIA_PRESETS = [
@@ -3266,6 +3269,14 @@ class StagedFile(models.Model):
32663269
]
32673270

32683271

3272+
# studio#5974 swap (next release, after backfill completes). One migration:
3273+
# - drop the @mirror_field decorator and the file_size_bigint field below
3274+
# - file_size = models.BigIntegerField(blank=True, null=True)
3275+
# - DB ops: drop the trigger + int file_size column, then RENAME file_size_bigint -> file_size
3276+
# - wrap in SeparateDatabaseAndState so the int->bigint AlterField is state-only (no rewrite)
3277+
# Transparent to old pods: they keep writing file_size (now bigint); only a brief metadata lock.
3278+
# Do NOT add db_column to reach file_size_bigint first — that generation breaks at the rename.
3279+
@mirror_field("file_size", "file_size_bigint") # studio#5974: dual-write int->bigint
32693280
class File(models.Model):
32703281
"""
32713282
The bottom layer of the contentDB schema, defines the basic building brick for content.
@@ -3275,6 +3286,9 @@ class File(models.Model):
32753286
id = UUIDField(primary_key=True, default=uuid.uuid4)
32763287
checksum = models.CharField(max_length=400, blank=True, db_index=True)
32773288
file_size = models.IntegerField(blank=True, null=True)
3289+
file_size_bigint = models.BigIntegerField(
3290+
blank=True, null=True
3291+
) # studio#5974 shadow
32783292
file_on_disk = models.FileField(
32793293
upload_to=object_storage_name,
32803294
storage=default_storage,
@@ -3485,6 +3499,11 @@ class Meta:
34853499
models.Index(
34863500
fields=["checksum", "file_size"], name=FILE_DISTINCT_INDEX_NAME
34873501
),
3502+
models.Index(
3503+
fields=["checksum", "file_size_bigint"],
3504+
name=FILE_DISTINCT_BIGINT_INDEX_NAME,
3505+
condition=Q(file_size_bigint__isnull=False),
3506+
),
34883507
models.Index(fields=["-modified"], name=FILE_MODIFIED_DESC_INDEX_NAME),
34893508
]
34903509
constraints = [

0 commit comments

Comments
 (0)