背景迁移
后台迁移可用于执行数据迁移,否则需要很长时间(数小时、数天、数年等)才能完成。例如,您可以使用后台迁移来迁移数据,这样数据就不会存储在单个JSON列中,而是存储在单独的表中。
何时使用后台迁移
注意:添加后台迁移时你必须确保在每月发布的帖子中公布它们,并估计完成迁移需要多长时间。
在绝大多数情况下,您会希望使用常规的Rails迁移。后台迁移应该只有在迁移时使用数据在有这么多行的表中,如果采用常规的Rails迁移,这个过程将花费数小时。
背景迁移可能不当它们被用来执行模式迁移时,它们应该只用于数据迁移。
一些后台迁移有用的例子:
- 将事件从一个表迁移到多个独立的表。
- 根据存储在另一列中的JSON填充一列。
- 迁移依赖于外部服务(例如API)输出的数据。
隔离
后台迁移必须是隔离的,并且不能使用应用程序代码(例如,定义在app /模型
)。由于这些迁移可能需要很长时间才能运行,因此可以在新版本仍在运行时部署它们。
同时执行不同的迁移也是可能的。这意味着不同的后台迁移不应该以可能导致冲突的方式迁移数据。
幂等性
后台迁移是在Sidekiq进程的上下文中执行的。通常的Sidekiq规则适用,特别是工作应该是小的和幂等的规则。
看到Sidekiq最佳实践指南了解更多详情。
确保万一要重新尝试迁移作业时,数据完整性得到了保证。
它是如何工作的
后台迁移是简单的类,它定义了执行
方法。然后,Sidekiq工作线程将执行这样的类,并将任何参数传递给它。所有迁移类都必须在名称空间中定义Gitlab: BackgroundMigration
,文件应放在该目录下lib / gitlab / background_migration /
.
调度
可以在常规迁移或部署后迁移中安排迁移。要做到这一点,只需使用以下代码,同时用迁移所需的任何值替换类名和参数:
BackgroundMigrationWorker.perform_async(“BackgroundMigrationClassName”,[__arg1,最长,…])
通常情况下,最好将作业批量排队,为此您可以使用BackgroundMigrationWorker.bulk_perform_async
:
BackgroundMigrationWorker.bulk_perform_async([[“BackgroundMigrationClassName”,[1]],[“BackgroundMigrationClassName”,[2]]])
您还需要确保新创建的数据被迁移,或者在创建时同时保存在新旧版本中。对于复杂且耗时的迁移,最好使用实例调度后台作业after_create
钩子,这样就不会影响响应时间。这同样适用于更新。通过简单地定义带有级联删除的外键来处理删除。
如果您希望批量调度作业并设置延迟,则可以使用BackgroundMigrationWorker.bulk_perform_in
:
工作=[[“BackgroundMigrationClassName”,[1]],[“BackgroundMigrationClassName”,[2]]]BackgroundMigrationWorker.bulk_perform_in(5.分钟,工作)
重新调度后台迁移
如果其中一个后台迁移包含在补丁版本中修复的错误,则需要重新安排后台迁移,以便在已经执行了初始迁移的系统上重复迁移。
当您重新安排后台迁移时,请确保将原始调度转换为no-op#起来
和#下来
执行调度的迁移方法。否则,在一次升级多个补丁版本的系统上,后台迁移将被安排多次。
清理
注意:清理任何剩余的后台迁移必须在主要或次要版本中完成不得请在补丁版本中执行此操作。
因为后台迁移可能需要很长时间,所以您无法在调度后立即清理它们。例如,您不能删除迁移过程中使用的列,因为这会导致作业失败。这意味着您需要添加一个单独的部署后在将来的版本中迁移,在清理之前完成所有剩余的作业(例如删除一个列)。
例如,假设您想从列中迁移数据喷火
(包含一个大的JSON blob)列酒吧
(包含字符串)。这个过程大致如下:
- 发布:
- 创建一个迁移类,对具有给定ID的行执行迁移。
- 部署此版本的代码,这应该包括一些将为新创建的数据调度作业的代码(例如使用
after_create
钩)。 - 为部署后迁移中的所有现有行安排作业。有些新创建的行可能会被调度两次,因此您的迁移应该考虑到这一点。
- 版本B:
- 部署代码,以便应用程序开始使用新列,并停止为新创建的数据调度作业。
- 在部署后迁移中,您需要确保没有作业保留。
- 使用
Gitlab: BackgroundMigration.steal
来处理塞德基克的剩余工作 - 重新安排迁移,以便在Sidekiq未迁移的任何行上直接运行(即不通过Sidekiq)。例如,如果Sidekiq接收到SIGKILL,或者如果某个批处理失败的次数足够多而被标记为死亡,则可能发生这种情况。
- 使用
- 删除旧列。
这也可能需要一个碰撞到导入/导出版本,如果从以前版本的GitLab导入项目需要使用新格式的数据。
例子
为了解释这一切,让我们使用以下示例:表服务
有一个字段叫做属性
存储在JSON中。对于要提取的所有行url
键,并将其存储在services.url
列。有数百万个服务,解析JSON很慢,因此不能在常规迁移中这样做。
为了使用后台迁移,我们将从定义迁移类开始:
类Gitlab::BackgroundMigration::ExtractServicesUrl类服务<ActiveRecord::基地自我.table_name=“服务”结束def执行(service_id)在调度和开始作业之间可能会删除一行,因此我们在做任何工作之前,需要确保数据仍然存在。服务=服务.选择(:属性)。find_by(id:service_id)返回除非服务开始json=JSON.负载(服务.属性)救援JSON::ParserError如果JSON是无效的,我们不想永远保留这个任务。相反,我们将保留“url”字段的默认值#。返回结束服务.更新(url:json[“url”])如果json[“url”]结束结束
接下来,我们需要调整代码,以便为新创建和更新的服务安排上述迁移。我们可以这样做:
类服务<ActiveRecord::基地after_commit: schedule_service_migration,::更新after_commit: schedule_service_migration,::创建defschedule_service_migrationBackgroundMigrationWorker.perform_async(“ExtractServicesUrl”,[id])结束结束
我们使用after_commit
确保在事务完成之前没有调度Sidekiq作业,因为这样做可能导致竞态条件,在这种情况下,工作线程还无法看到更改。
接下来,我们将需要部署后迁移,它将安排现有数据的迁移。由于我们要处理很多行,我们将分批调度作业,而不是一个接一个地调度作业:
类ScheduleExtractServicesUrl<ActiveRecord::迁移disable_ddl_transaction !类服务<ActiveRecord::基地自我.table_name=“服务”结束def向上服务.选择(: id)。in_batches做|关系|工作=关系.摘下(: id)。地图做|id|[“ExtractServicesUrl”,[id]]结束BackgroundMigrationWorker.bulk_perform_async(工作)结束结束def下来结束结束
一旦部署完成,我们的应用程序将像以前一样继续使用数据,但同时将确保迁移了现有数据和新数据。
在下一个版本中,我们可以删除after_commit
钩子和相关代码。我们还需要添加部署后迁移,该迁移消耗所有剩余的作业,并在所有未迁移的行上手动运行。这样的迁移看起来像这样:
类ConsumeRemainingExtractServicesUrlJobs<ActiveRecord::迁移disable_ddl_transaction !类服务<ActiveRecord::基地包括::EachBatch自我.table_name=“服务”结束def向上#这个必须包含在内Gitlab::BackgroundMigration.偷(“ExtractServicesUrl”)#这应该包括在内,但可以跳过-见下文服务.在哪里(url:零)。each_batch(:50)做|批处理|范围=批处理.摘下(“MIN (id)”,“马克斯(id)”)。第一个Gitlab::BackgroundMigration::ExtractServicesUrl.新.执行(*范围)结束结束def下来结束结束
在处理完所有作业之后,对所有未迁移的行执行最后一步。这是在运行后台迁移的Sidekiq进程收到SIGKILL导致作业丢失的情况下发生的。(见更可靠的Sidekiq队列了解更多信息。)
如果应用程序不依赖于100%迁移的数据(例如,数据是建议数据,而不是关键任务数据),那么可以跳过最后一步。
然后,此迁移将处理ExtractServicesUrl迁移的所有作业,并在处理完所有作业后继续。完成后,可以安全地取出services.properties
列。
测试
需要为后台迁移的调度迁移(常规迁移或部署后迁移)、后台迁移本身和清理迁移编写测试。你可以使用:迁移
在测试常规/部署后迁移时使用RSpec标签。看到自述.
当你这样做的时候,记住这一点之前
和后
RSpec钩子将上下迁移数据库,这可能导致调用其他后台迁移。这意味着使用间谍
测试加倍have_received
,而不是使用常规的测试double,因为您的期望定义在它
块可能与RSpec钩子中被调用的内容冲突。看到gitlab-org gitlab-ce # 35351了解更多详情。
最佳实践
- 确保后台迁移工作是幂等的。
- 确保您编写的测试没有误报。
- 确保如果正在迁移的数据非常重要且不能丢失,那么清理迁移也会在完成之前检查数据的最终状态。