这是《Elasticsearch 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 索引设计决定了系统未来的容量、性能和可维护性。分片数不合理、副本不足、字段膨胀、无生命周期管理,这些问题在数据量小时不明显,上线后会集中爆发。
本章覆盖索引规划、分片与副本、索引模板、Component Template、Data Stream、ILM 和索引治理。
16.1 索引设计原则
设计前先回答:
| 问题 | 说明 |
|---|---|
| 数据规模 | 当前量和 12 个月增长量 |
| 写入模型 | 持续写入、批量导入、频繁更新 |
| 查询模型 | 搜索、聚合、点查、时间范围 |
| 保留周期 | 天、月、年或永久 |
| 可用性 | 可接受的数据丢失和恢复时间 |
| 业务隔离 | 租户、环境、业务域 |
| 成本 | 磁盘、内存、节点和网络 |
通用原则:
- 业务搜索常用大索引加别名;
- 日志和指标按时间滚动索引;
- 主分片数按容量和写入吞吐规划;
- 副本至少 1 个;
- Mapping 提前定义;
- 所有时间序列数据设置生命周期;
- 索引名可预测但不直接硬编码到应用。
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,但避免过多碎索引 |
分片过多的代价:
- 更多元数据;
- 更多文件句柄;
- 更多次任务调度;
- 查询扇出更大;
- segment merge 更碎;
- master 节点压力更高。
16.3 副本设计
PUT /products/_settings
{
"index": {
"number_of_replicas": 1
}
}
副本作用:
- 主分片故障时提升为主;
- 提供读并发;
- 分布负载到更多节点。
副本代价:
- 磁盘空间翻倍;
- 写入放大;
- 更多内存和文件句柄;
- 分片平衡压力增加。
生产建议:
| 场景 | 副本数 |
|---|---|
| 生产关键业务 | 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": {}
}
优点:
- 统一配置;
- 降低复制成本;
- 便于版本管理;
- 适合平台化治理。
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"
}
要求:
- 必须有
@timestamp; - 写入进入当前 backing index;
- 只支持追加写;
- 更新和删除受限;
- 查询通过 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"
}
}
影响:
- 调大减少小 segment;
- 写入吞吐更高;
- 数据可见延迟增加;
- 调小会增加刷新压力;
-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 本章小结
- 索引设计要提前评估容量、查询、写入、保留和容灾;
- 主分片数创建后不能直接修改,必须按单分片目标大小规划;
- 副本提供容灾和读并发,但会增加成本和写入放大;
- 索引模板和 Component Template 统一配置;
- Data Stream 适合只追加时间序列数据;
- ILM 管理 hot、warm、cold、delete 生命周期;
- 应用应使用别名,不直接依赖物理索引名。
16.14 思考题
- 600GB 日志数据如何规划主分片和副本?
- 为什么索引主分片数不能直接修改?
- Data Stream 为什么要求时间字段和只追加写入?
refresh_interval从 1s 调到 30s 有什么利弊?- 如何为一个保留 90 天的日志索引设计 ILM?