数据库
首页 > 数据库> > Sentry 开发者贡献指南 - 数据库迁移

Sentry 开发者贡献指南 - 数据库迁移

作者:互联网

Django 迁移是我们处理 Sentry 中数据库更改的方式。

Django 迁移官方文档:https://docs.djangoproject.com/en/2.2/topics/migrations/

这些将涵盖了解迁移正在执行的操作所需的大部分内容。

命令

请注意,对于所有这些命令,如果在 getsentry 存储库中,您可以将 getsentry 替换为 sentry

将您的数据库升级到最新

sentry upgrade 会自动更新你的迁移。您也可以运行 sentry django migrate 来直接访问迁移命令。

将您的数据库移动到特定的迁移

当您要测试迁移时,这会很有帮助。

sentry django migrate <app_name> <migration_name> - 请注意,migration_name 可以是部分匹配,通常数字就是你所需要的。

例如:sentry django migrate sentry 0005

这也可用于回滚迁移。如果你犯了错误,在开发中很有用。

为迁移生成 SQL

这对审查您的代码的人很有帮助,因为并不总是清楚 Django 迁移实际要做什么。

sentry django sqlmigrate <app_name> <migration_name>

例如 sentry django sqlmigrate sentry 0003

生成迁移

这会根据您对模型所做的更改自动为您生成迁移。

sentry django makemigrations

或者

sentry django makemigrations <app_name> 用于一个指定的 app

例如 sentry django makemigrations sentry

当您在 pr 中包含迁移时,还要为迁移生成 sql 并将其作为注释包含在内,以便您的审阅者可以更轻松地了解 Django 正在做什么。

您还可以使用 sentry django makemigrations <app_name> --empty 生成空迁移。这对于数据迁移和其他自定义工作很有用。

将迁移合并到 master

合并到 master 时,您可能会注意到与 migrations_lockfile.txt 的冲突。
这个文件是为了帮助我们避免将具有相同迁移编号的两个迁移合并到 master,如果您与它发生冲突,那么很可能有人在您之前提交了迁移。

指南

在运行迁移时,我们需要注意一些事项。

过滤器

如果(数据)迁移涉及大表或未索引的列,最好迭代整个表而不是使用 filter。 例如:

EnvironmentProject.objects.filter(environment__name="none")

因为 EnvironmentProject 行太多,这会一次将太多行带入内存。
相反,我们应该使用 RangeQuerySetWrapperWithProgressBar 遍历所有 EnvironmentProject 行,因为它会分块进行。
例如:

for env in RangeQuerySetWrapperWithProgressBar(EnvironmentProject.objects.all()):
	if env.name == 'none':
		# Do what you need

我们通常更喜欢避免将 .filterRangeQuerySetWrapperWithProgressBar 一起使用。
由于它已经通过 id 对表进行排序,因此我们无法利用字段上的任何索引,并且可能会为每个块扫描大量行。
这会运行得更慢,但我们通常更喜欢这样,因为它在更长的时间内平均负载,并使每个查询获取每个块的成本相当低。

索引

我们更喜欢使用 CREATE INDEX CONCURRENTLY 在现有的大型表上创建索引。当我们这样做时,我们无法在事务中运行迁移,因此使用 atomic = False 来运行这些很重要。

删除列/表

由于我们的部署过程,这很复杂。
当我们部署时,我们运行迁移,然后推出应用程序代码,这需要一段时间。
这意味着如果我们只是删除一个列或模型,那么 sentry 中的代码将查找这些列/表并在部署完成之前出错。
在某些情况下,这可能意味着 Sentry 在部署完成之前很难停机。

为避免这种情况,请执行以下步骤:

这是删除已经可以为空的列的示例。首先我们从模型中删除列,然后修改迁移以仅更新状态而不进行数据库操作。

operations = [
        migrations.SeparateDatabaseAndState(
            database_operations=[],
            state_operations=[
                migrations.RemoveField(model_name="alertrule", name="alert_threshold"),
                migrations.RemoveField(model_name="alertrule", name="resolve_threshold"),
                migrations.RemoveField(model_name="alertrule", name="threshold_type"),
            ],
        )
    ]

一旦部署完成,我们就可以部署实际的列删除。这个 pr 只会有一个迁移,因为 Django 不再知道这些字段。请注意,反向 SQL 仅适用于开发人员,因此可以不分配默认值或进行任何类型的回填:

operations = [
        migrations.SeparateDatabaseAndState(
            database_operations=[
                migrations.RunSQL(
                    """
                    ALTER TABLE "sentry_alertrule" DROP COLUMN "alert_threshold";
                    ALTER TABLE "sentry_alertrule" DROP COLUMN "resolve_threshold";
                    ALTER TABLE "sentry_alertrule" DROP COLUMN "threshold_type";
                    """,
                    reverse_sql="""
                    ALTER TABLE "sentry_alertrule" ADD COLUMN "alert_threshold" smallint NULL;
                    ALTER TABLE "sentry_alertrule" ADD COLUMN "resolve_threshold" int NULL;
                    ALTER TABLE "sentry_alertrule" ADD COLUMN "threshold_type" int NULL;

                    """,
                )
            ],
            state_operations=[],
        )
    ]

如果该表在其他表中被引用为外键,则需要格外小心。在这种情况下,首先删除其他表中的外键列,然后返回到此步骤。

这是删除此模型的示例:

class AlertRuleTriggerAction(Model):
    alert_rule_trigger = FlexibleForeignKey("sentry.AlertRuleTrigger")
    integration = FlexibleForeignKey("sentry.Integration", null=True)
    type = models.SmallIntegerField()
    target_type = models.SmallIntegerField()
    # Identifier used to perform the action on a given target
    target_identifier = models.TextField(null=True)
    # Human readable name to display in the UI
    target_display = models.TextField(null=True)
    date_added = models.DateTimeField(default=timezone.now)

    class Meta:
        app_label = "sentry"
        db_table = "sentry_alertruletriggeraction"

首先,我们检查了它没有被任何其他模型引用,它没有。接下来,我们需要删除和 db 级外键约束。为此,我们改变这两列并生成一个迁移:

alert_rule_trigger = FlexibleForeignKey("sentry.AlertRuleTrigger", db_constraint=False)
integration = FlexibleForeignKey("sentry.Integration", null=True, db_constraint=False)

迁移中的操作看起来像

    operations = [
        migrations.AlterField(
            model_name='alertruletriggeraction',
            name='alert_rule_trigger',
            field=sentry.db.models.fields.foreignkey.FlexibleForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, to='sentry.AlertRuleTrigger'),
        ),
        migrations.AlterField(
            model_name='alertruletriggeraction',
            name='integration',
            field=sentry.db.models.fields.foreignkey.FlexibleForeignKey(db_constraint=False, null=True, on_delete=django.db.models.deletion.CASCADE, to='sentry.Integration'),
        ),
    ]

我们可以看到它生成的 sql 只是删除了 FK 约束

BEGIN;
SET CONSTRAINTS "a875987ae7debe6be88869cb2eebcdc5" IMMEDIATE; ALTER TABLE "sentry_alertruletriggeraction" DROP CONSTRAINT "a875987ae7debe6be88869cb2eebcdc5";
SET CONSTRAINTS "sentry_integration_id_14286d876e86361c_fk_sentry_integration_id" IMMEDIATE; ALTER TABLE "sentry_alertruletriggeraction" DROP CONSTRAINT "sentry_integration_id_14286d876e86361c_fk_sentry_integration_id";
COMMIT;

所以现在我们部署它并进入下一阶段。

下一阶段涉及从代码库中删除对模型的所有引用。所以我们这样做,然后我们生成一个迁移,从迁移状态中删除模型,而不是数据库。此迁移中的操作如下所示

operations = [
        migrations.SeparateDatabaseAndState(
            state_operations=[migrations.DeleteModel(name="AlertRuleTriggerAction")],
            database_operations=[],
        )
    ]

并且生成的 SQL 显示没有发生数据库更改。所以现在我们部署它并进入最后一步。

在这最后一步中,我们只想手动编写 DDL 来删除表。 所以我们使用 sentry django makemigrations --empty 来产生一个空的迁移,然后修改操作如下:

operations = [
        migrations.RunSQL(
            """
            DROP TABLE "sentry_alertruletriggeraction";
            """,
            reverse_sql="CREATE TABLE sentry_alertruletriggeraction (fake_col int)", # We just create a fake table here so that the DROP will work if we roll back the migration.
        )
    ]

然后我们部署它,我们就完成了。

外键

创建外键大多没问题,但是对于像 ProjectGroup 这样的大/繁忙的表,由于获取锁的困难,它可能会导致问题。
您仍然可以创建 Django 级别的外键,而无需创建数据库约束。为此,请在定义键时设置 db_constraint=False

重命名表

重命名表很危险,会导致停机。发生这种情况的原因是在部署期间将运行旧/新代码的混合。
因此,一旦我们在 Postgres 中重命名该表,如果旧代码尝试访问它,它就会立即开始出错。有两种方法可以处理重命名表:

添加列

创建新列时,它们应始终创建为可为空的。这是出于两个原因:

向列添加 NOT NULL

not null 添加到列可能很危险,即使该列的表的每一行都有数据。
这是因为 Postgres 仍然需要对所有行执行非空检查,然后才能添加约束。
在小表上这可能没问题,因为检查会很快,但在大表上这可能会导致停机。
这里有几个选项可以确保安全:

添加具有默认值的列

向现有表添加具有默认值的列是危险的。这需要 Postgres 锁定表并重写它。相反,更好的选择是:

operations = [
    migrations.SeparateDatabaseAndState(
        database_operations=[
            migrations.AddField(
                model_name="mymodel",
                name="new_field",
                # Don't use a default in Postgres, a data migration can be used afterward to backfill
                field=models.PositiveSmallIntegerField(null=True),
            ),
        ],
        state_operations=[
            migrations.AddField(
                model_name="mymodel",
                name="new_field",
                # Use the default in Django, new rows will use the specified default
                field=models.PositiveSmallIntegerField(null=True, default=1),
            ),
        ],
    )
    ]

改变列类型

改变列的类型通常是危险的,因为它需要重写整个表。有一些例外:

对于任何其他类型,最好的前进路径通常是:

通常,这值得在 #discuss-backend 中讨论。

重命名列

重命名列是危险的,会导致停机。
发生这种情况的原因是在部署期间将运行旧/新代码的混合。
因此,一旦我们在 Postgres 中重命名该列,如果旧代码尝试访问它,它就会立即开始出错。有两种方法可以处理重命名列:

更多

标签:operations,sentry,name,删除,数据库,Sentry,migrations,开发者,迁移
来源: https://www.cnblogs.com/hacker-linner/p/15814157.html