Grafana Loki 日志删除实战指南:从最小配置到取消一条发错的删除请求
【免费下载链接】lokiLike Prometheus, but for logs.项目地址: https://gitcode.com/GitHub_Trending/lok/loki
Grafana Loki 支持按流、时间窗口和可选行过滤器删除日志条目,删除请求由 compactor 承载:提交后先进入可反悔的取消期,之后数据才从对象存储中物理移除。这是一份 Loki 日志删除的落地指南。
什么情况下你真正需要删日志
日志只增不删的假设,在三种场景下会失效:
- 合规要求:GDPR 等法规要求删除特定主体关联的日志,"不再展示"不够,必须让数据物理消失;
- 隐私数据误写:密钥、token、个人信息被打进了日志,留在对象存储里就是长期风险;
- 容量回收:某批测试数据、某个事故期间的爆量日志占据了可观的存储费用。
但"删"在 Loki 里不是像rm那样直接擦掉一块数据。Loki 把日志按流(stream)打包成一个个chunk(压缩后的日志块,是对象存储的最小读写单位),索引只记录"哪个流的数据在哪些 chunk 里"。因此带行过滤器的物理删除,本质上是把受影响的 chunk 读出来、剔除匹配行、重建压缩块、再写回对象存储并更新索引;而不带行过滤器的删除(整条流的时间窗口),则可以直接废弃整块 chunk。
另外先明确一个索引前提:日志条目删除在TSDB 索引下完整支持;旧的 BoltDB-Shipper 索引也能用,但它计划在 Loki 4.0 移除,新集群请直接用 TSDB。
快速上手:三个前置条件加一份最小配置
删除能力不默认开启,三个条件缺一不可:
- 打开 retention 开关:
compactor.retention_enabled: true。删除 API 端点只有在它开启时才注册,否则所有请求都会得到 400Retention is not enabled(见 pkg/loki/modules.go 中的端点注册逻辑)。 - 配置删除请求存储桶:
delete_request_store必须非空。开启 retention 却没填它,compactor 启动校验会直接报错退出(pkg/compactor/config.go 中Validate())。 - 租户的
deletion_mode不是disabled:默认值就是filter-and-delete,所以前两条满足后,删除对所有租户天然生效。
⚠️开 retention 前先给对象存储桶打开版本控制(versioning)。retention 和删除都是不可逆的重操作,桶的版本历史是你唯一的后悔药。如果只想开删除能力、并不打算按保留期清理数据,把limits_config.retention_period设为0s即可。
一份最小可用配置:
limits_config: retention_period: 0s # 不强制执行 retention,仅开启删除能力 deletion_mode: filter-and-delete compactor: working_directory: /var/loki/compactor retention_enabled: true delete_request_store: gcs://your-bucket/delete-requests # 换成你的对象存储 delete_request_store_db_type: boltdb delete_request_cancel_period: 24h delete_max_interval: 24h与删除直接相关的 compactor 配置项及默认值(源码见 pkg/compactor/config.go):
| 配置项 | 默认值 | 作用 |
|---|---|---|
retention_enabled | false | 删除 API 的总开关 |
delete_request_store | 空(启用 retention 后必填) | 存放删除请求的对象存储桶 |
delete_request_store_key_prefix | index/ | 桶内路径前缀 |
delete_request_store_db_type | boltdb | 本地数据库引擎,可选sqlite |
backup_delete_request_store_db_type | 空 | 迁移用的备份库(当前仅支持 boltdb) |
delete_request_cancel_period | 24h | 取消期:请求超过该时长后数据才会被真正删除 |
delete_max_interval | 24h | 带行过滤器的删除请求分片跨度上限 |
delete_batch_size | 70 | 每个周期最多处理的删除请求数 |
retention_delete_delay | 2h | 取消期之后、chunk 真正删除前的额外缓冲 |
retention_delete_worker_count | 150 | 删除 chunk 的并发协程数 |
apply_retention_interval | 0s | retention 生效周期;为 0 时自动对齐 compaction 周期并加最多 10 分钟抖动,避免两者抢同一时刻 |
模式选型:deletion_mode 三种取值怎么选
deletion_mode定义在limits_config中,支持全局默认值加按租户覆盖(覆盖写在运行时配置文件里,取值来自 pkg/validation/limits.go)。它回答一个问题:匹配删除请求的日志,是"看不见"还是"不存在"?
按你的诉求对号入座:
- 只要"看不见",物理数据还要留着(例如法务要求隐藏,但你自己想保留取证副本)→ 选
filter-only。查询路径会按需过滤掉匹配行,对象存储里的 chunk 原封不动。 - 必须物理消失(GDPR 类合规)→ 选
filter-and-delete(默认值)。查询时过滤,同时 compactor 会把数据从存储中移除。 - 共享集群里不想让某个租户碰删除→ 对该租户覆盖为
disabled。此时它的删除 API 请求会被拒绝(validDeletionLimit检查不通过,见 pkg/compactor/deletion/util.go)。
两个容易踩的点:
- 拼写错误会被拒绝。
ParseMode只认disabled、filter-only、filter-and-delete三个值,写错一个字母,配置校验直接失败(pkg/compactor/deletionmode/mode.go)。 - 端点注册与 retention 强绑定,与 deletion_mode 无关。
retention_enabled为 false 时,端点压根不存在,handler 为 nil,任何租户访问都得到 400。先开 retention,再用deletion_mode做租户级收放,这是正确的控制顺序。
实操:提交、查询、取消一条删除请求
删除端点挂在 compactor 上,路径统一为/loki/api/v1/delete,POST 提交、GET 查询、DELETE 取消(注意取消用的是 HTTP DELETE 方法)。所有操作都需要租户头X-Scope-OrgID。
提交删除请求。参数规则来自 pkg/compactor/deletion/request_handler.go:
query(必填):LogQL 流选择器,可带行过滤器,如{cluster="prod"} |= "ERROR"。提交时就会完整解析并构建 pipeline,非法的正则、写错的ip()模式会当场返回 400,而不是留到执行期爆炸;start/end(必填):Unix 秒(必须恰好 10 位数字)或 RFC3339 格式。两条硬规则:不允许删除未来时间(deletes in the future are not allowed),且 start 必须严格小于 end;max_interval(可选):请求更细的分片,最小1s,单位只认s/m/h,不能大于delete_max_interval,也不能大于待删时间窗口本身。
curl -X POST -H "X-Scope-OrgID: tenant1" \ "http://loki-compactor:3100/loki/api/v1/delete?query=%7Bcluster%3D%22prod%22%7D&start=1704067200&end=1704153600"成功返回204 No Content,响应头X-Delete-Request-ID就是这条删除请求的 ID,取消时要用到。
查询删除请求。返回该租户全部请求的 JSON 数组,按创建时间排序,内部字段UserID、SequenceNum会被抹掉。
curl -H "X-Scope-OrgID: tenant1" \ "http://loki-compactor:3100/loki/api/v1/delete" # 只看在查询时参与过滤的请求: curl -H "X-Scope-OrgID: tenant1" \ "http://loki-compactor:3100/loki/api/v1/delete?for_querytime_filtering=true" # 按时间重叠过滤(start/end 同样支持 Unix 秒或 RFC3339): curl -H "X-Scope-OrgID: tenant1" \ "http://loki-compactor:3100/loki/api/v1/delete?start=1704067200&end=1704153600"被分片的大请求在列表里会合并回一条展示,状态按已完成子请求比例渲染为Received、N% Complete或Processed(合并逻辑mergeDeletes)。
取消一条发错的删除请求。常规取消只在取消期内、且请求还没开始处理时有效:
curl -X DELETE -H "X-Scope-OrgID: tenant1" \ "http://loki-compactor:3100/loki/api/v1/delete?request_id=<REQUEST_ID>"三种会被 400 挡回的情况:请求 ID 不存在(404)、状态已是Processed、请求已部分完成或创建时间超过delete_request_cancel_period(默认 24h)。后两种可以加force=true强制取消:
curl -X DELETE -H "X-Scope-OrgID: tenant1" \ "http://loki-compactor:3100/loki/api/v1/delete?request_id=<REQUEST_ID>&force=true"另有两个缓存辅助端点,与删除联动:GET /loki/api/v1/cache/generation_numbers查租户的缓存代数;POST /loki/api/v1/cache/generation_numbers/increase手动递增代数、令该租户的查询结果缓存失效(例如回放历史数据后)。删除处理完成时代数会自动递增,保证查询不会命中已删数据的旧缓存。
幕后机制:一条删除请求提交后发生了什么
时间线比直觉中慢很多,这是刻意的:
- 提交后先躺平。请求进入存储,状态
Received。只有当请求年龄超过delete_request_cancel_period(默认 24h),它才会进入实际删除阶段——这 24 小时就是给你留的取消窗口。 - compactor 周期扫描。每个 retention 周期(
apply_retention_interval,默认与 compaction 周期一致并带抖动)检查未处理请求,每个周期最多处理delete_batch_size(默认 70)条。 - 分片只针对带行过滤器的请求。流选择器本身不带过滤器时,整个时间窗口就是一条请求,不拆分。带行过滤器时,
buildRequests会把它切成每段不超过delete_max_interval的子请求,且相邻子请求刻意保留少量时间重叠而不是严丝合缝——因为端点是闭区间语义,精确衔接会在边界留 1ms 缝隙漏删日志。 - 重建与写回。
filter-and-delete模式下,涉及删除的 chunk 会被读出、剔除匹配行、重写压缩块、上传回对象存储并更新索引;带行过滤器的删除由删除清单(deletion manifest,deletion_manifest_builder.go)驱动。整个动作还要再等retention_delete_delay(默认 2h)的缓冲。 - 横向扩展。
horizontal_scaling_mode支持 main/worker 分工,删除作业经 jobqueue 分发给 worker(pkg/compactor/deletion/job_runner.go)。
执行进度由 delete_requests_manager.go 中的DeleteRequestsManager持续推进,指标定义在 pkg/compactor/deletion/metrics.go,包括deleteRequestsReceivedTotal、deleteRequestsProcessedTotal、deletedLinesTotal、deletionFailures等,前缀loki_compactor_,是观察积压和失败的第一现场。
⚠️带行过滤器的删除是 compactor 最耗资源的操作之一:每个相关 chunk 都要读出来、剔除匹配行、再重写回对象存储,CPU 和 IO 双密集。多租户、大批量的场景请把删除工作分散到多个 compactor 实例(横向扩展),别指望单机扛。
请求存储与迁移:boltdb 与 sqlite 双写思路
delete_request_store和delete_request_store_db_type是两个不同维度:前者是删除请求数据文件所在的对象存储位置,后者是 compactor 本地使用的数据库引擎(默认boltdb,可切sqlite)。两套实现分别在 delete_requests_db_boltdb.go 和 delete_requests_db_sqlite.go。
想从 boltdb 迁到 sqlite 又不丢请求,用备份库双写:
compactor: delete_request_store_db_type: sqlite # 目标引擎 backup_delete_request_store_db_type: boltdb # 迁移期间同时写老引擎backup_delete_request_store_db_type当前只支持boltdb(见 flag 说明),语义就是"迁移期间新请求同时写进备份库,老数据仍可被读取"。等迁移确认无遗漏后去掉备份配置即可。这也是为什么保留一个已开版本控制的delete_request_store桶格外重要:本地引擎迁移出问题,桶里的请求数据才是兜底。
避坑清单与运维检查项
上线前逐项过一遍:
- ⚠️对象存储桶版本控制已开启——包括日志数据桶和
delete_request_store桶。误删之后这是唯一能恢复的路径。 - 先用
filter-only观察,再切filter-and-delete。同一批流、同一时间窗口,先让删除请求以纯过滤模式跑几天,用查询结果核对命中范围是否符合预期,再物理落地。 - 控制分片粒度。大批量带过滤器的删除,用
max_interval把单次分片压小(如2h),避免一条请求横跨数天导致执行时间失控;注意它只能小于等于delete_max_interval。 - 取消期别调太短。flag 注释建议
delete_request_cancel_period至少 24h——调成 1h 看似"删得快",实则是把操作错误的缓冲时间压缩到几乎没有。 - 盯指标。
loki_compactor_deletion_*家族重点看请求积压(received 与 processed 的差值走势)和deletionFailures;持续积压就横向扩 compactor。 - 缺 chunk 时的逃生门要慎用。
deletion_ignore_missing_chunks(默认false)可在"索引指向的 chunk 在对象存储里丢失"时跳过该 chunk、不让整条删除请求卡死,被跳过的数量计入loki_compactor_deletion_missing_chunks_total。但源码注释警告得很直白:只有在确认真正丢失后才应临时开启——若 chunk 其实还在却被跳过,它承载的数据将永远不会被删掉,而请求却会显示已完成。
Loki 的日志删除把"强操作"拆成了可反悔的两段:提交到执行之间隔着取消期,执行之前还隔着 retention 与删除延迟的缓冲。把deletion_mode、分片粒度和监控接好,这套能力就能安全地服务于合规与容量需求。
【免费下载链接】lokiLike Prometheus, but for logs.项目地址: https://gitcode.com/GitHub_Trending/lok/loki
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考