一、为什么你的OpenSearch总是喊"磁盘不够用"

最近是不是经常收到OpenSearch的磁盘告警?就像你的手机总在提醒"存储空间不足"一样让人头疼。其实大多数情况下,问题都出在那些堆积如山的无用索引上。

想象一下你的衣柜:常年不穿的衣服占着空间,真正需要的衣服反而没地方放。OpenSearch的索引也是同理——测试用的临时索引、过期的日志索引、命名不规范的神秘索引...它们都在悄无声息地吃掉你的磁盘空间。

# 技术栈:OpenSearch Python客户端
# 示例:列出所有占用空间超过1GB的索引
from opensearchpy import OpenSearch

client = OpenSearch(
    hosts = [{'host': 'localhost', 'port': 9200}],
    http_auth = ('admin', 'admin')
)

# 获取所有索引的磁盘占用情况
stats = client.indices.stats(index='*')
for index, data in stats['indices'].items():
    size_in_gb = data['total']['store']['size_in_bytes'] / (1024 ** 3)
    if size_in_gb > 1:  # 筛选大于1GB的索引
        print(f"索引 {index} 占用空间: {size_in_gb:.2f}GB")

二、精准识别"垃圾索引"的三把利器

1. 按时间戳揪出过期索引

很多索引名包含日期(如logs-2023-01),这类索引最容易清理:

# 技术栈:OpenSearch Python客户端
# 找出30天前的日志索引
from datetime import datetime, timedelta

cutoff_date = (datetime.now() - timedelta(days=30)).strftime("%Y.%m.%d")
old_indices = [index for index in client.indices.get("*") 
               if index.startswith("logs-") and 
               index.split("-")[-1] < cutoff_date]
print("待清理的过期索引:", old_indices)

2. 用索引模板识别测试数据

开发人员常创建test_、temp_开头的索引:

# 识别所有测试索引
test_patterns = ['test_', 'temp_', 'demo_']
test_indices = [index for index in client.indices.get("*") 
                if any(index.startswith(p) for p in test_patterns)]
print("测试索引列表:", test_indices)

3. 通过文档数找出"僵尸索引"

有些索引虽然存在但几乎没有数据:

# 找出文档数少于10的索引
low_doc_indices = [
    index for index, data in client.indices.stats(index='*')['indices'].items()
    if data['primaries']['docs']['count'] < 10
]
print("低文档数索引:", low_doc_indices)

三、安全删除索引的黄金法则

直接删除索引就像永久删除文件——必须慎之又慎。以下是经过实战检验的删除流程:

  1. 先备份再操作
# 创建索引快照(需提前配置快照仓库)
client.snapshot.create(
    repository='my_backup',
    snapshot='before_cleanup',
    body={"indices": ",".join(old_indices)}
)
  1. 关闭索引观察影响
# 分批次关闭索引
for index in old_indices[:5]:  # 先试前5个
    client.indices.close(index=index)
    print(f"已关闭 {index},请观察系统运行状态")
  1. 最终删除确认
# 二次确认后删除
confirm = input(f"确认删除 {len(old_indices)} 个索引?(yes/no): ")
if confirm.lower() == 'yes':
    client.indices.delete(index=",".join(old_indices))

四、预防胜于治疗:索引生命周期管理

OpenSearch自带的ISM(Index State Management)功能可以自动清理旧索引:

PUT _plugins/_ism/policies/auto_delete_policy
{
  "policy": {
    "description": "30天后自动删除日志索引",
    "default_state": "hot",
    "states": [
      {
        "name": "hot",
        "actions": [
          {"rollover": {"min_docs": 1000000}}
        ],
        "transitions": [
          {"state_name": "delete", "conditions": {"min_index_age": "30d"}}
        ]
      },
      {
        "name": "delete",
        "actions": [
          {"delete": {}}
        ]
      }
    ]
  }
}

五、那些年我们踩过的坑

  1. 误删生产索引
    某团队曾因通配符使用不当误删customer_*索引,导致服务中断2小时。建议在删除前先用_count API验证内容:

    for index in candidate_indices:
        doc_count = client.count(index=index)['count']
        print(f"{index} 包含 {doc_count} 条真实数据")
    
  2. 忽略索引依赖关系
    有些索引被仪表盘或报警规则引用。删除前检查关联:

    # 检查索引是否被Alerting使用
    alerts_using_index = client.search({
        "query": {"term": {"monitor.indices": "target_index"}}
    }, index='.opensearch-alerting-config')
    
  3. 未考虑副本恢复开销
    大规模删除后集群会重新分配副本,可能引发性能波动。建议在低峰期操作,并临时调整副本数:

    # 删除前设置副本为0
    client.indices.put_settings(index="*", body={"number_of_replicas": 0})
    

六、终极清理策略组合拳

结合多个维度制定清理策略效果最佳:

def should_delete(index):
    # 条件1:超过保留期限
    is_old = index.endswith((datetime.now() - timedelta(days=90)).strftime("%Y.%m.%d"))
    # 条件2:属于测试数据
    is_test = any(index.startswith(p) for p in ['test_', 'temp_'])
    # 条件3:文档极少且近期无查询
    stats = client.indices.stats(index=index)
    is_inactive = (stats['indices'][index]['total']['docs']['count'] < 100 and
                   stats['indices'][index]['total']['search']['query_total'] == 0)
    return is_old or is_test or is_inactive

to_delete = [index for index in client.indices.get("*") if should_delete(index)]

七、扩展技巧:空间回收终极手段

当常规清理仍不满足需求时,可以考虑:

  1. 强制合并分段
# 减少Lucene分段数量
client.indices.forcemerge(index="*", max_num_segments=1)
  1. 重建索引压缩数据
# 创建压缩版新索引
client.reindex({
    "source": {"index": "old_index"},
    "dest": {"index": "new_index"},
    "script": {
        "lang": "painless",
        "source": "ctx._source.remove('unused_field')"
    }
})
  1. 冷热数据分层存储
PUT _template/cold_data_template
{
  "index_patterns": ["*cold"],
  "settings": {
    "index.routing.allocation.require.box_type": "cold"
  }
}

八、总结:给不同规模集群的建议

  • 小型集群(<10节点):每周手动运行清理脚本
  • 中型集群(10-50节点):配置ISM自动策略+月度人工审核
  • 大型集群(>50节点):建立完整的索引治理规范,配合定时自动化任务

记住,良好的索引管理习惯就像定期整理房间——虽然需要投入时间,但最终会让你的OpenSearch"住"得更舒适!