ElasticsearchNotes

第 16 章:索引设计与模板

zjc 于 2026-01-16 发布

这是《Elasticsearch 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 索引设计决定了系统未来的容量、性能和可维护性。分片数不合理、副本不足、字段膨胀、无生命周期管理,这些问题在数据量小时不明显,上线后会集中爆发。

本章覆盖索引规划、分片与副本、索引模板、Component Template、Data Stream、ILM 和索引治理。

16.1 索引设计原则

设计前先回答:

问题 说明
数据规模 当前量和 12 个月增长量
写入模型 持续写入、批量导入、频繁更新
查询模型 搜索、聚合、点查、时间范围
保留周期 天、月、年或永久
可用性 可接受的数据丢失和恢复时间
业务隔离 租户、环境、业务域
成本 磁盘、内存、节点和网络

通用原则:

  1. 业务搜索常用大索引加别名;
  2. 日志和指标按时间滚动索引;
  3. 主分片数按容量和写入吞吐规划;
  4. 副本至少 1 个;
  5. Mapping 提前定义;
  6. 所有时间序列数据设置生命周期;
  7. 索引名可预测但不直接硬编码到应用。

16.2 分片规划

主分片数量创建后不能直接修改。

PUT /products
{
  "settings": {
    "number_of_shards": 6,
    "number_of_replicas": 1
  }
}

常见规划步骤:

预估总量
-> 选择单分片目标大小
-> 计算主分片数
-> 结合节点数和写入吞吐调整
-> 压测验证

示例:

数据量:600GB
单分片目标:50GB
主分片数:600 / 50 = 12
节点数:6
每节点主分片:2
副本数:1
总分片数:24

建议范围:

数据类型 单分片大小
日志流水 10-50GB
业务搜索 20-75GB
安全审计 20-50GB
高频小索引 可小于 10GB,但避免过多碎索引

分片过多的代价:

  1. 更多元数据;
  2. 更多文件句柄;
  3. 更多次任务调度;
  4. 查询扇出更大;
  5. segment merge 更碎;
  6. master 节点压力更高。

16.3 副本设计

PUT /products/_settings
{
  "index": {
    "number_of_replicas": 1
  }
}

副本作用:

  1. 主分片故障时提升为主;
  2. 提供读并发;
  3. 分布负载到更多节点。

副本代价:

  1. 磁盘空间翻倍;
  2. 写入放大;
  3. 更多内存和文件句柄;
  4. 分片平衡压力增加。

生产建议:

场景 副本数
生产关键业务 1 或 2
开发测试 0
可搜索快照冷层 视方案而定
临时索引 0 或 1

16.4 索引模板

索引模板在创建索引时自动应用设置和 Mapping。

PUT /_index_template/applogs
{
  "index_patterns": ["applogs-*"],
  "priority": 100,
  "template": {
    "settings": {
      "number_of_shards": 3,
      "number_of_replicas": 1,
      "refresh_interval": "30s"
    },
    "mappings": {
      "dynamic": "false",
      "properties": {
        "@timestamp": { "type": "date" },
        "level": { "type": "keyword" },
        "service": { "type": "keyword" },
        "trace_id": { "type": "keyword" },
        "message": { "type": "text" },
        "host": {
          "properties": {
            "name": { "type": "keyword" },
            "ip": { "type": "ip" }
          }
        }
      }
    }
  },
  "data_stream": {}
}

模板匹配多个时,priority 越高越优先。

16.5 Component Template

把公共配置拆成组件:

PUT /_component_template/base-settings
{
  "template": {
    "settings": {
      "number_of_shards": 3,
      "number_of_replicas": 1,
      "refresh_interval": "30s"
    }
  }
}

通用 Mapping:

PUT /_component_template/common-fields
{
  "template": {
    "mappings": {
      "properties": {
        "created_at": { "type": "date" },
        "updated_at": { "type": "date" },
        "source": { "type": "keyword" }
      }
    }
  }
}

组合模板:

PUT /_index_template/applogs
{
  "index_patterns": ["applogs-*"],
  "composed_of": ["base-settings", "common-fields"],
  "priority": 100,
  "data_stream": {}
}

优点:

  1. 统一配置;
  2. 降低复制成本;
  3. 便于版本管理;
  4. 适合平台化治理。

16.6 Data Stream

Data Stream 适合只追加的时间序列数据:

logs-events
 +-- .ds-logs-events-2026.08.25-000001
 +-- .ds-logs-events-2026.08.26-000002

创建模板:

PUT /_index_template/logs-events-template
{
  "index_patterns": ["logs-events*"],
  "data_stream": {},
  "template": {
    "settings": {
      "number_of_shards": 3,
      "number_of_replicas": 1
    },
    "mappings": {
      "properties": {
        "@timestamp": { "type": "date" }
      }
    }
  }
}

创建 Data Stream:

PUT /_data_stream/logs-events

写入:

POST /logs-events/_doc
{
  "@timestamp": "2026-08-25T10:00:00Z",
  "level": "INFO",
  "message": "request finished"
}

要求:

  1. 必须有 @timestamp
  2. 写入进入当前 backing index;
  3. 只支持追加写;
  4. 更新和删除受限;
  5. 查询通过 Data Stream 名。

16.7 rollover

rollover 按条件滚动新索引:

POST /applogs-000001/_rollover
{
  "conditions": {
    "max_age": "1d",
    "max_primary_shard_size": "50gb",
    "max_docs": 100000000
  }
}

别名必须带 is_write_index

POST /_aliases
{
  "actions": [
    {
      "add": {
        "index": "applogs-000001",
        "alias": "applogs",
        "is_write_index": true
      }
    }
  ]
}

滚动条件建议优先使用 max_primary_shard_size,而不是只看天数。

16.8 ILM 索引生命周期

ILM 管理索引从热到删除的全生命周期。

PUT /_ilm/policy/applogs-policy
{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_age": "1d",
            "max_primary_shard_size": "50gb"
          },
          "set_priority": { "priority": 100 }
        }
      },
      "warm": {
        "min_age": "3d",
        "actions": {
          "shrink": { "number_of_shards": 1 },
          "forcemerge": { "max_num_segments": 1 },
          "allocate": { "number_of_replicas": 1 }
        }
      },
      "cold": {
        "min_age": "30d",
        "actions": {
          "set_priority": { "priority": 0 }
        }
      },
      "delete": {
        "min_age": "90d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}

阶段说明:

阶段 目标
Hot 接收写入和高频查询
Warm 不写入,仍常查询,可合并和收缩
Cold 偶尔查询,降低成本
Frozen 低频查询,成本更低
Delete 删除索引

查看状态:

GET /applogs-*/_ilm/explain

16.9 refresh_interval

默认约 1s:

{
  "index": {
    "refresh_interval": "1s"
  }
}

高写入日志场景可调大:

PUT /applogs-000001/_settings
{
  "index": {
    "refresh_interval": "30s"
  }
}

影响:

  1. 调大减少小 segment;
  2. 写入吞吐更高;
  3. 数据可见延迟增加;
  4. 调小会增加刷新压力;
  5. -1 表示禁用自动刷新。

16.10 translog 与 flush

常用配置:

{
  "index.translog.durability": "request",
  "index.translog.sync_interval": "5s",
  "index.translog.flush_threshold_size": "512mb"
}
配置 说明
request 每请求 fsync,可靠性高,成本高
async 周期 fsync,性能更好,丢失窗口更大
flush_threshold_size translog 达到阈值触发 flush

日志类数据可评估 async;交易和审计类数据应谨慎评估 RPO。

16.11 索引命名与别名

推荐命名:

{domain}.{dataset}.{version}
{domain}.logs.{environment}.{date}

示例:

mall.products.v1
mall.products.v2
ops.applogs.prod.2026.08.25

应用使用别名:

POST /_aliases
{
  "actions": [
    {
      "add": {
        "index": "mall.products.v2",
        "alias": "mall.products.read"
      }
    },
    {
      "add": {
        "index": "mall.products.v2",
        "alias": "mall.products.write",
        "is_write_index": true
      }
    }
  ]
}

读写别名分开,便于灰度和回滚。

16.12 索引容量治理

定期检查:

GET /_cat/indices?v&h=index,docs.count,store.size,pri.store.size
GET /_cat/shards?v
GET /_cluster/allocation/explain

治理项:

指标 问题
索引数量过多 元数据压力
单分片过小 浪费调度成本
单分片过大 恢复和迁移慢
副本不足 容灾风险
副本过多 成本过高
Mapping 字段过多 Mapping 爆炸
segment 过多 合并不足

16.13 本章小结

16.14 思考题

  1. 600GB 日志数据如何规划主分片和副本?
  2. 为什么索引主分片数不能直接修改?
  3. Data Stream 为什么要求时间字段和只追加写入?
  4. refresh_interval 从 1s 调到 30s 有什么利弊?
  5. 如何为一个保留 90 天的日志索引设计 ILM?