腾讯云Elasticsearch索引生命周期管理原理及实践

2021-12-04 12:59:57 浏览数 (3)

本文将从三个方面介绍Elasticsearch索引生命周期管理的特性,首先会介绍ES索引生命周期管理的基本原理,其次会通过一个常见的日志场景来一步步配置索引生命周期管理,最后向大家介绍在日常的ES运维工作中遇到的关于索引生命周期管理常见的问题及解决方法。

一、ES索引生命周期管理之原理篇

1.1 ES索引规划基本原则

随着腾讯云上ES产品功能日益完善以及内核性能的逐步增强,有越来越多的客户选择使用腾讯云ES集群来做搜索推荐等业务及日志分析等工作。当我们在给云上集群做运维工作时,总结出了如下几条索引规划的原则:

  • 索引分片设置为数据节点的倍数
  • 单个分片大小控制在30GB-50GB
  • 单个节点总分片建议不超过1000个
  • 集群总分片个数控制在3万以内

之所以要把索引分片设置为数据节点的倍数,主要是能够充分利用每个数据节点的性能,避免热点问题出现。而当我们能够严格按照前3条原则来实施的时候,集群的总分片个数也就能够很好的进行控制了,不至于集群总分片个数太大,造成Master节点压力较大,从而导致集群的元数据更新时延较高,例如创建索引、更新mapping等操作。

另外,对于日志分析类场景来说,单个分片大小建议控制在30-50GB的原因主要有两个:第一,如果分片大小太大(如300GB以上),就会导致分片恢复和搬迁的速度很慢,而如果将分片设置的太小,又会使得集群的总分片个数膨胀的比较厉害,影响集群的稳定性;另外一个原因是ES集群对分片的doc数量做了限制,上限是21亿条,如果单个分片太大的话,很有可能会触发doc数量的限制,导致集群red或者丢数据的情况。

常规做法:当我们把这些索引规划原则分享给客户的时候,客户通常的做法是提前对写入的数据量进行评估,然后规划集群规模,如节点配置、节点数量和磁盘容量、设置好分片索引,然后通过Logstash等组件按天/小时进行切换索引。

问题和痛点:

1)随着时间的推移,集群的数据量会越来越大,总分片个数越来越多,集群写入性能受到影响;

2)遇到运营活动等情况,会导致当天的索引数据量是平常的数倍,影响集群写入;

3)历史索引和新索引使用同样的存储介质,集群成本较大。

为了解决以上问题,Elasticsearch在6.6版本推出了索引生命周期管理(ILM)。

图1. 索引生命周期管理矩阵示意图图1. 索引生命周期管理矩阵示意图

从图1可以看出,索引生命周期管理有两个最基本的概念,即Phase和Action。

ES将索引划分为四个阶段,分别的是Hot、Warm、Cold和Delete。在Hot阶段,主要处理时序数据的实时写入,承担热点数据的查询,支持Rollover Action;在Warm阶段,索引被设置为只读,不再写入和更新,主要是用来查询,如查询7天内的索引数据,支持Read-Only,Force-Merge,Shrink,Allocate等Action;等了在Cold阶段,索引同样不再更新, 查询更少,查询速度也会更慢,支持Frozen和Allocate Action;而到了Delete阶段的索引,则表示索引数据将被删除,集群内的磁盘空间迅速释放,通过会在删除索引前,先将索引备份到腾讯云COS中。下面分别介绍每个Phase支持的Action。

1.2 ILM Action:Rollover

Rollover是滚动的意思,能够实现根据索引的大小、文档数和创建时间自动切换到新的索引。当Rollover触发时,新的索引将会被创建,Alias别名自动指向新的索引,并将新索引的is_write_index属性设置为true。由于业务端在写入的时候并无法感知到何时触发Rollover,因此Rollover必须结合Alias一起使用。

设置索引Rollover conditions API:

代码语言:javascript复制
POST /nginx_log/_rollover
{
  "conditions": {
    "max_age":   "1d",
    "max_docs":  100000000,
    "max_size": "30gb"
  }
}

如下图2所示,客户端只通过别名nginx_log向集群中写入数据,刚开始别名指向的索引是nginx_log_000001,随着数据不断写入,nginx_log_000001触发了设置的rollover的condition后,如size满足了30gb,则会自动创建新的索引nginx_log_000002,并将别名指向nginx_log_000002,新写入的数据都会写入到nginx_log_000002中,而nginx_log_000001将被设置为只读。

图2. Rollover示意图图2. Rollover示意图

Rollover优势:

  • 避免单个索引过大
  • 提升索引写入性能
  • 方便管理索引数据

Rollover避坑指南:

合理设置触发 Rollover的Condition,否则容易导致集群中大量的小索引,使得总分片数过多,影响查询性能和集群稳定性。

1.3 ILM Action:Shrink

Shrink作为降低索引主分片的利器,Shrink API可以实现将集群中存量的主分片数量收缩到一个很小的值,新索引的主分片数量必须是原索引主分片数量的因子。例如原索引主分片数量为20,那么Shrink后的索引主分片数量可设置为 1、2、3、5、10。不能是其他的值。执行Shrink操作的前提条件如下:

  • 索引必须设置为只读
  • 索引的健康状态必须是Green
  • 所有的主分片或者副本分片都必须集中到一个节点上

首先将源索引设置为只读,并将分片汇集到某一个节点上:

代码语言:javascript复制
PUT nginx-log-000001
{
      "settings": {
           "index.routing.allocation.require._name": "11260372000288532", 
           "index.blocks.write": true
      }
}

然后对源索引执行Shrink操作,将源索引主分片收缩为1个:

代码语言:javascript复制
POST /nginx-log-000001/_shrink/nginx-log-000001_shrink
{
          "settings": {
           "index.number_of_shards": 1
    }
}

Shrink原理:

Shrink底层是通过硬连接来实现,如下图3所示,源索引原本有4个分片,将4哥分片组成的一份完整数据被汇聚到一个节点上后,每个分片号占用一个文件名称。

图3. Shrink实现原理示意图图3. Shrink实现原理示意图

当我们执行了Shrink API后,内核层面通过硬连接方式将4个文件夹映射为一个文件夹,文件夹名称为0号分片。

图4. 索引分片硬连接示意图图4. 索引分片硬连接示意图

当新索引的硬连接创建好之后,则可以将源索引执行删除了,最后的效果就如图5所示。

图5. Shrink完成后的索引分片示意图图5. Shrink完成后的索引分片示意图

需要注意的是:

1)如果集群所在的文件系统不支持硬连接操作,则需要将索引中所有的segment文件拷贝到新索引上;

2)如果节点使用了多路径的方式挂载了多盘,则也需要执行复制操作。因为系统层面无法在多个文件系统之间执行硬连接操作。

1.3 ILM Action:Frozen

ES集群有三种健康状态,分别是Green,Yellow和Red。索引层面也有三种状态,分别是Open、Frozen和Close状态。如图6所示:

图6. ES索引三种状态示意图图6. ES索引三种状态示意图
  • Open状态的索引表示索引是可搜索并可读写的,倒排索引等数据结构加载进内存,因此搜索性能最高;
  • Frozen状态的索引表示索引依然可以被搜索,但是无法被写入,且只占用磁盘空间,不占用内存空间,因此搜索较慢;
  • Close状态的索引表示索引不可读写,不占用内存空间,只占用磁盘空间。但是需要注意的是Close索引无法正常Allocate。

Frozen适合的场景是时序数据 冷热分离的架构,尤其是当集群规模比较大的时候,数据持续写入导致节点JVM很高频繁触发GC和读写熔断时,可以将部分历史索引冻结,以释放节点内存,保障集群稳定性。

Frozen缩冻结索引API:

代码语言:javascript复制
POST /nginx_log-000001/_freeze

解冻索引API

代码语言:javascript复制
POST /nginx_log-000001/_unfreeze

需要注意的是,当索引被冻结后,默认是搜索不出来的,即通过正常的Search查询不会去查询被冻结的索引。因此,如果期望在查询数据时,也能查到被冻结的索引数据,则需要在Search查询中添加ignore_throttled=false参数,如下所示:

代码语言:javascript复制
GET /nginx_log/_search?ignore_throttled=false

1.4 ILM Action:Allocate

Allocate可作用于warm和cold Phase。必须指定include、exclude、require中的一个选项。

  • include: 将索引分片分配到至少满足其中一个属性的节点上;
  • require: 将索引分片分配到满足所有属性的节点上;
  • exclude: 将索引分片分配到不包含其中任何一个属性的节点上。

例如,我们需要将索引从hot属性节点上迁移到warm属性节点上,则可以使用require,api如下:

代码语言:javascript复制
PUT nginx-log-000001/_settings
{
    "index.routing.allocation.require.temperature": "warm"
}

再比如我们需要将一个节点上的分片驱逐到其他节点上,则可以使用exclude,api如下:

代码语言:javascript复制
PUT _cluster/settings
{
  "persistent": {
    "cluster.routing.allocation.exclude._name": "160701276700027053211"
  },
  "transient": {
    "cluster.routing.allocation.exclude._name": "160701276700027053211"
  }
}

1.5 ILM Policy

Policy 是索引生命周期管理的发动机,有了Policy才能够真正的将索引的生命周期管理起来。让静态的索引联动起来。也就是说有了Policy就能够将独立的Action给串联起来,索引也只有关联了对应的Policy,才会按照Policy中定义的Action执行。

  • 新增索引:在索引模版中指定Policy 名称
  • 存量索引:在索引的settings中指定Policy 名称

如果我们是新创建了索引,后创建的Policy,那如果需要将存量的索引也关联Policy的话,则需要手动设置存量索引的settings,将存量索引与Policy进行关联:

代码语言:javascript复制
PUT nginx-log_000001/_settings
{
  "settings": {
    "index.lifecycle.name":"nginx-log-ilm-policy"
  }
}

如果想要查看Policy的详情,可以在Kibana上通过可视化界面查看,也可以直接通过API进行查看:

代码语言:javascript复制
GET _ilm/policy

而对于新增的索引,若想在创建时就自动关联Policy,则需要在索引模板中进行设置,如下面的API所示:

代码语言:javascript复制
PUT _template/nginx_log_template
{
  "order": 100,
  "index_patterns": [
    "nginx-*"
  ],
  "settings": {
    "index": {
      "lifecycle": {
        "name": "nginx-log-ilm-policy",
        "rollover_alias": "nginx-log"
      },
      "routing": {
        "allocation": {
          "require": {
            "temperature": "hot"
          }
        }
      },
      "refresh_interval": "30s",
      "number_of_shards": "5",
      "translog": {
        "sync_interval": "5s",
        "durability": "async"
      },
      "max_result_window": "65536",
      "unassigned": {
        "node_left": {
          "delayed_timeout": "5m"
        }
      },
      "number_of_replicas": "1"
    }
  }
}

我们通过在索引模版中指定index.lifecycle.name为定义的Policy名称,以及index.lifecycle.rollover_alias为Rollover的别名即可。

二、ES索引生命周期管理之实践篇

在上面的原理篇,我们逐一介绍了索引生命周期管理中几个比较重要的Action。以及每个Action适用的Phase,同时也介绍了Policy的定义,与索引的关联绑定等。下面我们从实践操作出发,来感受下索引生命周期管理的强大功能。

2.1 实践效果

我们以常用的日志场景为例,结合腾讯云ES集群冷热分离架构,来逐步实施如何使用ILM,我们想要达到的生产效果如下:

1)将新索引实时写入到ES集群中的热节点上,当索引达到特定条件后,数据滚动 (Rollover) 写入到新索引;

2)索引在热节点上滚动完成后在hot阶段停留三天后,迁移 (Allocate) 到温节点,即进入warm阶段;

3)warm阶段将索引设置为只读 (Read-only),将副本设置为0,将主分片个数缩小 (Shrink) 到1个;

4)索引在温节点上停留7天后(从滚动更新时算起),进入delete阶段;

5)索引阶段delete阶段后执行删除 (Delete) 操作。

2.2 操作步骤

总体上,实施ILM一般都会按照如下四步来操作:

1)创建Policy

2)创建索引模版

3)创建初始索引

4)通过别名写入数据

下面我们进入详细操作环节:

第一步:创建Policy

Kibana路径:Kibana– Management– Index Lifecycle Policys – Create policy

在Kibana上的操作详情如下图8-10所示:

图8. 创建ILM的policy:Hot phase图8. 创建ILM的policy:Hot phase
图9. 创建ILM的Policy:Warm phase图9. 创建ILM的Policy:Warm phase
图10. 创建ILM的Policy:Delete phase图10. 创建ILM的Policy:Delete phase

除了可以在Kibana上通过可视化界面来创建,我们也可以直接通过API来创建:

代码语言:javascript复制
PUT _ilm/policy/nginx-log-ilm-policy
{
    "policy": {
        "phases": {
            "hot": {
                "actions": {
                    "rollover": {
                        "max_age": "1d",
                        "max_size": "50gb",
                        "max_docs": 10000000
                    },
                    "set_priority": {
                        "priority": 100
                    }
                }
            },
            "warm": {
                "min_age": "2d",
                "actions": {
                    "allocate": {
                        "require": {
                            "temperature": "warm"
                        },
                        "number_of_replicas": 1
                    },
                    "forcemerge": {
                        "max_num_segments": 1
                    },
                    "shrink": {
                        "number_of_shards": 1
                    },
                    "set_priority": {
                        "priority": 50
                    }
                }
            },
            "delete": {
                "min_age": "7d",
                "actions": {
                    "delete": {}
                }
            }
        }
    }
}

表1. 定义ILM的Policy各阶段说明

参数

说明

Hot phase

该策略设置索引只要满足其中任一条件:数据写入达到50GB、使用超过1天、doc数超过1千万,就会触发索引滚动更新。此时系统将创建一个新索引,该索引将重新启动策略,而旧索引将在滚动更新后等待3天进入warm阶段。

Warm phase

索引进入warm阶段后,ILM会将索引副本设置为0,主分片收缩到1个。完成该操作后,索引将在7天(从滚动更新时算起)后进入delete阶段。

Delete phase

索引进入delete阶段,将在7天后(从滚动更新时算起)被删除

注意事项:

1)Policy名称一旦创建后便不可修改,各Phase中的Action则可以修改;

2)Kibana上指定的min_age最小单位是小时,不便于测试,而API方式则可以指定到秒和分钟。

第二步:创建索引模版

Kibana路径:Kibana – Management – Index Management – Index Templates

索引模板的创建在原理篇中我们已经做了介绍,这里最主要的就是需要关联上第一步创建的policy,已经Rollover的别名。 表2是在索引模板中定义的一些参数属性说明。

表2. 索引模板中定义的参数属性说明

参数

说明

index.routing.allocation.require.temperature

指定索引新建时分片的节点

index.lifecycle.name

指定关联的ILM策略名称

index.lifecycle.rollover_alias

指定rollover的别名

index.refresh_interval

指定索引refresh周期

index.number_of_shards

指定索引的主分片数

index.number_of_replicas

指定索引的副本数

第三步:创建初始索引

如果我们在Hot phase定义了Rollover,则必须要手动创建一个初始索引,并且将该初始索引与索引别名进行关联,创建初始索引使用如下api:

代码语言:javascript复制
PUT nginx-log-000001
{
"aliases": {
    "nginx-log":{
       "is_write_index": true
        }
      }
}

is_write_index设置为true的意思就是表明当前的初始索引nginx-log-000001是可以写入的索引,即当我们通过别名nginx-log向集群中写入数据时,数据后直接写入到实体索引nginx-log-000001中。

在生产环境中,我们通常希望在索引名称上加上当前日期,以快速判断该索引创建的具体时间。例如通过下面的api创建的初始索引就是直接带上了年月日,索引名称中的转义字符如图11-12所示。

代码语言:javascript复制
PUT 
{
"aliases": {
    " nginx-log ":{
       "is_write_index": true
        }
      }
}

图11. ES索引名称转义说明图11. ES索引名称转义说明
图12. ES索引名称日期表达式图12. ES索引名称日期表达式

在创建初始索引过程中,需要注意下面三点:

  • 使用rollover滚动索引时候必须先手动创建初始索引;
  • 初始索引必须是以000001结尾,长度6位,否则策略不生效,滚动的索引名会自动 1;
  • 如果需要在索引名称上以日期进行标记,则可以使用{now/d},具体的表达式可以参考官方文档。

第四步:通过别名写入数据

当我们创建好初始索引后,即可以直接通过索引别名向集群中写入数据了,我们使用如下api模拟写入几条数据:

代码语言:javascript复制
POST _bulk
{ "create" : { "_index" : "nginx-log", "_id" : "1" } }
{ "message" : "11" }
{ "create" : { "_index" : "nginx-log", "_id" : "2" } }
{ "message" : "22" }
{ "create" : { "_index" : "nginx-log", "_id" : "3" } }
{ "message" : "33" }
{ "create" : { "_index" : "nginx-log", "_id" : "4" } }
{ "message" : "44" }
{ "create" : { "_index" : "nginx-log", "_id" : "5" } }
{ "message" : "55" }
{ "create" : { "_index" : "nginx-log", "_id" : "6" } }
{ "message" : "66" }
{ "create" : { "_index" : "nginx-log", "_id" : "7" } }
{ "message" : "77" }

当我们向集群中写入一些数据后,可以看到索引中已经有了7条doc。

图13. 索引管理列表图13. 索引管理列表

如果我们持续往集群中写入数据,则会不停的滚动创建新的索引,并且会按照我们在policy中定义的那样,滚动完成后1天自动迁移到温节点,然后在温节点上自动执行Shrink操作、去除副本和ForceMerge、Read-Only等操作,然后再在温节点上停留7天后从集群中删除,即进入Delete Phase。

三、ES索引生命周期管理注意事项

通过我们对腾讯云ES客户大量的集群运维经验来看,总结整理了如下5点需要注意的问题:

3.1 min_age问题

  • 如果 Policy 中没有设置 rollover,则每个阶段的 min_age 是以索引的创建时间为开始;
  • 如果 Policy 中设置了 rollover Action,则每个阶段的 min_age 是从索引执行完 rollover 后开始计算;
  • 索引从 snapshot 中恢复到集群后,如果索引关联了 Policy,则 min_age 同上,不是以索引恢复后的时间。

尤其是第三点,我们经常收到客户的工单反馈,从快照中恢复出来的索引很快就从集群中又被删除了,也不是人为操作。经过详细分析发现是由于该索引关联了ILM的Policy,而在该策略中定义了Delete Phase,也就是说在删除之前,会将索引备份到COS,备份成功后再进入到Delete阶段。因此在快照备份的时候,此时索引还处于Delete阶段之前。然后从快照总恢复到集群后,还是会从上一次备份前的min_age进行执行,当执行到Delete Phase后就会再次删除索引了。

3.2 ILM的执行周期

ILM 是周期性的执行,默认 10min触发一次。因此当索引满足了某一个 Condition 后,并不会立马执行 Action。如设置的 max_doc 为 10,可能会索引的 doc 超过了 10 后才开始 Rollover。可以通过下面的 API 进行调整执行周期:

代码语言:javascript复制
PUT _cluster/settings
{
    "transient": {
        "indices.lifecycle.poll_interval":"10m"
    }
} 

通常我们在测试环境中会调用上面的api将interval设置的很小,如1分钟。但是在生产环境不建议将该值设置得过小,避免时间间隔太短给集群造成较大负载;另外还有一点需要注意的就是:当到达一个执行周期后,检查到了索引满足了某一个阶段的Condition,但是由于前一个阶段还没有执行完,则不会执行下一个阶段。举个例子,当我们设置的策略是索引在滚动完成后1天开始迁移到温节点,2天删除,但是由于在迁移到温节点过程中,由于迁移速度和并发度很低,可能迁移了3天。这时候已经满足了删除的Condition了,但是由于还没有完成Warm Phase,因此即使满足删除条件,也不会执行删除动作,而是会等Warm阶段执行完成后才开始执行下一步操作。

3.3 Policy更新及删除问题

关于ILM的Policy问题,这里重点需要关注三个问题:

1)Policy的名称一旦确定后将不可更改

2)Policy中定义的各Action均可更改

Policy更新后,对于新增的索引会立即使用最新版本的Policy;

对于正处于Policy执行中的索引,只有当前阶段的Action执行完成后,进入到下一个阶段的Action才会使用最新版本的Policy (ES会将当前执行的phase定义信息缓存到索引元数据中);

对于已经完整执行了Policy的存量索引,更新了Policy后不会再次执行。 例如,原Policy只包含了从热节点迁移到温节点的Action,索引在完成了迁移后,状态即为completed,这时候再在Policy中加入Delete的Action,则历史索引将不会被删除。

解决办法:

1) 移除再关联

 先将索引的Policy 移除:

代码语言:javascript复制
POST {index_name}/_ilm/remove

再重新关联Policy(适合索引较少的场景)。

代码语言:javascript复制
PUT {index_name}/_settings
{
   "lifecycle.name": "ilm_policy"
}

2)直接重启ILM

第二种解决方法就是直接重启下ILM,适合索引较多的场景。

代码语言:javascript复制
POST _ilm/stop
POST _ilm/start

第三个需要关注的Policy问题就是Policy的删除

3)Policy需要等所有关联的索引都解关联后,才可被删除

3.4 从snapshpt中恢复索引的潜在问题

如果之前备份到COS中的索引关联了Policy,在恢复到集群中后,ILM依然会按照关联的Policy执行,并且min_age是从索引创建的时间或者从rollover滚动后的时间开始计算,而不是从快照中恢复完成的时间计算,如图14所示。

图14. ILM COS备份示意图图14. ILM COS备份示意图

例如,如果我们之前给索引关联的Policy是有Delete的Action,那么在索引备份完成后Policy就会直接从Delete阶段执行,索引将再次被删除。也就是说备份之前是在哪个阶段,恢复后还是在那个阶段。 如果希望索引从快照中恢复后不再自动执行ILM,则可以按照下面的步骤来配置:

1)先停止ILM:

代码语言:javascript复制
POST _ilm/stop

2)开始执行快照的恢复工作:

代码语言:javascript复制
POST /_snapshot/my_backup/snapshot_name/_restore

3)对恢复完成后的索引关联的Policy进行修改,可以移除Policy,或者更新Policy:

代码语言:javascript复制
POST {index_name}/_ilm/remove

4)重新启动ILM:

代码语言:javascript复制
POST _ilm/start

5)查看ILM的状态:

代码语言:javascript复制
GET _ilm/status

或者,直接在快照恢复的API中忽略Policy的相关配置:

代码语言:javascript复制
POST /_snapshot/repository_name/snapshot_name/_restore
{
  "ignore_index_settings":"index.lifecycle.name,index.lifecycle.rollover_alias"
}

3.5 ILM执行失败问题处理

当ILM在执行Policy的特定阶段时出错,ILM将会将该索引执行状态设置为”error”。然后停止Policy的进一步执行。例如,当我的Policy中有把索引的主分片最终Shrink到2个分片。然后我创建了一个新的索引只有一个主分片。但是这个索引也关联了该Policy,那在执行到该索引的时候就会报错。导致ILM执行不下去。可以通过 explain API来查看。找到执行失败的原因后,解决相关的问题,然后再重试ILM即可。

查看索引的ILM执行详情:

代码语言:javascript复制
GET {index_name}/_ilm/explain

重试索引的ILM执行任务:

代码语言:javascript复制
POST {index_name}/_ilm/retry 

这里重试ILM的时候需要注意,必须是索引名称,不可以是索引别名,否则重启ILM不生效) (7.X版本会自动重试)

四、ILM索引生命周期管理总结

ILM分为四个Phase,分别是Hot、Warm、Cold和Delete。在Hot阶段支持Rollover Action,在Warm阶段支持Read-Only、Force-Merge、Shrink、Allocate等Action,在Cold阶段支持Frozen、Allocate等Action,在Delete阶段支持Delete的Action。

通常我们实施ILM的时候,一般通过如下四步进行:

第一步:创建Policy

第二步:创建索引模版

第三步:创建初始索引

第四步:通过别名写入数据

在本文中,我们也详细介绍了很多ILM相关的API,现汇总如下:

ILM相关API

API说明

PUT _ilm/policy/<policy_id>

创建ILM的Policy

GET _ilm/policy<policy_id>

获取ILM的Policy

DELETE _ilm/policy/<policy_id>

删除特定的Policy

GET _ilm/status

获取ILM的状态

POST _ilm/start

启动ILM

POST _ilm/stop

停止ILM的执行

GET <index_name>/_ilm/explain

获取ILM的执行详情

POST <index_name>/_ilm/retry

重试某个索引的ILM执行

0 人点赞