Django PostgreSQL后端,对数据库锁应用迁移
django-pg-zero-downtime-migrations的Python项目详细描述
django pg零停机迁移
Django PostgreSQL后端,对数据库锁应用迁移。
安装
pip install django-pg-zero-downtime-migrations
< Buff行情>注意:此软件包适用于django 2.0+。
用法
要为Postgres启用零停机迁移,只需设置此软件包提供的django后端并添加最安全的设置:
DATABASES = {
'default': {
'ENGINE': 'django_zero_downtime_migrations.backends.postgres',
#'ENGINE': 'django_zero_downtime_migrations.backends.postgis',
...
}
}
ZERO_DOWNTIME_MIGRATIONS_LOCK_TIMEOUT = '2s'
ZERO_DOWNTIME_MIGRATIONS_STATEMENT_TIMEOUT = '2s'
ZERO_DOWNTIME_MIGRATIONS_FLEXIBLE_STATEMENT_TIMEOUT = True
ZERO_DOWNTIME_MIGRATIONS_RAISE_FOR_UNSAFE = True
ZERO_DOWNTIME_MIGRATIONS_USE_NOT_NULL = False
< Buff行情>注意:此后端只为迁移(schema和runsql
操作,而不是runpython
操作)带来了零停机时间改进,其他方面它与标准django后端工作相同。
注意:此软件包是beta版的,请在应用于生产之前检查迁移SQL,并提交任何问题。
与标准django后端的区别
此后端提供相同的结果状态(而不是not null
constraint replacement),但提供了不同的方式和额外的保证,以避免表被锁定。
此后端不使用事务进行迁移(除了runpython
操作),因为并非所有固定sql都可以在事务中运行,而且它允许避免复杂迁移的死锁。因此,当您的迁移在事务处理过程中停止时,您需要手动修复它(而不是潜在的停机时间)。
部署流程
零停机部署有一个主要规则:
- 我们有一个数据库;
- 我们有几个应用程序实例-应用程序应该始终可用,即使您重新启动其中一个实例;
- 我们在实例之前有平衡器;
- 我们的应用程序在迁移前、迁移时和迁移后运行良好-旧应用程序在新旧数据库架构版本下运行良好;
- 我们的应用程序在实例更新之前、之后和之后都可以正常工作-旧的和新的应用程序版本可以与新的数据库架构版本一起正常工作。 < > >
- 应用迁移
- 断开实例窗体Balancer的连接,重新启动它并返回到Balancer-对所有实例逐个重复此操作 < > >
无
-当前使用的Postgres设置- 其他-将应用超时,
0
和等效值表示将禁用超时 无
-当前使用的Postgres设置- 其他-将应用超时,
0
和等效值表示将禁用超时 无
-标准Django的行为(针对零停机时间的提升或针对不安全的迁移的提升=真的
)true
-始终用check(字段不为空)替换
not null
约束(对于zero\u宕机,不提升;对于不安全的,不提升)
)false
-始终使用not null
约束(对于zero_down_migration_raise_unsafe=true,不要引发)
int
值-如果表的值大于值,则使用
检查(字段不为空)
而不是不为空
zero_down_migration_raise_for_unsafe=true
)- 迁移操作在一个线程中同步工作并覆盖架构迁移(数据迁移与业务逻辑操作冲突,与业务逻辑冲突同时发生)。
- 业务逻辑操作同时工作。
- 操作时间-花在架构更改上的时间,因此对许多行表(如
create index
或alter table add column set default
)进行长时间运行操作时会出现问题,因此您需要使用更多的存储等效项。 - 等待时间-您的迁移将等待所有事务完成,因此对于像Analytic这样长时间运行的操作/事务存在问题,因此您需要避免它或禁用迁移时间。
- 每秒查询数+执行时间和连接池-如果要表的查询太多,而此查询花费的时间太长,则此查询只能将所有可用的连接带到数据库,直到等待释放锁为止,因此看起来您需要在那里进行不同的优化:在加载最少,减少查询计数和执行时间,分割数据。
- 一个事务中的操作太多-对于一个操作,前面的所有点都有问题,因此如果一个事务中有多个操作,则有更多的机会遇到此问题,因此,您应该避免在一个事务中执行许多操作(或者事件根本不在事务中运行它,但当某些操作失败时,您应该更加小心)。 < > >
- 创建新表/列、复制现有数据、删除旧表/列
- 停机时间 < > >
- 在负载最小时运行迁移,以避免锁定的负面影响。
设置语句超时
并尝试为小表设置不为空
约束。- 使用
check(column is not null)
约束,而不是使用nextvalidate constraint的支持
无效的选项,有关详细信息,请参阅文章https://medium.com/doctorib engineering/adding-a-not-null-constraint-on-pg-faster-with-minimal-locking-38b2c00c4d1c
< > > varchar(较少)
到varchar(较多)
其中,较少<;较多varchar(任意)
到文本
数值(更少,相同)
到数值(更多,相同)
其中less<;more and same==相同 < > >- 提取零停机模式到mixin,以允许将此逻辑与其他后端一起使用
- 将模块从
django_zero_down_migrations_postgres_backend
移动到django_zero_down_migrations.backends.postgres
- 标记
django_zero_dowtime_migrations_postgres_backend
模块为已弃用 - 添加Postgis后端支持
- 自述文件改进
- 将
Zero_Downtime_Migrations_Lock_Timeout
和Zero_Downtime_Migrations_Statement_Timeout
的默认值从0ms
更改为None
以获得与默认Postgres超时相同的默认Django行为 - 使用默认选项向文档添加更新
- 使用最佳选项为文档添加更新
- 修复了在默认情况下添加可为空字段没有错误和警告问题
- 添加指向文档的链接,其中包含问题描述和安全的替代方案错误和警告的用法
- 使用类型转换解决方案为文档添加更新
- 使用
meta.index
和meta.constraints
属性添加django 2.2支持 - 修复regexp的python弃用警告
- 删除未使用的TimeoutException
- 改进自述文件和pyPI说明
- 当语句超时设置为全局时,添加允许对长操作(如在约束验证时创建索引)禁用
语句超时的选项
- 添加详细说明内容类型
- 首次发布:
- 将默认SQL查询替换为更安全的查询
- 添加
语句超时和
锁定超时的选项
- 添加
不为空的选项
约束行为- 添加不安全操作限制选项
- 添加
流量:
如果我们的部署不满足零停机部署规则,则将其拆分为较小的部署。
附加设置
零停机时间迁移锁定超时
对于需要access exclusive
lock的sql语句,应用statement-timeout
,默认为none
:
ZERO_DOWNTIME_MIGRATIONS_LOCK_TIMEOUT = '2s'
允许值:
零停机时间迁移语句超时
对于需要access exclusive
lock、默认none
:
ZERO_DOWNTIME_MIGRATIONS_STATEMENT_TIMEOUT = '2s'
允许值:
零停机时间迁移灵活语句超时
将SQL语句的语句超时设置为
0ms
需要share update exclusive
锁定,在全局启用语句超时
并尝试运行索引创建或约束验证等长期运行操作时非常有用,默认值为false
:
ZERO_DOWNTIME_MIGRATIONS_FLEXIBLE_STATEMENT_TIMEOUT = True
零停机时间迁移提高不安全性
启用选项不允许运行潜在的不安全迁移,默认值false
:
ZERO_DOWNTIME_MIGRATIONS_RAISE_FOR_UNSAFE = True
零停机时间迁移使用非空
设置避免不为空的策略
约束创建长锁,默认为无
:
ZERO_DOWNTIME_MIGRATIONS_USE_NOT_NULL = 10 ** 7
允许值:
处理部分索引
< Buff行情>注意:django 2.2支持本机部分索引机制:https://docs.djangoproject.com/en/2.2/ref/models/indexes/condition和
如果您在postgres中使用了部分索引包https://github.com/mattiaslinnap/django-partial-index" rel="nofollow">https://github.com/mattiaslinnap/django-partial-index,那么您可以很容易地使这个包对于迁移也是安全的:
from django_zero_downtime_migrations_postgres_backend.schema import PGShareUpdateExclusive
from partial_index import PartialIndex
PartialIndex.sql_create_index['postgresql'] = PGShareUpdateExclusive(
'CREATE%(unique)s INDEX CONCURRENTLY %(name)s ON %(table)s%(using)s (%(columns)s)%(extra)s WHERE %(where)s',
disable_statement_timeout=True
)
工作原理
Postgres表级锁
postgres在表级别上有不同的锁,这些锁可能相互冲突https://www.postgresql.org/docs/current/static/explicit locking.html locking-tables:
<表><广告>访问共享
行共享
排他行
独占共享更新
共享
独占共享行
独占
独占访问
访问共享
行共享
排他行
独家共享更新
共享
共享行独占
独家
独家访问
迁移和业务逻辑锁
让我们将此锁拆分为迁移和业务逻辑操作。
迁移锁
<表><广告>独家访问
创建序列
,删除序列
,创建表
,删除表
*,更改表
**,删除索引
共享
创建索引
独家共享更新
并发创建索引
,并发删除索引
***,更改表验证约束
****:创建序列
,删除序列
,创建表
,删除表
不应该有冲突,因为逻辑不应该使用它操作
**:不是所有的alter table
操作都采用access exclusive
lock,但目前所有的django迁移都是通过https://github.com/django/django/blob/master/django/db/backends/base/schema.py" rel="nofollow">https://github.com/django/django/blob/master/django/db/backends/base/schema.py,https://github.com/django/django/blob/master/django/db/backends/postgresql/schema.py和https://www.postgresql.org/docs/current/static/sql altertable.html
***:django当前不支持并发操作
操作
***:django没有validate constraint
逻辑,但我们将在某些情况下使用它
业务逻辑锁
<表><广告>锁定
操作
与锁冲突
与操作冲突
< /广告><正文>访问共享
选择
独家访问
更改表
,删除索引
行共享
选择进行更新
访问独占
,独占
更改表
,删除索引
排他行
插入
,更新
,删除
访问独占
,独占
,共享行独占
,共享
更改表
,删除索引
,创建索引
访问共享
选择
独家访问
更改表
,删除索引
行共享
选择进行更新
访问独占
,独占
更改表
,删除索引
排他行
插入
,更新
,删除
访问独占
,独占
,共享行独占
,共享
更改表
,删除索引
,创建索引
因此,您可以发现exist表的所有django模式更改都与业务逻辑冲突,但幸运的是,它们是安全的,或者在gen中有安全的替代方案。艾莱依.
Postgres行级锁
由于业务逻辑主要与表行一起工作,因此理解行级的锁冲突也很重要https://www.postgresql.org/docs/current/static/explicit locking.html;锁定行:
<表><广告>用于密钥共享
共享
不更新密钥
更新
用于密钥共享
共享
表示无钥匙更新
用于更新
要点是,如果有两个事务更新一行,那么第二个事务将等到第一个事务完成。因此,对于业务逻辑和数据迁移,最好避免对整个表进行更新,而是使用批处理操作。
< Buff行情>注意:批处理操作也可以更快地工作,因为它有助于Postgres制定更优化的执行计划。
事务FIFO等待
在一篇有趣的文章中找到了相同的图表http://pankrat.github.io/2015/django-migrations-without-downtimes/" rel="nofollow">http://pankrat.github.io/2015/django migrations without downtimes/
在这个图表中,我们可以提取几个指标:
处理超时
Postgres有两个处理图中显示的等待时间和操作时间的设置:锁定超时
和语句超时
将锁定超时设置为"2s"
允许您在运行迁移之前长时间运行查询/事务时避免停机(https://www.postgresql.org/docs/current/static/runtime config client.html guc-lock-timeout)。
将statement\u timeout设置为"2s"
允许您在长时间运行迁移查询时避免停机(https://www.postgresql.org/docs/current/static/runtime config client.html;guc-statement-timeout)。
死锁
死锁不存在停机问题,但是一个事务中的太多操作将获取最具冲突性的锁,并仅在事务提交或回滚后释放它。因此,在一个事务中避免access exclusive
锁定操作和长时间操作是一个好主意。当不同的表将被锁定时,死锁也会使您在生产部署时无法进行迁移,例如,对于使用access exclusive
锁定两个表的外键。
存储的行和值
Postgres以不同的方式存储不同类型的值https://www.postgresql.org/docs/current/static/storage toast.html storage-toast-ondisk。当您试图将一种类型转换为另一种类型,并且它以不同的方式存储时,postgres将重写所有值。幸运的是,某些类型以相同的方式存储,Postgres不需要做任何事情来更改类型,但在某些情况下,Postgres需要检查所有值是否具有相同的新类型限制。
多版本并发控制
关于文档https://www.postgresql.org/docs/current/static/mvcc intro.html使用多版本模型维护Postgres中的数据一致性。这意味着每个sql语句都能看到数据的快照。它的优点是添加和删除不带任何索引、约束和默认值的列不会更改现有数据,新版本的数据将在insert
和update
上创建,删除只是将您的记录标记为过期。所有垃圾稍后将通过vacuum
或auto vacuum
收集。
Django Migrations破解
任何模式更改都可以通过创建新表并将数据复制到表中来处理,因此只要将没有停机时间的安全方法的不安全操作标记为no
创建序列
放置顺序
创建表
放置表格
将表重命名更改为
更改表集表空间
更改表添加列
设置为非空
,设置默认值
,主键
,唯一
*更改表添加列设置默认值
alter table add column
然后填充column,然后设置默认值
*alter table add column set不为空
alter table add column
,然后填充column,再设置alter table alter column set not null
*和**alter table add column主键
添加索引并添加约束 不安全操作,因为您要花时间迁移到创建索引
,所以建议使用alter table add column
然后并发创建索引然后使用索引alter table add constraint primary key
alter table add column unique
创建索引
,所以建议使用alter table add column
然后并发创建索引,然后使用索引alter table add constraint unique
alter table alter column type
alter table alter column set不为空
列中的所有项是否不为空
**更改表alter column drop不为空
更改表格更改列设置默认值
更改表alter column drop default
alter table drop column
alter table alter column drop not null
,alter table drop constraint
和drop index
before*和*****更改表重命名列
alter table create column
然后将所有数据复制到新列*更改表添加约束检查
更改表格删除约束
(检查
)alter table add constraint外键
添加为无效并验证 不安全操作,因为您要花时间在迁移中检查约束,请锁定两个表
alter table drop constraint
(外键
)alter table add constraint主键
添加索引并添加约束 不安全的操作,因为您要花时间在迁移中创建索引***
alter table drop constraint
(主键
)alter table add constraint unique
alter table drop constraint
(unique
)创建索引
同时创建索引
删除索引
同时删除索引
*:在没有停机的情况下进行生产迁移的要点是,您的代码应该在迁移前后正常工作,让我们仔细看一下下面这一点
**:Postgres将检查列中所有需要时间的项是否为空,让我们仔细查看下面的这一点
***:当您跳过alter table add constraint unique using index
并且仍然不清楚与concurrent的区别时,postgres将有相同的行为,除了锁的区别,让我们仔细看下面这一点
***:让我们仔细看下面这一点
*****:如果使用python manage.py makemigrations--check
检查ci上的迁移,则在不创建迁移的情况下不能删除代码中的列,因此在这种情况下,可以使用后迁移流:在所有实例上应用代码,然后迁移数据库
处理迁移前后应该工作的逻辑
新的和正在删除的型号和列
迁移:创建序列
,删除序列
,创建表
,删除表
,更改表添加列
,更改表删除列
这种迁移非常安全,因为在迁移之前,您的逻辑无法处理这些数据
工作逻辑的变化
迁移:将表重命名更改为
,更改表集表空间
,更改表重命名列
对于这种迁移,实现逻辑太难了,所有实例都能正常工作,因此有两种方法来处理它:
使用默认值创建列
迁移:alter table add column set default
使用默认值创建列的标准django行为是使用默认值填充所有值。django不会永久使用数据库默认值,因此当您添加具有默认值的新列时,django将创建具有默认值的列并立即删除此默认值,例如,新的默认值将来自django代码。在这种情况下,并非所有实例应用的迁移都已更新,此时表中的新行将没有默认值,可能需要在此之后更新可为空的值。因此,要避免这种情况,最好的方法是避免使用默认的创建列和拆分列创建(默认为新行)以及将数据填充到两个迁移(使用部署)。
处理非空
约束
postgres在应用非空
约束时检查所有列项非空
,很遗憾,不能将此检查推迟到无效
时。但我们有一些技巧和选择。
处理唯一性
约束
Postgres有两种唯一性方法:创建唯一索引
和更改表添加约束唯一
-都在内部使用唯一索引。区别在于,我发现不能同时对约束应用删除索引
。但是仍然不清楚除了锁中的差异之外,删除索引
和同时删除索引
有什么区别,但是正如您之前看到的,两者都标记为安全-您不会花时间在删除索引
中,只是等待锁定。因此,当django使用约束来获得唯一性时,我们也有安全使用约束的技巧。
处理alter table alter column type
接下来的操作是安全的:
对于其他操作,建议创建新列并将数据复制到其中。有些类型也可以是安全的,但你应该自己检查。
django pg零停机迁移更改日志
0.5
0.4
0.3
0.2
0.1.1
0.1
推荐PyPI第三方库
接下来的操作是安全的:
对于其他操作,建议创建新列并将数据复制到其中。有些类型也可以是安全的,但你应该自己检查。