Bootstrap 是什么

TiDB 的 Bootstrap 是启动时的初始化过程,由 session.go 中的 BootstrapSession 完成。它在 tidb-server 的 createStoreAndDomain 中被调用。

先判断是新建还是升级

Bootstrap 分为两种情形。它首先会去存储层中读取 BootstrapKey,取得当前的 bootstrapVersion:

  • 如果版本号为 0,说明这是一个新集群,执行 bootstrap;
  • 如果版本号低于 currentBootstrapVersion,说明是已有集群需要升级,执行 upgrade。
func BootstrapSession(store kv.Storage) (*domain.Domain, error) {
    ver := getStoreBootstrapVersion(store)
    if ver == notBootstrapped {
        runInBootstrapSession(store, bootstrap)
    } else if ver < currentBootstrapVersion {
        runInBootstrapSession(store, upgrade)
    }
    // ...
}

bootstrap:创建系统表并写入元数据

新集群的初始化会先做一次已初始化检查,然后分两步完成:

// bootstrap initiates system DB for a store.
func bootstrap(s Session) {
    b, err := checkBootstrapped(s)
    if err != nil {
        log.Fatal(err)
    }
    if b {
        upgrade(s)
        return
    }
    doDDLWorks(s) // 创建一些系统表
    doDMLWorks(s) // 插入一些元数据到系统表
}

doDDLWorks 负责建立系统库与系统表;doDMLWorks 负责写入初始元数据。这个划分很清晰:结构走 DDL,数据走 DML。

一个具体的例子是系统变量。所有系统变量定义在 sysvar.godefaultSysVars 中,在 init 阶段被设置到 SysVars map 里;而在 doDMLWorks 时,它们会被逐一插入到 mysql.GLOBAL_VARIABLES 表中。

upgrade:按版本号逐级升级

升级路径则依据版本号逐级推进:

func upgrade(s Session) {
    ver, err := getBootstrapVersion(s)
    if err != nil {
        log.Fatal(errors.Trace(err))
    }
    if ver >= currentBootstrapVersion {
        // 已被更高版本的 TiDB server 升级过
        return
    }
    // ...
    if ver < version15 {
        upgradeToVer15(s) // 例如这里创建了 mysql.gc_delete_range
    }
    // ...
}

可以看到,每个版本区间对应一个升级函数。这样做的好处是升级逻辑可以累积,任意旧版本都能通过顺序执行升级函数到达最新状态。已是最新版本时直接返回,避免重复执行升级动作。

启动的后台协程

除了元数据初始化,BootstrapSession 还负责启动若干长期运行的后台任务。

LoadPrivilegeLoop:权限变更监听

因为 TiDB 是分布式的,权限数据可能被其他 TiDB 实例修改了。为了让所有实例保持一致,TiDB 使用 etcd 作为变更通知通道:当权限被修改时,通知所有 TiDB 实例重新加载新的权限数据

其职责包括创建一个 goroutine 循环加载权限表,并在启动时先做一次全量更新。从注释可以看出,它应当在 BootstrapSession 中只被调用一次。

UpdateTableStatsLoop:统计信息维护

这个循环用于做 Analyze。它会创建统计信息句柄、注册 DDL 事件通道(表结构变化时统计信息需要相应更新),并先执行一次更新。若统计信息租约为非正值则直接返回;否则会启动统计信息更新 worker;在开启自动分析的情况下,还会额外启动自动分析 worker。

统计信息的重要性在于:优化器选择执行计划时依赖统计信息估算行数,统计信息陈旧会直接导致执行计划选错。

GCWorker:垃圾回收

GCWorker 同样在 BootstrapSession 中被启动,且仅在存储后端支持 etcd 时启动:

if raw, ok := store.(domain.EtcdBackend); ok {
    err = raw.StartGCWorker()
    if err != nil {
        return nil, errors.Trace(err)
    }
}

GCWorker 主要做三件事:

  1. 处理 DDL 的 DropTable、DropIndex 等产生的 Delete range 操作;
  2. 执行存储层的 GC,基于数据生命周期清理保存的历史版本数据;
  3. 扫描全库,查找并清理过期的锁。

小结

Bootstrap 这条链路虽然叫「初始化」,但它实际承担了三类职责:元数据结构与初始数据的一次性建立、版本化的升级推进、以及长期后台任务的启动。理解这条链路,对排查启动失败、升级异常和后台任务不生效等问题都很有帮助。

Bootstrap 职责清单
版本判定读取 BootstrapKey 取得版本号,为 0 则初始化新集群,低于当前版本则升级
DDL 工作doDDLWorks 创建系统库与系统表
DML 工作doDMLWorks 写入初始元数据,例如把默认系统变量插入系统表
升级机制按版本区间顺序执行升级函数,已是最新版本直接返回
后台任务权限加载循环、统计信息更新与自动分析、GCWorker 垃圾回收