회기 연속성과 멀티 케이스 계약을 영속화
This commit is contained in:
parent
be08c0b573
commit
72353ecd82
26 changed files with 2170 additions and 127 deletions
87
infra/db/init/21_single_active_session.sql
Normal file
87
infra/db/init/21_single_active_session.sql
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
-- =============================================================================
|
||||
-- Vignette · migration 21 — learner-persona별 단일 활성 회기
|
||||
-- =============================================================================
|
||||
-- 기존 DB 적용 전 owner가 아래 읽기 전용 점검을 실행한다.
|
||||
-- 중복 또는 실패 후 남은 invalid index가 있으면 임의 종료·DROP 하지 말고
|
||||
-- 소유자가 보존·복구 방식을 결정해야 한다.
|
||||
--
|
||||
-- SELECT learner_id, persona_id, count(*)
|
||||
-- FROM app.sessions
|
||||
-- WHERE ended_at IS NULL AND persona_id IS NOT NULL
|
||||
-- GROUP BY learner_id, persona_id
|
||||
-- HAVING count(*) > 1;
|
||||
--
|
||||
-- CREATE INDEX CONCURRENTLY는 transaction block 안에서 실행할 수 없다.
|
||||
-- release agent가 online migration으로 owner psql에 전달한다.
|
||||
-- 이 파일의 전·후 condition은 IF NOT EXISTS가 invalid/wrong index를 조용히
|
||||
-- 건너뛰는 경우를 fail-closed로 막는다.
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
target_index oid := to_regclass('app.uq_sessions_one_active_learner_persona');
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM app.sessions
|
||||
WHERE ended_at IS NULL
|
||||
AND persona_id IS NOT NULL
|
||||
GROUP BY learner_id, persona_id
|
||||
HAVING count(*) > 1
|
||||
) THEN
|
||||
RAISE EXCEPTION
|
||||
'migration 21 blocked: duplicate active learner-persona sessions exist';
|
||||
END IF;
|
||||
|
||||
IF target_index IS NOT NULL AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_index AS index_meta
|
||||
WHERE index_meta.indexrelid = target_index
|
||||
AND index_meta.indrelid = 'app.sessions'::regclass
|
||||
AND index_meta.indisvalid
|
||||
AND index_meta.indisready
|
||||
AND index_meta.indisunique
|
||||
AND index_meta.indnkeyatts = 2
|
||||
AND pg_get_indexdef(index_meta.indexrelid, 1, true) = 'learner_id'
|
||||
AND pg_get_indexdef(index_meta.indexrelid, 2, true) = 'persona_id'
|
||||
AND regexp_replace(
|
||||
pg_get_expr(index_meta.indpred, index_meta.indrelid),
|
||||
'[[:space:]()]',
|
||||
'',
|
||||
'g'
|
||||
) = 'ended_atISNULLANDpersona_idISNOTNULL'
|
||||
) THEN
|
||||
RAISE EXCEPTION
|
||||
'migration 21 blocked: target index exists but is invalid or has a different definition';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS uq_sessions_one_active_learner_persona
|
||||
ON app.sessions (learner_id, persona_id)
|
||||
WHERE ended_at IS NULL AND persona_id IS NOT NULL;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_index AS index_meta
|
||||
WHERE index_meta.indexrelid = 'app.uq_sessions_one_active_learner_persona'::regclass
|
||||
AND index_meta.indrelid = 'app.sessions'::regclass
|
||||
AND index_meta.indisvalid
|
||||
AND index_meta.indisready
|
||||
AND index_meta.indisunique
|
||||
AND index_meta.indnkeyatts = 2
|
||||
AND pg_get_indexdef(index_meta.indexrelid, 1, true) = 'learner_id'
|
||||
AND pg_get_indexdef(index_meta.indexrelid, 2, true) = 'persona_id'
|
||||
AND regexp_replace(
|
||||
pg_get_expr(index_meta.indpred, index_meta.indrelid),
|
||||
'[[:space:]()]',
|
||||
'',
|
||||
'g'
|
||||
) = 'ended_atISNULLANDpersona_idISNOTNULL'
|
||||
) THEN
|
||||
RAISE EXCEPTION
|
||||
'migration 21 failed: valid target unique index was not created';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
Loading…
Add table
Add a link
Reference in a new issue