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 和监控日志绑定类型错误。
定位结果
- 高 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 中执行。
- 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 全表扫描特征。
- 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

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 和监控日志绑定类型错误。
定位结果
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 中执行。
对高 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 全表扫描特征。
对当前镜像的 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