yumi-golang/migrations/058_yumi_game_king.sql
2026-07-18 15:32:58 +08:00

222 lines
11 KiB
SQL

-- Yumi Game King: canonical GAME_CONSUME_GOLD -> draw chances, deterministic ranking and settlement.
--
-- Performance review:
-- 1. The event path first resolves the very small set of enabled activities by
-- (sys_origin, enabled, start_time, end_time), then inserts a unique activity/event ledger row
-- and updates one aggregate primary key. It never scans wallet shards or arbitrary expenses.
-- 2. Ranking reads the materialized aggregate through
-- (activity_id, total_consumed DESC, consume_reached_time ASC, user_id ASC); settlement freezes
-- only Top 30 and does not GROUP BY the append-only ledger.
-- 3. Draw idempotency, settlement idempotency, finite-stock reservation and reward delivery claims
-- all use primary/unique keys. Unlimited stock (-1) is not updated and therefore avoids a hot row.
-- 4. Backfill binds sys_origin + GAME_CONSUME_GOLD and keysets by (occurred_at,id) through the added
-- index. The public lastId is resolved to its scoped occurred_at before each page, preserving the
-- lastId-only contract without sorting/rescanning the whole activity window.
-- 5. The activity tables are empty, but the backfill index targets the existing production
-- task_center_event_archive_outbox (about 35 million rows / 50 GiB at review time). Treat that ALTER
-- as an independent online DDL: run it before the application rollout, monitor metadata-lock waits,
-- temporary disk usage and replica lag until completion, and do not hide it inside a service restart.
-- 6. The ALTER explicitly requires ALGORITHM=INPLACE and LOCK=NONE. If the target MySQL version or table
-- state cannot honor those guarantees, the migration must fail instead of silently falling back to a
-- table-copy or write-blocking algorithm. It performs no UPDATE/DELETE/backfill.
-- 7. The one-row-per-sysOrigin scope lock is touched only by admin enable/save transactions; it keeps
-- concurrent overlap checks correct without serializing the high-volume game event path.
SET NAMES utf8mb4;
CREATE TABLE IF NOT EXISTS `yumi_game_king_activity` (
`id` BIGINT NOT NULL,
`activity_code` VARCHAR(64) NOT NULL,
`activity_name` VARCHAR(128) NOT NULL,
`activity_desc` VARCHAR(1000) NOT NULL DEFAULT '',
`sys_origin` VARCHAR(32) NOT NULL,
`time_zone` VARCHAR(64) NOT NULL DEFAULT 'Asia/Riyadh',
`start_time` DATETIME(3) NOT NULL,
`end_time` DATETIME(3) NOT NULL,
`settlement_delay_minutes` INT NOT NULL DEFAULT 30,
`settlement_time` DATETIME(3) NOT NULL,
`coin_per_draw` BIGINT NOT NULL,
`ranking_type` VARCHAR(32) NOT NULL DEFAULT 'TYCOON',
`ranking_period` VARCHAR(32) NOT NULL DEFAULT 'OVERALL',
`enabled` TINYINT(1) NOT NULL DEFAULT 0,
`settlement_status` VARCHAR(32) NOT NULL DEFAULT 'NOT_STARTED',
`create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
UNIQUE KEY `uk_yumi_gk_activity_code` (`activity_code`),
KEY `idx_yumi_gk_activity_window` (`sys_origin`, `enabled`, `start_time`, `end_time`),
KEY `idx_yumi_gk_activity_settle` (`settlement_status`, `settlement_time`, `end_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Yumi Game King activity';
CREATE TABLE IF NOT EXISTS `yumi_game_king_scope_lock` (
`sys_origin` VARCHAR(32) NOT NULL,
`update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`sys_origin`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Serializes enabled-window checks per Yumi tenant';
CREATE TABLE IF NOT EXISTS `yumi_game_king_prize` (
`id` BIGINT NOT NULL,
`activity_id` BIGINT NOT NULL,
`prize_name` VARCHAR(128) NOT NULL,
`prize_image` VARCHAR(500) NOT NULL DEFAULT '',
`weight` BIGINT NOT NULL,
`stock` BIGINT NOT NULL DEFAULT -1 COMMENT '-1 unlimited, 0 depleted, positive remaining',
`resource_group_id` BIGINT NOT NULL,
`reward_group_name` VARCHAR(128) NOT NULL DEFAULT '',
`reward_items_json` TEXT NOT NULL,
`enabled` TINYINT(1) NOT NULL DEFAULT 1,
`sort_order` TINYINT NOT NULL,
`create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
UNIQUE KEY `uk_yumi_gk_prize_position` (`activity_id`, `sort_order`),
KEY `idx_yumi_gk_prize_enabled` (`activity_id`, `enabled`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Seven fixed Yumi Game King prizes';
CREATE TABLE IF NOT EXISTS `yumi_game_king_rank_reward` (
`id` BIGINT NOT NULL,
`activity_id` BIGINT NOT NULL,
`start_rank` SMALLINT NOT NULL,
`end_rank` SMALLINT NOT NULL,
`resource_group_id` BIGINT NOT NULL,
`reward_name` VARCHAR(128) NOT NULL DEFAULT '',
`reward_items_json` TEXT NOT NULL,
`create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
UNIQUE KEY `uk_yumi_gk_rank_range` (`activity_id`, `start_rank`, `end_rank`),
KEY `idx_yumi_gk_rank_start` (`activity_id`, `start_rank`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Six fixed end-settlement rank reward ranges';
CREATE TABLE IF NOT EXISTS `yumi_game_king_consume_ledger` (
`id` BIGINT NOT NULL,
`activity_id` BIGINT NOT NULL,
`event_id` VARCHAR(128) NOT NULL,
`user_id` BIGINT NOT NULL,
`sys_origin` VARCHAR(32) NOT NULL,
`amount` BIGINT NOT NULL,
`event_time` DATETIME(3) NOT NULL,
`payload_json` TEXT NOT NULL,
`create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
UNIQUE KEY `uk_yumi_gk_ledger_event` (`activity_id`, `event_id`),
KEY `idx_yumi_gk_ledger_user` (`activity_id`, `user_id`, `event_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Idempotent canonical game-consumption ledger';
CREATE TABLE IF NOT EXISTS `yumi_game_king_user` (
`activity_id` BIGINT NOT NULL,
`user_id` BIGINT NOT NULL,
`total_consumed` BIGINT NOT NULL DEFAULT 0,
`used_chances` BIGINT NOT NULL DEFAULT 0,
`consume_reached_time` DATETIME(3) NOT NULL,
`create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`activity_id`, `user_id`),
KEY `idx_yumi_gk_user_rank` (
`activity_id`, `total_consumed` DESC, `consume_reached_time` ASC, `user_id` ASC
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Overall game-consumption aggregate and draw chance usage';
CREATE TABLE IF NOT EXISTS `yumi_game_king_user_daily` (
`activity_id` BIGINT NOT NULL,
`stat_date` DATE NOT NULL,
`user_id` BIGINT NOT NULL,
`total_consumed` BIGINT NOT NULL DEFAULT 0,
`consume_reached_time` DATETIME(3) NOT NULL,
`create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`activity_id`, `stat_date`, `user_id`),
KEY `idx_yumi_gk_daily_rank` (
`activity_id`, `stat_date`, `total_consumed` DESC, `consume_reached_time` ASC, `user_id` ASC
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Activity-timezone daily game-consumption aggregate';
CREATE TABLE IF NOT EXISTS `yumi_game_king_draw_record` (
`id` BIGINT NOT NULL,
`activity_id` BIGINT NOT NULL,
`user_id` BIGINT NOT NULL,
`request_id` VARCHAR(64) NOT NULL,
`prize_id` BIGINT NOT NULL,
`prize_name` VARCHAR(128) NOT NULL,
`prize_image` VARCHAR(500) NOT NULL DEFAULT '',
`resource_group_id` BIGINT NOT NULL,
`delivery_status` VARCHAR(32) NOT NULL,
`retry_count` INT NOT NULL DEFAULT 0,
`failure_reason` VARCHAR(500) NOT NULL DEFAULT '',
`draw_time` DATETIME(3) NOT NULL,
`deliver_time` DATETIME(3) NULL,
`create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
UNIQUE KEY `uk_yumi_gk_draw_request` (`activity_id`, `user_id`, `request_id`),
KEY `idx_yumi_gk_draw_user` (`activity_id`, `user_id`, `create_time`),
KEY `idx_yumi_gk_draw_manage` (`activity_id`, `delivery_status`, `id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Idempotent Yumi Game King draw records';
CREATE TABLE IF NOT EXISTS `yumi_game_king_settlement_record` (
`id` BIGINT NOT NULL,
`activity_id` BIGINT NOT NULL,
`user_id` BIGINT NOT NULL,
`rank_no` SMALLINT NOT NULL,
`total_consumed` BIGINT NOT NULL,
`rank_reward_id` BIGINT NOT NULL,
`resource_group_id` BIGINT NOT NULL,
`business_no` VARCHAR(128) NOT NULL,
`delivery_status` VARCHAR(32) NOT NULL,
`retry_count` INT NOT NULL DEFAULT 0,
`failure_reason` VARCHAR(500) NOT NULL DEFAULT '',
`deliver_time` DATETIME(3) NULL,
`create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
UNIQUE KEY `uk_yumi_gk_settle_user` (`activity_id`, `user_id`),
UNIQUE KEY `uk_yumi_gk_settle_rank` (`activity_id`, `rank_no`),
UNIQUE KEY `uk_yumi_gk_settle_business` (`business_no`),
KEY `idx_yumi_gk_settle_status` (`activity_id`, `delivery_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Frozen deterministic Top30 settlement records';
CREATE TABLE IF NOT EXISTS `yumi_game_king_delivery_item` (
`id` BIGINT NOT NULL,
`owner_type` VARCHAR(20) NOT NULL COMMENT 'DRAW or SETTLEMENT',
`owner_id` BIGINT NOT NULL,
`activity_id` BIGINT NOT NULL,
`user_id` BIGINT NOT NULL,
`resource_group_id` BIGINT NOT NULL,
`delivery_status` VARCHAR(32) NOT NULL,
`retry_count` INT NOT NULL DEFAULT 0,
`failure_reason` VARCHAR(500) NOT NULL DEFAULT '',
`deliver_time` DATETIME(3) NULL,
`create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
UNIQUE KEY `uk_yumi_gk_delivery_owner` (`owner_type`, `owner_id`),
KEY `idx_yumi_gk_delivery_status` (`delivery_status`, `update_time`, `id`),
KEY `idx_yumi_gk_delivery_activity` (`activity_id`, `delivery_status`, `id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Group-level reward delivery and UNKNOWN reconciliation';
SET @current_schema := DATABASE();
SET @add_task_center_game_king_backfill_sql := IF(
NOT EXISTS (
SELECT 1
FROM INFORMATION_SCHEMA.STATISTICS
WHERE table_schema = @current_schema
AND table_name = 'task_center_event_archive_outbox'
AND index_name = 'idx_task_center_game_king_backfill'
),
'ALTER TABLE `task_center_event_archive_outbox` ADD INDEX `idx_task_center_game_king_backfill` (`sys_origin`, `event_type`, `occurred_at`, `id`), ALGORITHM=INPLACE, LOCK=NONE',
'SELECT 1'
);
PREPARE add_task_center_game_king_backfill_stmt FROM @add_task_center_game_king_backfill_sql;
EXECUTE add_task_center_game_king_backfill_stmt;
DEALLOCATE PREPARE add_task_center_game_king_backfill_stmt;