Explorar o código

feat(s8): add notification log indexes for dedup and cleanup

YY968XX hai 1 mes
pai
achega
54b2594b8f

+ 77 - 0
scripts/migrations/20260531_s8_notification_log_indexes.sql

@@ -0,0 +1,77 @@
+-- S8-NOTIFICATION-LOG-INDEX-DDL-P2-3
+-- Target table: ado_s8_notification_log
+-- Purpose: 仅用于 S8 notification_log 索引硬化,覆盖以下查询路径:
+--   A. (channel, created_at) → stuck/orphan dedup 60min cooldown
+--      query (S8ActiveFlowWatchService.cs 行 67-70 / 175-178)。
+--   B. (created_at)          → P1.2 S8LogRetentionCleanupJob 的
+--      created_at < cutoff 批量清理。
+--
+-- 设计边界:
+--   1. 仅新增 2 个二级索引,不改变任何数据;
+--   2. 不改字段、不改字段类型、不改 nullable、不改表结构;
+--   3. 不执行 DML(INSERT/UPDATE/DELETE);
+--   4. 不执行 DROP TABLE / TRUNCATE / ALTER COLUMN / MODIFY COLUMN;
+--   5. 执行前必须先跑下方 pre-check SELECT,命中(>0 行)即跳过对应 CREATE INDEX;
+--   6. 索引名必须与 AdoS8NotificationLog 实体 [SugarIndex] 注解完全一致:
+--        - idx_s8_notif_channel_time (Channel, CreatedAt)
+--        - idx_s8_notif_created_at   (CreatedAt)
+--      避免 EnableInitTable=true 时 CodeFirst 与手工 DDL 冲突;
+--   7. 使用 ALGORITHM=INPLACE, LOCK=NONE,与 InnoDB ONLINE DDL 一致,
+--      避免对在线写入产生表锁;
+--   8. 建议低峰执行;执行时机由数据负责人在阶段 2 决定。
+--
+-- 不在本脚本范围:
+--   - 暂不包含 (tenant_id, factory_id, channel, created_at) 复合索引(候选 C,
+--     留待后续聚合/API 瓶颈出现后再评估);
+--   - 不在 ado_s8_notification_log 上做任何 DROP;如需回滚,请使用同目录的
+--     20260531_s8_notification_log_indexes_rollback.sql。
+--
+-- Idempotency:
+--   MySQL 8 不支持 CREATE INDEX IF NOT EXISTS。如果索引已存在重跑会抛
+--   "Duplicate key name",属安全错误,先跑 pre-check 可避免。
+
+-- =========================================================================
+-- Pre-check (run first; non-destructive; expect 0 rows when index absent)
+-- =========================================================================
+
+-- Pre-check A: idx_s8_notif_channel_time
+SELECT COUNT(*) AS exists_channel_time
+FROM INFORMATION_SCHEMA.STATISTICS
+WHERE TABLE_SCHEMA = DATABASE()
+  AND TABLE_NAME = 'ado_s8_notification_log'
+  AND INDEX_NAME = 'idx_s8_notif_channel_time';
+
+-- Pre-check B: idx_s8_notif_created_at
+SELECT COUNT(*) AS exists_created_at
+FROM INFORMATION_SCHEMA.STATISTICS
+WHERE TABLE_SCHEMA = DATABASE()
+  AND TABLE_NAME = 'ado_s8_notification_log'
+  AND INDEX_NAME = 'idx_s8_notif_created_at';
+
+-- =========================================================================
+-- Index creation (run each only when its pre-check returned 0)
+-- =========================================================================
+
+-- Index A: stuck/orphan dedup 60min cooldown (channel + created_at)
+CREATE INDEX idx_s8_notif_channel_time
+ON ado_s8_notification_log (channel, created_at)
+ALGORITHM=INPLACE LOCK=NONE;
+
+-- Index B: retention cleanup (created_at < cutoff)
+CREATE INDEX idx_s8_notif_created_at
+ON ado_s8_notification_log (created_at)
+ALGORITHM=INPLACE LOCK=NONE;
+
+-- =========================================================================
+-- Post-check (run after creation; expect 2 rows total, one per index)
+-- =========================================================================
+
+SELECT INDEX_NAME, GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) AS cols
+FROM INFORMATION_SCHEMA.STATISTICS
+WHERE TABLE_SCHEMA = DATABASE()
+  AND TABLE_NAME = 'ado_s8_notification_log'
+  AND INDEX_NAME IN (
+    'idx_s8_notif_channel_time',
+    'idx_s8_notif_created_at'
+  )
+GROUP BY INDEX_NAME;

+ 63 - 0
scripts/migrations/20260531_s8_notification_log_indexes_rollback.sql

@@ -0,0 +1,63 @@
+-- S8-NOTIFICATION-LOG-INDEX-DDL-P2-3 — ROLLBACK
+-- Target table: ado_s8_notification_log
+-- Purpose: 回滚 20260531_s8_notification_log_indexes.sql 创建的 2 个二级索引。
+--
+-- 重要前置:
+--   1. 如果 AdoS8NotificationLog.cs 实体上的 [SugarIndex] 注解仍然存在,
+--      下一次 Database.json 的 TableSettings.EnableInitTable=true 启动时,
+--      CodeFirst 会按注解自动重建本脚本刚 DROP 的索引;
+--   2. 因此回滚 DDL 时,必须同步执行以下任一动作:
+--        - 同步移除 AdoS8NotificationLog.cs 中对应的 [SugarIndex] 注解,或
+--        - 在回滚期间将 Database.json 中 TableSettings.EnableInitTable 改为 false,
+--      否则回滚将被 CodeFirst 自动覆盖。
+--   3. 本脚本仅 DROP 二级索引,不删除任何数据行,不改字段,不改表结构,
+--      不触碰其他索引(PRIMARY 与未来新增索引不受影响)。
+--   4. 不执行 DML(INSERT/UPDATE/DELETE)。
+--   5. 不执行 DROP TABLE / TRUNCATE / ALTER COLUMN / MODIFY COLUMN。
+--
+-- Idempotency:
+--   MySQL 8 不支持 DROP INDEX IF EXISTS(仅 8.0.29+ 支持,且本环境未验证)。
+--   如果索引已不存在,DROP 会抛 "Can't DROP ... ; check that column/key exists",
+--   属安全错误,先跑 pre-check 可避免。
+
+-- =========================================================================
+-- Pre-check (run first; expect 1 row each when index present)
+-- =========================================================================
+
+-- Pre-check A: idx_s8_notif_channel_time
+SELECT COUNT(*) AS exists_channel_time
+FROM INFORMATION_SCHEMA.STATISTICS
+WHERE TABLE_SCHEMA = DATABASE()
+  AND TABLE_NAME = 'ado_s8_notification_log'
+  AND INDEX_NAME = 'idx_s8_notif_channel_time';
+
+-- Pre-check B: idx_s8_notif_created_at
+SELECT COUNT(*) AS exists_created_at
+FROM INFORMATION_SCHEMA.STATISTICS
+WHERE TABLE_SCHEMA = DATABASE()
+  AND TABLE_NAME = 'ado_s8_notification_log'
+  AND INDEX_NAME = 'idx_s8_notif_created_at';
+
+-- =========================================================================
+-- Index drop (run each only when its pre-check returned 1)
+-- =========================================================================
+
+-- Drop index A
+DROP INDEX idx_s8_notif_channel_time ON ado_s8_notification_log;
+
+-- Drop index B
+DROP INDEX idx_s8_notif_created_at ON ado_s8_notification_log;
+
+-- =========================================================================
+-- Post-check (run after drop; expect 0 rows total)
+-- =========================================================================
+
+SELECT INDEX_NAME, GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) AS cols
+FROM INFORMATION_SCHEMA.STATISTICS
+WHERE TABLE_SCHEMA = DATABASE()
+  AND TABLE_NAME = 'ado_s8_notification_log'
+  AND INDEX_NAME IN (
+    'idx_s8_notif_channel_time',
+    'idx_s8_notif_created_at'
+  )
+GROUP BY INDEX_NAME;

+ 3 - 3
server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj

@@ -11,9 +11,9 @@
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <Copyright>Admin.NET</Copyright>
     <Description>Admin.NET 通用权限开发平台</Description>
-    <AssemblyVersion>1.0.139</AssemblyVersion>
-    <FileVersion>1.0.139</FileVersion>
-    <Version>1.0.139</Version>
+    <AssemblyVersion>1.0.140</AssemblyVersion>
+    <FileVersion>1.0.140</FileVersion>
+    <Version>1.0.140</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 2 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S8/AdoS8NotificationLog.cs

@@ -4,6 +4,8 @@ namespace Admin.NET.Plugin.AiDOP.Entity.S8;
 /// 通知发送日志(规则命中后的落库;非「列表 SQL 结果集」独立业务对象)。
 /// </summary>
 [SugarTable("ado_s8_notification_log", "S8 通知日志")]
+[SugarIndex("idx_s8_notif_channel_time", nameof(Channel), OrderByType.Asc, nameof(CreatedAt), OrderByType.Asc)]
+[SugarIndex("idx_s8_notif_created_at", nameof(CreatedAt), OrderByType.Asc)]
 public class AdoS8NotificationLog
 {
     [SugarColumn(ColumnName = "id", IsPrimaryKey = true, IsIdentity = true, ColumnDataType = "bigint")]