---
title: "时间序列建模"
date: 0001-01-01
description: "按时间组织索引与数据：常见模式与注意事项。"
tags: ["最佳实践", "数据建模"]
summary: "时间序列建模 #  时间序列数据（如日志、指标、事件流）是 Easysearch 的常见用例。这类数据有几个特点：文档数量快速增长、基本不更新、主要查询最近的数据。本页介绍如何为时间序列数据设计索引结构。
时间序列数据的特点 #  与传统的搜索场景不同，时间序列数据有以下特点：
 文档数量快速增长：日志、指标等数据持续写入，不会停顿 文档基本不更新：写入后很少修改，主要是追加 查询集中在最近数据：大多数查询关注最近几小时、几天或几周的数据 旧数据逐渐失去价值：随着时间推移，旧数据的查询频率降低  按时间范围索引 #  如果我们为此种类型的文档建立一个超大索引，我们可能会很快耗尽存储空间。日志事件会不断的进来，不会停顿也不会中断。
我们可以使用 scroll 查询和批量删除来删除旧的事件。但这种方法非常低效。当你删除一个文档，它只会被标记为被删除。在包含它的段被合并之前不会被物理删除。
替代方案是，我们使用一个时间范围索引。你可以着手于一个按年的索引 (logs_2014) 或按月的索引 (logs_2014-10)。也许当你的数据变得十分繁忙时，你需要切换到一个按天的索引 (logs_2014-10-24)。删除旧数据十分简单：只需要删除旧的索引。
这种方法有这样的优点，允许你在需要的时候进行扩容。你不需要预先做任何艰难的决定。每天都是一个新的机会来调整你的索引时间范围来适应当前需求。
应用相同的逻辑到决定每个索引的大小上。起初也许你需要的仅仅是每周一个主分片。过一阵子，也许你需要每天五个主分片。这都不重要——任何时间你都可以调整到新的环境。
使用别名管理时间序列索引 #  别名可以帮助我们更加透明地在索引间切换。当创建索引时，你可以将 logs_current 指向当前索引来接收新的日志事件，当检索时，更新 last_3_months 来指向所有最近三个月的索引：
POST /_aliases { &#34;actions&#34;: [ { &#34;add&#34;: { &#34;alias&#34;: &#34;logs_current&#34;, &#34;index&#34;: &#34;logs_2014-10&#34; }}, { &#34;remove&#34;: { &#34;alias&#34;: &#34;logs_current&#34;, &#34;index&#34;: &#34;logs_2014-09&#34; }}, { &#34;add&#34;: { &#34;alias&#34;: &#34;last_3_months&#34;, &#34;index&#34;: &#34;logs_2014-10&#34; }}, { &#34;remove&#34;: { &#34;alias&#34;: &#34;last_3_months&#34;, &#34;index&#34;: &#34;logs_2014-07&#34; }} ] } 这样，写入操作始终使用 logs_current 别名，查询操作可以使用 last_3_months 别名来查询最近三个月的数据。"
---


# 时间序列建模

时间序列数据（如日志、指标、事件流）是 Easysearch 的常见用例。这类数据有几个特点：文档数量快速增长、基本不更新、主要查询最近的数据。本页介绍如何为时间序列数据设计索引结构。

## 时间序列数据的特点

与传统的搜索场景不同，时间序列数据有以下特点：

- **文档数量快速增长**：日志、指标等数据持续写入，不会停顿
- **文档基本不更新**：写入后很少修改，主要是追加
- **查询集中在最近数据**：大多数查询关注最近几小时、几天或几周的数据
- **旧数据逐渐失去价值**：随着时间推移，旧数据的查询频率降低

## 按时间范围索引

如果我们为此种类型的文档建立一个超大索引，我们可能会很快耗尽存储空间。日志事件会不断的进来，不会停顿也不会中断。

我们可以使用 `scroll` 查询和批量删除来删除旧的事件。但这种方法非常低效。当你删除一个文档，它只会被标记为被删除。在包含它的段被合并之前不会被物理删除。

替代方案是，我们使用一个**时间范围索引**。你可以着手于一个按年的索引 (`logs_2014`) 或按月的索引 (`logs_2014-10`)。也许当你的数据变得十分繁忙时，你需要切换到一个按天的索引 (`logs_2014-10-24`)。删除旧数据十分简单：只需要删除旧的索引。

这种方法有这样的优点，允许你在需要的时候进行扩容。你不需要预先做任何艰难的决定。每天都是一个新的机会来调整你的索引时间范围来适应当前需求。

应用相同的逻辑到决定每个索引的大小上。起初也许你需要的仅仅是每周一个主分片。过一阵子，也许你需要每天五个主分片。这都不重要——任何时间你都可以调整到新的环境。

## 使用别名管理时间序列索引

别名可以帮助我们更加透明地在索引间切换。当创建索引时，你可以将 `logs_current` 指向当前索引来接收新的日志事件，当检索时，更新 `last_3_months` 来指向所有最近三个月的索引：

```json
POST /_aliases
{
  "actions": [
    { "add":    { "alias": "logs_current",  "index": "logs_2014-10" }},
    { "remove": { "alias": "logs_current",  "index": "logs_2014-09" }},
    { "add":    { "alias": "last_3_months", "index": "logs_2014-10" }},
    { "remove": { "alias": "last_3_months", "index": "logs_2014-07" }}
  ]
}
```

这样，写入操作始终使用 `logs_current` 别名，查询操作可以使用 `last_3_months` 别名来查询最近三个月的数据。

## 时间序列索引的最佳实践

### 1. 选择合适的索引粒度

- **按年索引**：适合数据量较小、查询跨度较长的场景
- **按月索引**：适合中等数据量、查询跨度中等的场景
- **按天索引**：适合数据量很大、查询主要集中在最近几天的场景

### 2. 合理设置分片数

- 根据单索引的数据量设置分片数
- 避免过多的小分片，也避免过少的大分片
- 考虑查询的并行度和写入的吞吐量

### 3. 使用索引模板

使用索引模板可以自动创建时间序列索引：

```json
PUT /_template/logs_template
{
  "index_patterns": ["logs_*"],
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 1
  },
  "mappings": {
    "properties": {
      "@timestamp": {
        "type": "date"
      },
      "message": {
        "type": "text"
      }
    }
  }
}
```

### 4. 定期清理旧数据

- 根据业务需求设置数据保留策略
- 定期删除超过保留期的索引
- 可以使用索引生命周期管理（如果支持）自动化这个过程

## 查询时间序列数据

查询时间序列数据时，通常需要：

1. **指定时间范围**：使用 `range` 查询过滤时间
2. **使用别名**：通过别名查询多个时间段的索引
3. **时间聚合**：使用 `date_histogram` 聚合分析趋势

示例：

```json
GET /last_3_months/_search
{
  "query": {
    "range": {
      "@timestamp": {
        "gte": "now-7d/d"
      }
    }
  },
  "aggs": {
    "per_hour": {
      "date_histogram": {
        "field": "@timestamp",
        "calendar_interval": "hour"
      }
    }
  }
}
```

## 小结

- 时间序列数据适合按时间范围组织索引，而不是使用单个大索引
- 使用别名可以透明地管理时间序列索引的切换
- 根据数据量和查询模式选择合适的索引粒度（年/月/天）
- 定期清理旧数据，避免存储成本过高
- 使用索引模板自动化索引创建过程

下一步可以继续阅读：

- [多租户建模](./multi-tenancy.md)
- [索引设置]({{< relref "/docs/operations/data-management/index-settings" >}})
- [别名（Aliases）]({{< relref "/docs/operations/data-management/aliases" >}})
- [数据生命周期与保留策略]({{< relref "/docs/best-practices/data-lifecycle.md" >}})

