NewAPI 的日志目前是单表存储,已经看到太多佬友因为日志撑爆磁盘了,下面给出一种解决方式,可能不完全兼容,但是个人测试下来没有什么问题
与 NewAPI 版本无关,基本所有版本都可以用,我用的版本是 1.0.0-rc.10
自带的日志删除
最常用的是使用 NewAPI 自带的 “清理历史日志” 来进行删除
// 代码逻辑如下
// 直接按照时间戳删除
LOG_DB.Where("created_at < ?", targetTimestamp).Limit(limit).Delete(&Log{})
这里有两个问题:
- Delete 的效率不高,或者说在数据量过大的情况下使用 Delete 带来的 IO 可能成为灾难
- Delete 之后磁盘空间并不会回收,需要手动回收
通过数据库分片解决日志删除
适用于 MySQL,如果您是其他的 DB 如 Postgres / SQLite,将下面的内容提供给 AI 相信也会有对应的解决方案
分片的核心逻辑是,将日志拆分到对应的日期上,比如分片 logs_20260601 只存储当天的数据,在超出需要删除的日期后,比如 1 个月后的 2026/07/01,直接删除 logs_20260601 分片,分片占用的空间会立刻释放,对应的日志数据也会直接删除
不过这里要提示您,所有的 DB 变更都可能引入风险,请您在执行前通过 mysqldump 或者类似工具,导出完整的数据结构和数据内容,确保执行出现异常可以随时回滚
执行下面的步骤前,建议停机,否则可能导致数据不完整,如果数据完整性不在考虑范围内,也可以在线更新
Step 1 检查数据符合预期 (Read)
执行下面的查询语句,确保 count 为 0,即所有的日志的创建时间不为空
SELECT COUNT(*) AS null_created_at_count
FROM logs
WHERE created_at IS NULL;
Step 2 创建临时表 (Write)
CREATE TABLE logs_new LIKE logs;
Step 3 添加准备分区语句 (Read)
通过下面的 SQL,可以获得一个建表 SQL,对 DB 无任何副作用,可以放心执行
下面的 @start_date 和 @end_date 两行,可以按照您的实际数据存储情况调整,下面的配置为创建 180 天前到 7 天后的分区,如果您的日志已经回收或删除过了,可以考虑缩减分区数量
SET time_zone = '+08:00'; 这里可以按照您的需求调整为具体的时区
分区必须提前创建,否则插入数据会有问题,所以您至少需要创建 7 天后的备用
SET SESSION group_concat_max_len = 1024 * 1024;
SET time_zone = '+08:00';
SET @start_date = DATE_SUB(CURDATE(), INTERVAL 180 DAY);
SET @end_date = DATE_ADD(CURDATE(), INTERVAL 7 DAY);
WITH RECURSIVE dates AS (
SELECT @start_date AS d
UNION ALL
SELECT DATE_ADD(d, INTERVAL 1 DAY)
FROM dates
WHERE d < @end_date
)
SELECT GROUP_CONCAT(
CONCAT(
' PARTITION p', DATE_FORMAT(d, '%Y%m%d'),
' VALUES LESS THAN (',
UNIX_TIMESTAMP(DATE_ADD(d, INTERVAL 1 DAY)),
')'
)
ORDER BY d
SEPARATOR ',\n'
) INTO @parts
FROM dates;
SET @sql = CONCAT(
'ALTER TABLE logs_new
MODIFY `created_at` bigint NOT NULL,
DROP PRIMARY KEY,
DROP INDEX `idx_created_at_id`,
ADD PRIMARY KEY (`id`, `created_at`)
PARTITION BY RANGE (`created_at`) (
', @parts, ',
PARTITION pmax VALUES LESS THAN MAXVALUE
)'
);
SELECT @sql;
Step 4 执行分区语句 (Write)
执行上一步输出的 SQL,会将新的 logs_new 表调整为分区表
Step 5 导入旧的数据 (Write)
取决于您的数据量,这一步可能会花费一些时间
INSERT INTO logs_new
SELECT *
FROM logs;
Step 6 校验数据已经导入完成 (Read)
如果您是停机更新,确保两条 SQL 输出的内容是一致的
如果您是在线更新,确保数据接近或一致
SELECT "logs" as `table`, COUNT(*), MIN(created_at), MAX(created_at) FROM logs
UNION
SELECT "logs_new" as `table`, COUNT(*), MIN(created_at), MAX(created_at) FROM logs_new;
Step 7 确认分区 (Read)
下面的 SQL 会打印新表的所有分区,以及每个分区的数据量
SELECT
PARTITION_NAME,
PARTITION_DESCRIPTION,
TABLE_ROWS
FROM information_schema.PARTITIONS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'logs_new'
ORDER BY PARTITION_ORDINAL_POSITION;
Step 8 切表 (Write)
切换流量到新表,执行完成后请检查 NewAPI 各组件各页面是否能正常工作,特别是依赖日志的内容
RENAME TABLE logs TO logs_old, logs_new TO logs;
Step 9 创建自动回收创建/分区任务 (Write)
MySQL 不会自动创建或者删除分区,需要您创建定时任务来实现,下面会给出创建/删除分区的任务,您可以按需添加
Step 9.1 开启 MySQL Event Scheduler在数据库中执行下面的命令,通常需要 root 用户,无需重启
SET GLOBAL event_scheduler = ON;
SHOW VARIABLES LIKE 'event_scheduler';
在配置文件的 mysqld 章节,增加下面的配置,无需重启
这一步是为了保证,即使后面重启数据库,Event Scheduler 仍然会是开启状态
[mysqld]
event_scheduler=ON
Step 9.2 创建新建分区任务
下面的两个 SQL 都需要执行
第一个 SQL 中的 SET time_zone = '+08:00'; 可按需修改为您的时区
第二个 SQL 中的时间您可以修改为适用于您的服务的时间,目前设置的是每天的 02:00,用于控制定时触发的时机
DELIMITER $$
DROP PROCEDURE IF EXISTS sp_logs_create_future_partitions$$
CREATE PROCEDURE sp_logs_create_future_partitions()
BEGIN
DECLARE v_i INT DEFAULT 0;
DECLARE v_d DATE;
DECLARE v_partition_name VARCHAR(32);
DECLARE v_less_than BIGINT;
DECLARE v_exists INT DEFAULT 0;
DECLARE v_sql TEXT;
SET time_zone = '+08:00';
WHILE v_i <= 7 DO
SET v_d = DATE_ADD(CURDATE(), INTERVAL v_i DAY);
SET v_partition_name = CONCAT('p', DATE_FORMAT(v_d, '%Y%m%d'));
SET v_less_than = UNIX_TIMESTAMP(DATE_ADD(v_d, INTERVAL 1 DAY));
SELECT COUNT(*)
INTO v_exists
FROM information_schema.PARTITIONS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'logs'
AND PARTITION_NAME = v_partition_name;
IF v_exists = 0 THEN
SET v_sql = CONCAT(
'ALTER TABLE logs REORGANIZE PARTITION pmax INTO (',
'PARTITION ', v_partition_name,
' VALUES LESS THAN (', v_less_than, '), ',
'PARTITION pmax VALUES LESS THAN MAXVALUE',
')'
);
SET @sql = v_sql;
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END IF;
SET v_i = v_i + 1;
END WHILE;
END$$
DELIMITER ;
DROP EVENT IF EXISTS ev_logs_create_future_partitions;
CREATE EVENT ev_logs_create_future_partitions
ON SCHEDULE EVERY 1 DAY
STARTS TIMESTAMP(CURRENT_DATE, '02:00:00')
DO
CALL sp_logs_create_future_partitions();
Step 9.3 创建删除分区任务 (可选)
这个是用于替代 NewAPI 自带的删除数据任务,如果您有需要可以配置这里的自动删除
第一个 SQL 中的 INTERVAL 180 DAY 表示删除 180 天之前的数据,可以按需修改,SET time_zone = '+08:00'; 也可按需修改为您的时区
第二个 SQL 中的时间您可以修改为适用于您的服务的时间,目前设置的是每天的 03:00,用于控制定时触发的时机
DELIMITER $$
DROP PROCEDURE IF EXISTS sp_logs_drop_old_partitions$$
CREATE PROCEDURE sp_logs_drop_old_partitions()
BEGIN
DECLARE v_cutoff_date DATE;
DECLARE v_cutoff_ts BIGINT;
DECLARE v_drop_partitions TEXT;
DECLARE v_sql TEXT;
SET time_zone = '+08:00';
SET v_cutoff_date = DATE_SUB(CURDATE(), INTERVAL 180 DAY);
SET v_cutoff_ts = UNIX_TIMESTAMP(v_cutoff_date);
SELECT GROUP_CONCAT(PARTITION_NAME ORDER BY PARTITION_ORDINAL_POSITION)
INTO v_drop_partitions
FROM information_schema.PARTITIONS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'logs'
AND PARTITION_NAME REGEXP '^p[0-9]{8}$'
AND CAST(PARTITION_DESCRIPTION AS UNSIGNED) <= v_cutoff_ts;
IF v_drop_partitions IS NOT NULL AND v_drop_partitions <> '' THEN
SET v_sql = CONCAT(
'ALTER TABLE logs DROP PARTITION ',
v_drop_partitions
);
SET @sql = v_sql;
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END IF;
END$$
DELIMITER ;
DROP EVENT IF EXISTS ev_logs_drop_old_partitions;
CREATE EVENT ev_logs_drop_old_partitions
ON SCHEDULE EVERY 1 DAY
STARTS TIMESTAMP(CURRENT_DATE, '00:10:00')
DO
CALL sp_logs_drop_old_partitions();
Step 9.4 检查任务
show events;
SHOW PROCEDURE STATUS WHERE Db = DATABASE();
Step 10 大功告成
一切已准备就绪,请使用吧!数据库将按照您的配置自动创建新的分片,回收旧的分片,后续如果有调整,也可以直接修改 SQL 配置再次执行。
您可以定期通过下面的 SQL 来检查分区任务的运行状态和分区的数据量,请检查 pmax 分区数据量为 0,且已经创建了 7 天后的分区
SELECT
PARTITION_NAME,
PARTITION_DESCRIPTION,
TABLE_ROWS
FROM information_schema.PARTITIONS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'logs'
ORDER BY PARTITION_ORDINAL_POSITION DESC
LIMIT 15;
1 个帖子 - 1 位参与者