1Panel-dev / 1Panel-dev/1Panel

[Bug] 1PWAF event_id query fails to hit some indexes, causing a single OpenResty worker to remain at 100% CPU for extended periods and the log queue to overflow.

Open
#13,583 3 comments 0 reactions 1 assignee View on GitHub

@wanghe-fit2cloud is already working on this.

Since Aug 18, 2026.

type: optimization
Dominant language
Go
Stars
37k
Forks
3.4k
Avg merge
9h 16m
Merged PRs (30d)
105

Description

Contact Information

19674250@qq.com

1Panel Version

2.2.5

Problem Description

环境信息
1Panel 版本:v2.2.5 stable
操作系统:Debian GNU/Linux 12(bookworm)
内核:6.1.0-49-cloud-amd64
架构:x86_64
Docker:29.5.2
OpenResty 镜像:1panel/openresty:1.31.1.1-2-3-noble
CPU:16 核
内存:15 GiB
WAF 状态:开启,protection 模式
WAF 日志:开启,配置为 maxDay: 180、maxSize: 1
问题现象
OpenResty 容器长期保持约 97%~100% CPU,但并不是全部 worker 都繁忙。

进程检查发现:

只有第一个 OpenResty worker 长期占用约 91%~98% CPU。
该 worker 已运行约 4 天 10 小时,累计 CPU 时间约 4 天 1 小时。
其余 15 个 worker 基本为 0%。
服务器整体负载约 1.x,内存充足,没有发生 OOM。
当前访问日志抽样流量约 1.7 请求/秒,不足以解释持续满一核。
容器状态示例:

NAME CPU % MEM USAGE
1Panel-openresty-xxxx 97.37% 982.8 MiB
worker 状态示例:

PID STAT %CPU ELAPSED TIME
379818 R 91.3 4-10:50:30 4-01:36:14
影响
OpenResty 错误日志持续出现 WAF 攻击日志队列已满:

[lua] attack_log.lua:0: attack_log queue full,
dropped: 111651,
error: pending limit reached while logging request
首次出现队列满的时间为:

2026/08/10 04:28:40
截至排查时:

attack_log queue full 告警记录约 3420 条。
WAF 内部丢弃计数至少达到 111651。
历史日志还出现过 database is locked 和监控日志绑定类型错误。
定位结果

  1. 高 CPU 来自 worker 0 的 WAF 日志后台任务
    OpenResty 配置包含:

init_worker_by_lua_file /usr/local/openresty/1pwaf/worker.lua;
对当前镜像中的 worker.lua 反汇编后确认,worker ID 0 会周期运行:

ngx.timer.every(2, waf_task.insert_log)
因此 WAF 日志消费者集中在第一个 worker 中执行。

  1. worker 正在反复读取 SQLite 攻击日志数据库
    对高 CPU worker 进行 5 秒 /proc//io 采样:

逻辑读取增量:约 5.59 GB
读取系统调用增量:1,365,508 次
平均读取调用:约 273,000 次/秒
物理磁盘读取增量:0
说明进程正在页缓存中高频扫描数据库,并不是磁盘 I/O 卡住。

使用 strace 抓取后,确认它连续读取:

/usr/local/openresty/1pwaf/data/db/waf/attack_logs.db
示例:

pread64(116</usr/local/openresty/1pwaf/data/db/waf/attack_logs.db>,
..., 4096, 370524160) = 4096
pread64(116</usr/local/openresty/1pwaf/data/db/waf/attack_logs.db>,
..., 4096, 370528256) = 4096
pread64(116</usr/local/openresty/1pwaf/data/db/waf/attack_logs.db>,
..., 4096, 370540544) = 4096
读取偏移持续递增,符合 SQLite 全表扫描特征。

  1. event_id 查询没有使用现有索引
    对当前镜像的 waf_task.lua 反汇编后,确认存在动态查询:

SELECT id FROM

WHERE event_id = ? LIMIT 1
以 attack_logs 为例,实际查询为:

SELECT id
FROM attack_logs
WHERE event_id = ?
LIMIT 1;
数据库现有索引是部分唯一索引:

CREATE UNIQUE INDEX idx_attack_logs_event_id
ON attack_logs(event_id)
WHERE event_id IS NOT NULL
AND event_id <> '';
但对实际查询执行 EXPLAIN QUERY PLAN,结果是:

SCAN attack_logs
即现有部分索引没有被使用。

如果在查询中补齐部分索引条件:

SELECT id
FROM attack_logs
WHERE event_id = ?
AND event_id IS NOT NULL
AND event_id <> ''
LIMIT 1;
查询计划立即变为:

SEARCH attack_logs
USING COVERING INDEX idx_attack_logs_event_id (event_id=?)
同样的问题也存在于:

nginx_logs
block_ips
验证结果:

attack_logs:
原查询:SCAN attack_logs
补齐条件:SEARCH attack_logs USING COVERING INDEX idx_attack_logs_event_id

nginx_logs:
原查询:SCAN nginx_logs
补齐条件:SEARCH nginx_logs USING COVERING INDEX idx_nginx_logs_event_id

block_ips:
原查询:SCAN block_ips
补齐条件:SEARCH block_ips USING COVERING INDEX idx_block_ips_event_id
排查时数据库规模
attack_logs.db
大小:约 718 MB
自增序号:约 4,950,276

nginx_logs.db
大小:约 1.94 GB
自增序号:约 4,950,257

block_ips.db
大小:约 4.86 MB
自增序号:约 106,721
当 waf_task 每处理一条日志都执行一次没有命中索引的 event_id 查询时,会反复扫描数百 MB 甚至接近 2 GB 的数据库,导致 worker 0 长期占满一个 CPU,并进一步造成日志队列积压和丢弃。

Steps to Reproduce

使用 1panel/openresty:1.31.1.1-2-3-noble。
开启 1PWAF 和 WAF 日志。
让 attack_logs、nginx_logs 累积到较大数据量。
检查 OpenResty worker CPU。
对以下查询执行查询计划:
EXPLAIN QUERY PLAN
SELECT id FROM attack_logs WHERE event_id = ? LIMIT 1;
实际结果:

SCAN attack_logs
补充部分索引条件后再次检查:
EXPLAIN QUERY PLAN
SELECT id
FROM attack_logs
WHERE event_id = ?
AND event_id IS NOT NULL
AND event_id <> ''
LIMIT 1;
结果变为索引查询。

The expected correct result

WAF 日志写入过程中,使用 event_id 判断记录是否存在时,应命中 event_id 索引,不能随日志数据库增长而反复全表扫描。

实际结果
实际 SQL 未包含部分索引的完整条件,SQLite 未使用现有索引,导致:

worker 0 长期满一核。
SQLite 数据库被反复全表扫描。
WAF 日志消费速度下降。
日志队列达到上限。
大量攻击日志被丢弃。
建议修复
建议修改 WAF 日志事件查询,将:

SELECT id FROM %s WHERE event_id = ? LIMIT 1
修改为:

SELECT id
FROM %s
WHERE event_id = ?
AND event_id IS NOT NULL
AND event_id <> ''
LIMIT 1
该修改可以直接使用现有部分索引,避免额外增加重复索引。

需要同时确认动态表名涉及的以下表:

attack_logs
nginx_logs
block_ips
另一种兼容方案是为这些表增加不带 WHERE 条件的普通 event_id 索引,但会和现有部分索引重复,占用额外磁盘空间并增加写入开销,因此更建议修正查询语句。

建议增加回归测试,确保以下查询计划不能出现:

SCAN attack_logs
SCAN nginx_logs
SCAN block_ips

Related log output

Additional Information
Image

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.