数据库基础体系 · 第 129/139 篇。文章以各产品官方稳定版本的公开语义为准;示例会明确引擎、事务与部署边界。

Elasticsearch 快照、恢复与升级:兼容、重建索引、灰度和回滚

Elasticsearch 的快照、恢复和升级经常被放在同一个操作手册中,但它们解决的是不同问题:

  • 快照(snapshot):把集群中的索引数据和部分集群元数据保存到外部仓库,用于灾备、迁移和恢复。
  • 恢复(restore):从快照仓库重新创建索引和相关状态。
  • 升级(upgrade):更换 Elasticsearch 节点运行的版本,通常不等于重建索引。
  • 重建索引(reindex):读取旧索引中的文档,按照新的映射、分词器或字段结构写入新索引。
  • 灰度(canary/gray rollout):让新版本集群或新索引逐步承接流量。
  • 回滚(rollback):在变更失败后恢复到可用状态。Elasticsearch 的回滚通常不是把已升级节点“降级回去”,而是恢复快照、切换流量,或切换到仍保留的旧集群。

理解这些边界很重要。快照可以保存数据,但不能自动解决版本兼容;升级可以保留索引,但不能自动改变映射;重建索引可以改变结构,但不是事务性数据库迁移;灰度可以降低发布风险,但必须处理新旧数据之间的同步问题。


一、先建立三个边界:数据、索引结构和集群状态

一个 Elasticsearch 集群至少包含三类需要区分的状态。

1. 文档数据

例如:

{
  "_id": "o-1001",
  "customer_id": 42,
  "amount": 199.9,
  "created_at": "2025-01-01T10:00:00Z"
}

文档存储在分片中。主分片负责接受写入,副本分片用于冗余和读取。快照最终保存的是分片层面的数据文件及其元数据,而不是一个逐条执行的“导出 JSON 文件”。

2. 索引结构

索引结构包括:

  • 字段映射(mapping);
  • 分析器、分词器和归一化器;
  • 索引设置;
  • 分片数和副本数;
  • 数据流、索引模板、别名等相关配置。

有些设置只能在创建索引时确定,例如主分片数量。想改变这类结构,通常需要创建新索引并重建数据,而不是直接修改旧索引。

3. 集群状态

集群状态包括:

  • 索引和别名元数据;
  • 模板;
  • ILM 策略;
  • 持久化集群设置;
  • 部分系统功能的状态;
  • 与安全、任务、观察性等功能相关的状态。

快照可以包含一部分集群状态,但恢复集群状态是高风险操作。把生产集群的全局状态直接恢复到另一个已有业务的集群,可能覆盖或引入模板、别名和持久化设置。因此,跨环境恢复通常明确使用:

"include_global_state": false

然后按目标环境重新安装模板、策略和必要配置。


二、快照到底保存了什么

2.1 快照仓库不是单个备份文件

Elasticsearch 快照需要先配置一个 snapshot repository。仓库可以是共享文件系统、对象存储或其他官方支持的存储类型。生产环境通常要求:

  1. 所有可能执行快照或恢复的节点都能访问仓库;
  2. 仓库由 Elasticsearch 统一读写;
  3. 不要让外部程序直接修改仓库目录;
  4. 仓库中的数据不应和任一 Elasticsearch 数据目录混用;
  5. 仓库本身应有访问控制、跨可用区或跨地域冗余。

以共享文件系统为例,节点配置中必须允许对应路径:

path.repo:
  - /srv/es-snapshots

随后在集群中注册仓库:

curl -X PUT "$ES/_snapshot/backup-repo" \
  -H 'Content-Type: application/json' \
  -d '{
    "type": "fs",
    "settings": {
      "location": "/srv/es-snapshots"
    }
  }'

这里的 backup-repo 是 Elasticsearch 内部使用的仓库名称,location 是各节点看到的共享路径。单机本地目录不等于生产级备份:如果节点磁盘损坏,数据和快照可能同时丢失。

注册后应验证仓库:

curl -X POST "$ES/_snapshot/backup-repo/_verify"

成功时会返回参与验证的节点。若部分节点看不到目录,验证会失败;这比等到灾难恢复时才发现权限或挂载错误要安全得多。

2.2 快照是增量的,但恢复结果是完整索引

同一仓库中连续创建的快照会复用已经存在的段文件,因此第二次快照通常不需要重新上传所有未变化的数据。这是仓库层面的增量复用。

但恢复某个快照时,目标索引应表现为该快照时间点的完整索引。不能把“增量快照”理解为必须按顺序恢复:

snapshot-001 -> snapshot-002 -> snapshot-003

通常可以直接恢复 snapshot-003,因为快照元数据会引用仓库中所需的所有文件。

快照过程中仍可继续读写。快照记录的是一个一致的恢复点,但它不是应用层事务:

  • 某个文档在快照开始前提交,通常可能被纳入快照;
  • 快照开始后的新写入可能不在快照中;
  • 恢复得到的是快照对应的时间点,而不是恢复时刻的实时数据;
  • 跨索引的业务事务不会因为快照而自动具备数据库事务语义。

因此,快照时间点决定了数据恢复点,应用在这之后产生的写入需要通过重放、双写、日志或其他方式补回。

2.3 创建和检查快照

创建快照:

curl -X PUT "$ES/_snapshot/backup-repo/prod-2025-03-01" \
  -H 'Content-Type: application/json' \
  -d '{
    "indices": "orders-*,customers-*",
    "include_global_state": false,
    "wait_for_completion": false
  }'

wait_for_completion: false 表示请求提交后立即返回,快照在后台执行。查询快照:

curl "$ES/_snapshot/backup-repo/prod-2025-03-01"
curl "$ES/_snapshot/backup-repo/prod-2025-03-01/_status"

必须检查快照的最终状态,而不能仅凭创建请求返回 200 就认为备份成功。需要关注:

  • state 是否为 SUCCESS
  • 是否有失败分片;
  • 快照包含了哪些索引;
  • 目标索引在快照期间是否发生了异常;
  • 仓库是否仍然可读。

如果快照存在失败分片,不能把它当作完整灾备点。失败原因通常可以从快照详情、集群日志和具体分片状态中诊断。

2.4 快照不等于所有配置的备份

快照与以下内容不能简单等同:

  • Elasticsearch 安装包和 JVM 参数;
  • 节点证书、操作系统配置;
  • 外部负载均衡器配置;
  • 应用代码和应用数据库;
  • 某些外部密钥管理系统中的密钥;
  • 由外部系统生成的组件配置。

安全相关状态、系统索引和功能状态是否包含,取决于具体 Elasticsearch 版本、功能和快照选项。不能仅凭“快照成功”推断整个运行环境已经可以原样重建。灾备方案应同时保存目标版本、插件清单、集群配置、证书和访问控制配置。


三、恢复快照:恢复的是数据,不是无条件覆盖目标集群

3.1 在新集群中恢复通常最安全

在目标集群中恢复一个索引:

curl -X POST "$TARGET_ES/_snapshot/backup-repo/prod-2025-03-01/_restore" \
  -H 'Content-Type: application/json' \
  -d '{
    "indices": "orders-*",
    "include_global_state": false,
    "include_aliases": false,
    "wait_for_completion": false
  }'

恢复前需要注意:

  • 目标集群必须能访问同一个仓库;
  • 目标集群中不能已经存在同名索引,除非先处理冲突;
  • 目标集群的节点、磁盘和分片容量必须足够;
  • 目标版本必须满足快照兼容要求;
  • 恢复的是索引数据,目标集群的模板和策略可能仍需单独配置。

如果不想恢复后与现有索引重名,可以使用重命名:

curl -X POST "$TARGET_ES/_snapshot/backup-repo/prod-2025-03-01/_restore" \
  -H 'Content-Type: application/json' \
  -d '{
    "indices": "orders-*",
    "include_global_state": false,
    "include_aliases": false,
    "rename_pattern": "orders-(.*)",
    "rename_replacement": "recovered-orders-$1",
    "wait_for_completion": false
  }'

恢复过程是异步的,应观察:

curl "$TARGET_ES/_cluster/health?level=indices"
curl "$TARGET_ES/_cat/recovery?v"
curl "$TARGET_ES/_cat/shards?v"

在副本恢复完成前,索引可能暂时不是绿色状态。若目标磁盘不足,恢复可能卡在分片分配阶段,而不是立即返回一个直观的“磁盘不足”错误。此时应结合:

curl "$TARGET_ES/_cluster/allocation/explain?pretty"

查看具体分片为什么没有被分配。

3.2 恢复到已有集群的风险

如果目标集群中已经存在同名索引,恢复通常会因名称冲突失败。不能为了绕过错误直接删除生产索引。更安全的顺序是:

  1. 恢复到临时名称;
  2. 验证文档数量、映射、关键查询和时间范围;
  3. 决定是否停写、重命名或切换别名;
  4. 最后再清理旧索引。

如果恢复 include_global_state: true,可能恢复全局状态和功能状态。对于空的灾备集群,这可能是需要的;对于已有其他业务的集群,可能造成模板、别名或持久化设置冲突。除非明确知道恢复内容和影响范围,否则不应默认恢复全局状态。


四、版本兼容:快照恢复和原地升级是两套规则

4.1 快照恢复的基本方向

一般规则是:

  • 快照可以恢复到创建它的同一 Elasticsearch 版本;
  • 快照通常可以恢复到较新的兼容版本;
  • 新版本创建的快照不能恢复到较旧版本;
  • 跨多个大版本时必须遵守官方规定的升级路径,不能自行假设“版本号更大就一定能恢复”。

形式化地表示,若版本兼容关系为:

VsVtV_s \preceq V_t

其中:

  • VsV_s 是创建快照的源版本;
  • VtV_t 是恢复目标版本;
  • \preceq 表示官方支持的快照恢复方向;

则恢复要求至少满足:

CompatibleSnapshot(Vs,Vt)=true\text{CompatibleSnapshot}(V_s,V_t)=\text{true}

而不是简单满足数字上的:

number(Vs)number(Vt)\text{number}(V_s)\leq \text{number}(V_t)

例如,不能把“8.x 创建的快照可以恢复到 9.x”推广为“任意旧版本都可以直接恢复到任意新版本”。跨大版本时还要检查索引格式、弃用特性、系统索引和具体版本文档。

4.2 升级不是降级

Elasticsearch 的原地升级通常不是可逆事务。节点一旦使用新版本启动并对数据目录或集群状态完成升级,就不能把同一数据目录直接交给旧版本继续运行。即使程序能够启动,也不代表这种操作受支持。

因此,升级前的正确回滚资产是:

  • 升级前版本的完整快照;
  • 可启动的旧集群或旧节点镜像;
  • 应用侧可切换的流量入口;
  • 对写入的补偿或重放方案。

“保留旧二进制包”不等于具备回滚能力。

4.3 版本升级前的检查

升级前至少需要确认:

  1. 当前版本到目标版本的官方升级路径;
  2. 是否需要先升级到中间大版本;
  3. 现有插件是否有目标版本;
  4. 客户端是否支持目标集群;
  5. Kibana 与 Elasticsearch 版本是否匹配;
  6. 是否存在弃用 API、旧映射类型或废弃设置;
  7. 快照是否成功且做过实际恢复演练;
  8. 磁盘空间是否足以承受恢复、段合并和副本迁移;
  9. 是否存在未完成的集群任务、异常分片或持续写入压力。

检查集群状态:

curl "$ES/_cluster/health?pretty"
curl "$ES/_cat/nodes?v"
curl "$ES/_cat/shards?v"
curl "$ES/_cluster/pending_tasks?pretty"

检查弃用信息和日志比只看集群健康更重要。green 只说明当前分片分配状态正常,不说明所有旧 API、插件和业务查询都兼容新版本。


五、滚动升级:节点逐个更换,集群暂时处于混合版本

5.1 滚动升级的状态变化

滚动升级是指:

全部旧版本
  -> 停止一个节点
  -> 启动该节点的新版本
  -> 等待它重新加入集群
  -> 等待分片和集群状态稳定
  -> 处理下一个节点
  -> 全部新版本

在中间阶段,集群会同时运行旧版本和新版本节点。这个状态只有在官方升级路径允许时才成立。

混合版本期间要区分三种兼容性:

  1. 节点间通信兼容性:新旧节点能否加入同一个集群;
  2. 集群状态兼容性:旧节点能否理解新节点提交的状态;
  3. 数据格式兼容性:旧版本能否读取已经被新版本写入或升级过的数据。

因此,混合版本集群不是长期运行模式。升级期间通常只能使用两者共同支持的能力,不能在尚未全部升级时依赖新版本专有特性。

5.2 升级一个节点时发生什么

假设某节点承载主分片和副本,操作顺序通常包括:

  1. 暂停或降低该节点的分片迁移影响;
  2. 停止节点;
  3. 替换 Elasticsearch 程序和兼容插件;
  4. 使用原有配置启动;
  5. 等待节点重新加入集群;
  6. 检查节点版本、角色和分片状态;
  7. 恢复正常分配策略;
  8. 等待集群稳定后处理下一个节点。

具体分配开关和操作顺序应以目标版本官方升级文档为准,不能机械套用旧版本命令。升级期间关闭分片分配可以避免节点短暂停止时立刻触发大量迁移,但关闭时间过长会降低故障冗余。

升级后检查:

curl "$ES/_cat/nodes?v"
curl "$ES/_cluster/health?level=indices&pretty"
curl "$ES/_cat/recovery?v"

结果应关注:

  • 新节点是否已经加入;
  • 节点角色是否正确;
  • 主分片和副本是否都已分配;
  • 是否有未分配分片;
  • 是否出现持续的 pending tasks;
  • 查询、写入、Bulk 和 Ingest 管道是否正常。

滚动升级不能保证业务无感。节点离开会导致连接重建、请求重试、分片迁移和缓存失效。客户端必须设置合理的连接池、超时和重试策略,并避免把不可重试的写入错误盲目重试成重复文档。


六、重建索引:解决结构变化,而不是替代快照

6.1 为什么不能直接修改某些映射

假设旧索引中:

"amount": {
  "type": "text"
}

现在希望把它改成:

"amount": {
  "type": "scaled_float",
  "scaling_factor": 100
}

已有倒排索引和列式数据已经按 text 生成。直接把字段声明改为数值类型,并不会把历史数据重新解析成数值,也不能安全地改变已有字段的物理索引结构。

正确方法是:

  1. 创建新索引;
  2. 使用目标映射和设置;
  3. 将旧索引文档写入新索引;
  4. 验证;
  5. 原子切换别名。

6.2 一个完整的别名切换示例

先创建目标索引:

curl -X PUT "$ES/orders-v2" \
  -H 'Content-Type: application/json' \
  -d '{
    "settings": {
      "number_of_shards": 3,
      "number_of_replicas": 1
    },
    "mappings": {
      "properties": {
        "customer_id": { "type": "long" },
        "amount": {
          "type": "scaled_float",
          "scaling_factor": 100
        },
        "created_at": { "type": "date" }
      }
    }
  }'

将旧索引写入新索引:

curl -X POST "$ES/_reindex?wait_for_completion=false" \
  -H 'Content-Type: application/json' \
  -d '{
    "source": {
      "index": "orders-v1"
    },
    "dest": {
      "index": "orders-v2",
      "op_type": "create"
    }
  }'

op_type: create 的含义是:目标中已有相同 _id 时不覆盖。这样可以避免重跑任务时静默覆盖目标数据,但也要求检查版本冲突和失败文档。

查询任务:

curl "$ES/_tasks/<task_id>?pretty"

生产环境通常需要显式设置切片、限速和批量大小,但这些参数应根据节点资源和版本文档调整。切片可以并行化重建,代价是增加 CPU、磁盘和合并压力;无限速会让在线业务与重建任务竞争资源。

6.3 重建期间的并发写入问题

设:

  • T0T_0:重建开始时间;
  • T1T_1:重建完成时间;
  • W(T0,T1)W(T_0,T_1):旧索引在此期间的新写入集合;
  • RR:重建任务已经复制到新索引的数据。

那么普通 _reindex 得到的目标数据大致是:

Dnew=RD_{\text{new}} = R

而不是:

Dnew=Dold at T1D_{\text{new}} = D_{\text{old at }T_1}

如果旧索引在 T0T_0T1T_1 期间持续更新,则可能有:

W(T0,T1)⊈RW(T_0,T_1)\not\subseteq R

这些更新就会丢失,除非额外补偿。

常见解决方案有三类。

方案一:短暂停写

流程为:

停止业务写入
  -> 执行 reindex
  -> 校验新旧索引
  -> 原子切换别名
  -> 恢复写入

它最容易证明正确,但停写时间取决于数据量和资源。

方案二:双写

应用先同时写 orders-v1orders-v2,再对历史数据执行重建。此时要处理:

  • 双写部分成功;
  • 重试导致重复写入;
  • 两边写入顺序不同;
  • 删除操作如何同步;
  • 目标索引切换前后的幂等性。

双写不是“调用两个 API”这么简单,必须有请求 ID、版本号或其他冲突解决规则。

方案三:重建后补偿

应用继续写旧索引,同时记录变更日志。重建完成后:

  1. 读取 T0T_0 之后的变更;
  2. 按顺序或按版本补写新索引;
  3. 对删除操作执行删除;
  4. 校验新旧索引的一致性;
  5. 切换别名。

如果没有可靠的变更日志,不能仅凭 _reindex 完成时间判断数据已经追平。

6.4 原子切换别名

假设业务访问的是 orders-read,写入别名是 orders-write。切换时使用一个 _aliases 请求:

curl -X POST "$ES/_aliases" \
  -H 'Content-Type: application/json' \
  -d '{
    "actions": [
      {
        "remove": {
          "alias": "orders-read",
          "index": "orders-v1"
        }
      },
      {
        "add": {
          "alias": "orders-read",
          "index": "orders-v2"
        }
      },
      {
        "remove": {
          "alias": "orders-write",
          "index": "orders-v1"
        }
      },
      {
        "add": {
          "alias": "orders-write",
          "index": "orders-v2",
          "is_write_index": true
        }
      }
    ]
  }'

同一个别名操作请求具有原子性:其他请求不会看到只完成了一半的别名变更。它并不保证正在执行的查询自动改读新索引;已经发出的请求仍可能在切换前使用旧索引。

切换后验证:

curl "$ES/_alias/orders-read"
curl "$ES/_alias/orders-write"
curl "$ES/orders-v2/_count"
curl "$ES/orders-v2/_search"

不要只比较 _count。还应验证:

  • 关键字段的空值率;
  • 数值范围和时间范围;
  • 聚合结果;
  • 排序;
  • 分词查询;
  • 删除记录;
  • 采样文档的 _source
  • 应用实际使用的查询 DSL。

七、灰度:新集群和新索引是两种不同的灰度

7.1 原地滚动升级不等于业务流量灰度

滚动升级是节点层面的版本切换。它主要验证:

  • 节点能否加入集群;
  • 集群状态能否正常传播;
  • 分片能否恢复;
  • 节点间协议是否兼容。

它并不天然提供“把 5% 用户请求发给新版本”的能力。一个混合版本集群通常仍是同一个逻辑集群,客户端不会按节点版本自动为请求做业务灰度。

如果需要真正的业务灰度,通常要准备两个可独立承载流量的集群:

旧集群 <---- 数据同步/快照恢复 ---- 新集群
    \                              /
     \---- 应用路由或负载均衡器 ----/

7.2 基于快照的灰度为什么有数据延迟

用快照把旧集群复制到新集群时,新集群只拥有快照时间点的数据:

Dnew(t0)=Dold(t0)D_{\text{new}}(t_0)=D_{\text{old}}(t_0)

若当前时间为 t1t_1,则旧集群在区间 (t0,t1](t_0,t_1] 产生的变更:

ΔD(t0,t1]\Delta D(t_0,t_1]

不会自动出现在新集群中。因此不能直接把读流量切到仅通过一次旧快照创建的新集群,除非允许这段数据丢失。

要实现低风险灰度,需要补齐这段变更,例如:

  • 应用双写;
  • 使用可靠的变更日志;
  • 使用官方支持的跨集群复制能力,并确认目标版本、许可和故障切换语义;
  • 在切换前短暂停写并完成最终同步。

7.3 读灰度和写灰度的风险不同

读灰度可以先让少量查询进入新集群,通过比较:

  • 命中数量;
  • 聚合结果;
  • 延迟和错误率;
  • 业务关键字段;

来发现映射、分词和查询兼容问题。

写灰度更复杂,因为一旦新旧集群的数据 diverge,回滚时不仅要切回读流量,还要决定新集群已经接受的写入如何回放到旧集群。一个可回滚的写灰度必须定义:

  1. 哪个系统是权威写入源;
  2. 每条写入是否有幂等 ID;
  3. 更新和删除如何传播;
  4. 切换时如何确定最后一个已提交事件;
  5. 回滚时如何合并灰度期间的新数据。

没有这些定义,“保留旧集群”只能回滚程序,不能保证回滚数据。


八、回滚:先区分节点回滚、流量回滚和数据回滚

8.1 节点回滚通常不可行

以下操作不能视为安全回滚:

升级到 Elasticsearch 目标版本
  -> 停止节点
  -> 换回旧版本程序
  -> 使用原数据目录启动

原因是新版本可能已经改变数据目录、索引格式或集群元数据。旧版本通常不支持读取这些状态。

8.2 流量回滚

如果旧集群仍未改动,新集群通过应用网关承接灰度流量,则回滚可以是:

停止向新集群发送新流量
  -> 将流量权重恢复到旧集群
  -> 观察旧集群错误率和延迟
  -> 处理新集群产生但尚未同步的写入

这是最容易控制的回滚类型,但前提是旧集群仍然健康,且灰度期间的写入没有造成不可逆的数据分叉。

8.3 数据回滚

如果旧集群也已经被修改,或者必须回到某个历史时间点,则需要从快照恢复:

创建临时恢复集群
  -> 恢复升级前快照
  -> 验证索引和查询
  -> 补回快照后的必要写入
  -> 切换应用入口

若快照时间为 tst_s,故障发现时间为 tft_f,则未包含在快照中的数据窗口为:

[ts,tf][t_s,t_f]

恢复后的数据丢失量取决于该窗口内实际发生且无法重放的写入,而不是取决于快照文件大小。灾备设计中:

  • RPO 表示最多能接受多少数据时间窗口的丢失;
  • RTO 表示从故障发生到业务恢复需要多长时间。

快照频率只能约束潜在 RPO,不能单独保证实际 RPO;恢复速度、仓库吞吐量、分片数量和验证流程共同影响 RTO。


九、典型故障路径与诊断方法

1. 快照创建成功,但恢复失败

可能原因:

  • 目标版本不兼容;
  • 仓库权限变化;
  • 目标节点无法访问仓库;
  • 同名索引已存在;
  • 磁盘水位阻止分片分配;
  • 插件或分析器缺失;
  • 快照本身存在失败分片。

诊断顺序:

curl "$TARGET_ES/_snapshot/backup-repo/prod-2025-03-01"
curl "$TARGET_ES/_cat/recovery?v"
curl "$TARGET_ES/_cluster/allocation/explain?pretty"

同时检查节点日志和仓库访问权限。

2. 重建索引后文档数相同,但查询结果错误

常见原因:

  • 字段类型改变后,业务查询仍使用旧语义;
  • 分词器或 analyzer 配置不同;
  • null_value、日期格式或数值格式不同;
  • 重建脚本没有正确处理脏数据;
  • 只比较总文档数,没有验证删除和更新;
  • _reindex 期间存在并发写入遗漏。

应对比代表性查询和聚合,而不是只执行:

GET /orders-v2/_count

_count 相同只说明文档数量相同,不能证明字段值和索引行为相同。

3. 升级后集群长期黄色或红色

黄色表示至少有副本未分配,红色表示至少有主分片未分配。常见原因包括:

  • 节点角色或节点数量不满足分片分配条件;
  • 磁盘超过水位;
  • 分片恢复压力过大;
  • 分配过滤器仍指向旧节点;
  • 节点发现配置错误;
  • 插件或模块加载失败。

使用分配解释 API:

curl -X GET "$ES/_cluster/allocation/explain?pretty" \
  -H 'Content-Type: application/json' \
  -d '{
    "index": "orders-v2",
    "shard": 0,
    "primary": false
  }'

不要在没有理解原因时直接提高副本数、强制分配分片或关闭保护性设置。强制分配可能把问题从“服务不可用”变成“数据冗余已经丢失”。

4. 回滚后出现数据重复或覆盖

如果应用重试机制、双写逻辑和恢复后的补偿任务都没有统一的幂等键,可能出现:

  • 同一业务事件写入两次;
  • 新集群的更新覆盖旧集群较新的版本;
  • 删除事件没有被补回;
  • _id 相同但 _source 版本不一致。

解决这类问题需要业务事件 ID、版本号或事件时间线。Elasticsearch 的文档写入不是跨两个集群的分布式事务,不能用一次 HTTP 请求同时保证两个集群成功。


十、把升级、重建和回滚组织成一个可证明的流程

一个较完整的生产流程如下:

1. 明确目标:
   是升级节点版本,还是改变索引结构?

2. 建立恢复点:
   创建快照,确认 SUCCESS,并在隔离环境实际恢复。

3. 做兼容性检查:
   检查官方升级路径、插件、客户端、弃用 API 和模板。

4. 若只升级 Elasticsearch:
   选择官方支持的滚动升级或其他升级方式。
   每次只处理有限节点,等待状态稳定。

5. 若改变索引结构:
   创建 v2 索引,执行 reindex,并处理并发写入。
   验证查询、聚合、更新和删除。

6. 若需要业务灰度:
   使用独立集群或别名分流,不把混合版本误当作流量灰度。
   明确新旧集群的数据同步和写入权威。

7. 切换:
   使用原子别名切换或应用路由切换。
   保留旧索引或旧集群,直到观察窗口结束。

8. 回滚:
   优先切回仍然健康的旧集群或旧别名。
   如果必须回到历史数据,则恢复升级前快照并补回快照后的变更。

其中最关键的推导是:

  • 快照提供一个历史恢复点;
  • 重建索引提供一个新的结构;
  • 灰度提供逐步验证的流量路径;
  • 回滚依赖仍可用的旧数据或可重放的变更;
  • 原地升级本身通常不提供“换回旧二进制并继续运行”的回滚能力。

因此,真正可回滚的发布不是“执行命令时多加一个参数”,而是让数据复制、写入一致性、流量入口和恢复点在变更前就形成闭环。


系列导航与关联阅读

官方资料

本文依据数据库官方文档重新梳理;正文、示例与生产检查清单由 WR BLOG 编写。