实现生产级的Migrate操作
前言
在涉及到关系型数据库开发时、我们每次在线上操作数据库都需要找一个叫DBA的角色,时光荏苒,就这样过了一年又一年。一群不堪压迫,集颜值、智商、动手能力于一身的程序员们发现,明明我们手里有操纵DB的能力,为什么要把命运交给别人主宰呢?
这群程序员做了一个伟大的决定!
让数据库的数据结构也能够通过版本追踪,这种操作,就被称为“Database Migrate”。本文将以Flask-Migrate作为基础蓝本,讲解如何实现生产级质量的Migrate。
其实Migrate实现并不复杂,只要我们:
· 支持追踪每一次的数据库结构变更【构建commit】
· 新开发者参与到项目中可以自动的应用变更到最新版本【生效commit】
· 回滚更新【revert】
一个基础完备且健壮的Migrate就算实现了。常见的Migrate实现都是直接使用当前数据库来进行构建的。Flask-Migrate也不例外。
在Flask-Migrate中,构建commit是通过文件的方式生成的。以一个简单的项目为例:
而生效的commit,存储在DB的alembic_version中:
在本例中,当前的migrate有两个,但是只生效了一个。当我们再次执行Migrate操作时,系统会对比当前目录和数据库中的记录,根据依赖关系顺序生效还未生效的commit。
近十年来,随着MVC、敏捷开发、DevOps等等设计、开发模式从出现、探讨到成熟。Migrate已经是一种不争的维护关系型数据库表结构的手段之一。
然而很不幸的是,虽然Flask有比较稳定的flask-migrate来支持这一功能。但是官方文档给出的做法确差强人意。
我们一直认为Flask生态最大的优势是极其简单、学习迅速,但是用好Flask,做到生产级可用,真的不简单。Flask不像Django用约束换取自由,Flask本身可以认为是完全自由的。
不禁想起了社区的金句:Pirates use Flask, the navy uses Django.
但是因为Flask自身的Micro风格,对等的带来一个巨大的优势,可以将Flask代码作为一个项目的内嵌项目来实现,特别是对于一些老旧项目的重构改造等,Flask有着难以比拟的整合性。从Migrate角度讲,Flask-Migrate拥有更广泛的使用场景。
生产级的Migrate
我们认为一个生产级的Migrate的指标有:
基础指标
· 正确识别表结构变动并支持Migrate
· 支持回滚操作(可以是有条件限制的)
· 支持多个模块的Migrate
进阶指标
· 自动触发Migrate操作
· 新项目可以只复用表结构而无需复用Migrations
很不幸的是,flask-migrate除了基础指标的1和2之外,均不能达成……所以,是时候从入门到放弃了吗?
当然不!要告诉你的是:这世上不存在开源且完全符合自身需求的程序。如果有,在编程这条路上,你离被淘汰不远了。
解决方案
Step1.实现跨模块
分析
· Flask框架风格追求Micro。所以每个App风格都是尽可能少的引用,于是每个App中的Model只会包含数据库中的一部分而不是全部。
· 其实和Migrate的思路产生了冲突。因为Migrate的目标是掌控整个数据库。如果我们有办法让Migrate识别出所有的Model,那问题不是就解决了?
核心代码
完整版代码传送门:https://github.com/wangwenpei/fantasy/blob/master/fantasy/cli.py#L71
Step2.自动触发Migrate
分析
· 如果你对Migrate的概念比较熟悉。你就会意识到,Migrate操作始终是在当前版本代码载入生效之前执行的。
· 常见的思路是,我们通常会把这种操作构建到持续部署(continuous deployment)系统中。而基于Flask自由的灵魂,我们还可以构造出更加灵活简单的方式,只要我们保证在代码生效前执行就可以了。
核心代码
完整版代码传送门:https://github.com/wangwenpei/fantasy/blob/master/fantasy/init.py#L63
Step3.多项目无冲突复用表结构
使用场景
· 随着公司和项目的成长,举例:人事的变动,已有的项目A负责人开始接手新项目B。老项目A和项目B之间是完全独立的。而有一部分数据库表结构,希望只共用结构,而不共用数据。比如:消息发送记录表(table1)。
· 一个粗暴的办法,我们当然可以直接施展复制粘贴大法,从此分道扬镳。在风爻看来,这种策略实在低级,牺牲了太多特性。
好的策略
我们认为一个好的策略是这样的:
· 一方负责维护,其他方仅引用使用
这样一旦有商议通过的更新,所有人都会同步更新,项目组之间无需独立维护。
· 新项目只依赖需要的数据结构,创建全新的migrations文件
项目A的依赖关系没有任何变动,项目B的依赖关系为全新,互不影响。
完整版Demo传送门:https://github.com/wangwenpei/shining-flask/tree/master/aivptr
Migrate风格DB设计原则
以下为风爻建议的一般通用性原则。
· 保持简单
你应该保持当前DB表结构的简单,不要做过度的设计,尽量不要预留字段。
特别是不太确定是否要添加的字段。
· 保持小巧
在生成Migrate时候,应该尽可能的发挥DB的原子操作性。
不要多个表的Migrate混在一起操作。
在一些极端的情况下,执行Migrate时DB连接会出现中断,而利用好DB原子特性的Migrate,可以将风险和损失降到最低。
· 保持单一的核心
关系型数据库作为一种经典数据库,作为核心数据库,数据存储应该单一化,突出关系特性。
做关系型数据库擅长的事情,比如订单、会员资料等是适用于关系型数据库的。
一个简单(粗略但快速的)的判断标准是,你使用到的数据是否需要事务特性。
· 拥抱多元的扩展
现代数据库相比10年前已经出现了极大的演进,仅开源DB据统计就有100种之多。
随着需求的不断更新,关系型数据库虽然大部分特定需求都可以满足,但相比专用数据库实现复杂,性能和效率也差强人意。
对于一些专用数据,风爻建议使用外围DB更合适,如MongoDB, LevelDB, Cassandra等。
随着云计算的成熟,引入多种DB成本是极低的。
作者简介
王文沛(风爻) 开源爱好者。专注于Python社区,曾为数个明星社区贡献过代码,如Mongoengine、Django Plugins级项目等。对Javascript也有少量涉猎,曾为Pug(原名Jade)社区等贡献过代码。也为国内一些云厂商SDK捐助过代码或撰写过第三方独立扩展。对于开源生态:主张融合与协作,尽量避免重复造轮子。目前就职于UCloud,担任资深SRE专家职务。