背景
前段时间客户根据看到的ES日志报了索引无法创建的Bug,研发工作完成差不多后,开始梳理前线客户Bug,调研后才发现原来是Dangling索引的问题;这篇文档算是对Dangling Indices知识的简短梳理。ES(5.6.4)日志的记录如下:
[2020-09-17T01:51:05,715][WARN ][o.e.g.DanglingIndicesState] [elasticsearch1] [[filebeat-2020.08.11/Uwim4o6nREed8_It7vBQ7A]] can not be imported as a dangling index, as index with same name already exists in cluster metadata
上述WARN信息,来源于ES的DanglingIndicesState类的findNewDanglingIndices(...)方法,如下:
/**
* Finds new dangling indices by iterating over the indices and trying to find indices
* that have state on disk, but are not part of the provided meta data, or not detected
* as dangled already.
*/
Map<Index, IndexMetaData> findNewDanglingIndices(final MetaData metaData) {
final Set<String> excludeIndexPathIds = new HashSet<>(metaData.indices().size() + danglingIndices.size());
for (ObjectCursor<IndexMetaData> cursor : metaData.indices().values()) {
excludeIndexPathIds.add(cursor.value.getIndex().getUUID());
}
excludeIndexPathIds.addAll(danglingIndices.keySet().stream().map(Index::getUUID).collect(Collectors.toList()));
try {
final List<IndexMetaData> indexMetaDataList = metaStateService.loadIndicesStates(excludeIndexPathIds::contains);
Map<Index, IndexMetaData> newIndices = new HashMap<>(indexMetaDataList.size());
final IndexGraveyard graveyard = metaData.indexGraveyard();
for (IndexMetaData indexMetaData : indexMetaDataList) {
if (metaData.hasIndex(indexMetaData.getIndex().getName())) {
logger.warn("[{}] can not be imported as a dangling index, as index with same name already exists in cluster metadata",
indexMetaData.getIndex());
} else if (graveyard.containsIndex(indexMetaData.getIndex())) {
logger.warn("[{}] can not be imported as a dangling index, as an index with the same name and UUID exist in the " +
"index tombstones. This situation is likely caused by copying over the data directory for an index " +
"that was previously deleted.", indexMetaData.getIndex());
} else {
logger.info("[{}] dangling index exists on local file system, but not in cluster metadata, " +
"auto import to cluster state", indexMetaData.getIndex());
newIndices.put(indexMetaData.getIndex(), indexMetaData);
}
}
return newIndices;
} catch (IOException e) {
logger.warn("failed to list dangling indices", e);
return emptyMap();
}
}
从WARN信息中可以得到两点信息:
- 存在Dangling indices
- 因为重名的问题,ES无法向正常的处理Dangling indices那样,处理当前的Dangling indices
结合ES集群重现上述问题的操作步骤:
- 搭建1master与1data的ES集群
- 创建索引(假定为my_index),并写入少量数据
- 使用cp将data节点的nodes/0/indices路径下my_index的目录拷贝走
- 对my_index执行DELETE操作(或手工删除master、data节点的data目录中的数据,然后重启master与data)
- 重复第2步操作,并将原my_index目录拷贝到data节点下的nodes/0/indices目录中
- 重启data节点
- 这时data节点就会报出上述WARN信息
Dangling Indices
Dangling indices(悬空索引)指数据存储在一个或多个节点磁盘上但当前集群的clusterMetaData中并不包含这些索引信息。ES数据节点的启动会首次从dataPath路径下加载这些索引数据,然后master能够获取到这些索引数据。Dangling indices通常是由以下几种情况产生的:
- 当有数据结点处于offline状态,而此时通过DELETE操作删除索引,删除的索引数大于集群设置的tombstones数量(默认为500),然后该数据节点启动并重新加入集群
-- DELETE操作将索引信息从clusterMetaData中删除,而索引的真实数据在磁盘中 - 可能是因为原始集群丢失了其所有主节点的原因,原始集群中的某个节点添加到另一个集群中
添加到另一个集群的节点,数据真实存储在节点中,但新集群的clusterMetaData中不包含这些索引数据的信息 - 对于集群的数据节点来说,可能是从备份中还原了老的、旧的索引文件
- 集群丢失了所有主节点,并且从备份中还原了这些主节点,但是备份中的主节点不包含这些索引信息
-- 同样是节点存储着索引数据,但主节点维护的clusterMetaData中不包含这些索引信息
分析源码可知,ES对Dangling Indices的处理策略是首先会去寻找并判定数据节点中的哪些索引属于Dangling状态,然后组装好这些Indices,最后将这些Dangling Indices发送给master等待着后续的Allocation操作。上述的findNewDanglingIndices(...)函数主要职责就是寻找并判定处于Dangling状态的索引,可以看到当一个索引本身是Dangling Index,但在判定过程中发现clusterMetaData中已经存在与当前Dangling索引完全一样名称的索引时,则会报出WARN:can not be imported as a dangling index...;即尽管是Dangling indices,但因为存在与clusterMetaData中重名的缘故,因此ES自身不能像处理正常Dangling indices那样来处理此索引。ES会选择放弃这类Dangling indices的处理
对于这些重名的Dangling indices,查阅了一些资料,发现并没有比较好的方式来处理;ES官方讨论区推荐的方式为要么删除Dangling indices;要么是删除ES中已经存在的与Dangling状态同名的索引,别的也没有比较好的方式;小结
总结下来,处理重名的Dangling indices的方式主要有如下三种:
- 删除存储在磁盘上的Dangling indices(一定的数据丢失)
- 删除已存储在ES中的重名索引(一定的数据丢失)
- 对已存储在ES中的索引进行rename操作,然后由ES正常处理Dangling indices(操作上繁琐一些)
其实最好的方式应该是尽可能的规避这个问题的发生,通过调研客户环境发现其ES集群非常不稳定,经常会出现节点卡死并带有重启的操作;所以对此处理的策略是依据处理的数据量做好ES集群的规划,包括master、data节点的部署划分、依据ES能力进行正常的写入与搜索等操作。尽可能减少或避免繁重的且会导致ES卡死的操作,进而避免ES节点频繁的重启发生。