Files
2nd/10_Wiki/Topic_Programming/Coding/DB_Bitemporal_Data.md
T
Antigravity Agent 9148c358d0 docs(10_Wiki): 위키 전체 재구성 — Topic_* 폴더를 4개 카테고리로 통합 + 대규모 중복 제거
Topic_Agent/Topic_Blog/Topics/Topics_Biz/Topics_Meeting/Topics_Rag의 마크다운 지식 문서를
Topic_General/Topic_Programming/Topic_Graphic/Topic_Business 4개 카테고리로 재분류.

- 중복 제거: frontmatter의 status:duplicate/merged + duplicate_of/redirect_to 필드로
  자기 자신을 중복으로 선언한 리다이렉트 stub 1032개 제거, 완전 동일 내용 파일 472개 제거,
  동일 파일명·다른 내용 충돌 시 더 큰(완전한) 버전만 유지(162개 제거) — 총 1639개 중복 제거.
- 분류: 폴더 단위로 명확한 항목(AI_and_ML/Coding/Architecture 등 → Programming,
  Comfyui/Visual_Effects → Graphic, Topics_Biz/Topics_Meeting/사업 등 → Business,
  Poetic_Blog_Writing/창의성/Game_Design 등 → General)은 폴더 우선순위로,
  나머지 혼재 폴더(Topic_Agent/Topic_Blog/Topics 루트/Thinking & Reasoning/Other/UI_UX_Assets)는
  title/tags 키워드 스코어링으로 파일 단위 분류(불명확한 경우 General로 폴백).
  원본 폴더명은 "From_*" 서브폴더로 보존해 추적 가능성 유지.
- 최종 배치: Programming 2784 / General 1608 / Graphic 285 / Business 249 = 4926개 문서.
- 에이전트 운영 상태(.astra/.agent/.obsidian/sessions/memory/_company/docs/lessons/_shared/src)는
  지식 콘텐츠가 아니므로 재분류 대상에서 제외하고 원위치 유지.
- Topics/Topic_email(상위 보호 폴더 Topic_email과 파일명 100% 중복) 삭제 — 보호 폴더 자체는 미변경.
- 완전히 비게 된 Topic_Agent/Topic_Blog/Topics_Biz/Topics_Rag 폴더 제거.
2026-07-05 00:33:48 +09:00

7.5 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
db-bitemporal-data Bitemporal Data — valid time + transaction time Coding draft B conceptual 2026-05-09 2026-05-09
database
time
vibe-coding
language applicable_to
SQL
Database
bitemporal
valid time
transaction time
system time
SCD
slowly changing dimension
history

Bitemporal Data

"이 fact 가 valid 한 기간" + "이 fact 가 DB 에 있던 기간". Bitemporal = 둘 다. 회계 / 의료 / legal 가 필수. SCD (Slowly Changing Dimension) 의 generalization.

📖 핵심 개념

  • Valid time (effective): 현실 의 fact 시점.
  • Transaction time (system): DB 안 시점.
  • 둘 다 = bitemporal.
  • "내가 1주 전에 알았던 사실 가 무엇" 가능.

💻 코드 패턴

일반 (no time)

CREATE TABLE customers (
  id INT PRIMARY KEY,
  name VARCHAR,
  address VARCHAR
);

UPDATE customers SET address = 'New' WHERE id = 1;
-- → 옛 address 잃음.

Valid time (effective from / to)

CREATE TABLE customers (
  id INT,
  name VARCHAR,
  address VARCHAR,
  valid_from DATE NOT NULL,
  valid_to DATE NOT NULL DEFAULT '9999-12-31',
  PRIMARY KEY (id, valid_from)
);

-- 변경 시
UPDATE customers SET valid_to = '2026-05-09' WHERE id = 1 AND valid_to = '9999-12-31';
INSERT INTO customers VALUES (1, 'Alice', 'New', '2026-05-09', '9999-12-31');

-- "2025-01-01 의 address?"
SELECT address FROM customers
WHERE id = 1 AND valid_from <= '2025-01-01' AND valid_to > '2025-01-01';

→ SCD Type 2.

Transaction time (system)

CREATE TABLE customers (
  id INT,
  name VARCHAR,
  sys_from TIMESTAMP NOT NULL,
  sys_to TIMESTAMP NOT NULL DEFAULT '9999-12-31',
  PRIMARY KEY (id, sys_from)
);

-- "1주 전 DB 에 있던 record?"
SELECT * FROM customers
WHERE id = 1 AND sys_from <= NOW() - INTERVAL '1 week'
  AND sys_to > NOW() - INTERVAL '1 week';

→ "내가 어제 본 거" — even if data 가 retro 변경.

Bitemporal (둘 다)

CREATE TABLE policies (
  policy_id INT,
  premium DECIMAL,
  
  valid_from DATE,    -- 현실 effective
  valid_to DATE DEFAULT '9999-12-31',
  
  sys_from TIMESTAMP,  -- DB transaction
  sys_to TIMESTAMP DEFAULT '9999-12-31',
  
  PRIMARY KEY (policy_id, valid_from, sys_from)
);

Insert (bitemporal)

-- 새 policy
INSERT INTO policies (policy_id, premium, valid_from, valid_to, sys_from, sys_to)
VALUES (1, 100, '2026-01-01', '9999-12-31', NOW(), '9999-12-31');

Update (premium 가 retroactive 변경)

-- 2026-05-09 가 발견: 2026-01-01 부터 premium 가 110 였다 (오타).

-- 1. 옛 row close
UPDATE policies SET sys_to = NOW()
WHERE policy_id = 1 AND sys_to = '9999-12-31';

-- 2. 새 row (valid 가 1월 1일 부터)
INSERT INTO policies VALUES (1, 110, '2026-01-01', '9999-12-31', NOW(), '9999-12-31');

→ "이 시점 에 우리 가 아는 premium" 가 query 가능.

Query: As-of (valid + system)

-- "2026-03-01 시점 의 policy. 1주 전 DB 의 view."
SELECT premium FROM policies
WHERE policy_id = 1
  AND valid_from <= '2026-03-01' AND valid_to > '2026-03-01'
  AND sys_from <= NOW() - INTERVAL '1 week' AND sys_to > NOW() - INTERVAL '1 week';

→ 4 가지 query 가능:

  • 현재 valid + 현재 system (default)
  • 과거 valid + 현재 system (history)
  • 현재 valid + 과거 system ("when did we know?")
  • 과거 valid + 과거 system (full audit)

SQL:2011 Temporal

-- Postgres + extension (BTRIM 또는 system-versioned tables 가 옵션)
CREATE TABLE customers (...) WITH SYSTEM VERSIONING;

SELECT * FROM customers FOR SYSTEM_TIME AS OF TIMESTAMP '2026-05-01';

→ MariaDB / SQL Server / IBM DB2 가 native 지원.

Use case: 보험

2026-01-01: Policy 시작, premium $100.
2026-03-01: Customer 가 새 car (premium $120).
2026-04-01: 발견 - 2026-01-01 부터 premium $110 였어야 함.

쿼리:
- "2026-02-15 의 premium" = $100 ($110 retroactive 적용 안 했을 시 본 거).
- "2026-02-15 의 premium (현재 truth)" = $110.

→ 보험 / regulatory 가 "we knew on date X" 답 필요.

Use case: 회계

"2026-Q1 매출 = ?"

- Q1 끝날 시점 답 = $1M.
- 4월 의 retroactive correction 후 답 = $1.05M.

→ Audit: 우리 가 Q1 close 때 본 = $1M.
정정 후 = $1.05M.
둘 다 record 유지.

Pitfall: complexity

Bitemporal 가 어려움.
- 매 update = 2-3 row
- Index 가 복잡 (composite)
- Query 가 복잡

→ 진짜 필요할 때 만.
대부분 = SCD Type 2 (valid time 만) 충분.

Implementation: trigger

CREATE OR REPLACE FUNCTION close_old_row() RETURNS TRIGGER AS $$
BEGIN
  UPDATE policies SET sys_to = NOW()
  WHERE policy_id = NEW.policy_id AND valid_from = NEW.valid_from AND sys_to = '9999-12-31';
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER policy_versioning BEFORE INSERT ON policies
FOR EACH ROW EXECUTE FUNCTION close_old_row();

XTDB (bitemporal native)

(xt/submit-tx node
  [[::xt/put {:xt/id :alice :address "New"}
    #inst "2026-05-09"]])  ; valid time

(xt/q (xt/db node #inst "2026-05-08")
  '{:find [?addr] :where [[:alice :address ?addr]]})
; ↑ as-of yesterday

→ Bitemporal first-class.

Datomic (similar)

(d/q '[:find ?addr
       :where [?e :customer/address ?addr]]
     (d/as-of db tx-id))

→ Immutable + time travel.

Snapshot tables (simpler alternative)

-- 매일 entire DB snapshot
CREATE TABLE customers_snapshot_2026_05_09 AS SELECT * FROM customers;

-- "2026-05-09 의 truth"
SELECT * FROM customers_snapshot_2026_05_09;

→ Storage 큼, query simple.

Event log (append-only) → state.
"2026-05-09 시점 의 state" = event log replay until then.

→ Bitemporal 의 different angle.

Backend_Event_Sourcing.

Audit log (simpler)

CREATE TABLE customer_audit (
  audit_id BIGSERIAL,
  customer_id INT,
  changed_at TIMESTAMP,
  old_data JSONB,
  new_data JSONB
);

-- Trigger 가 매 변경 추가

→ Full bitemporal 가 X. "언제 무엇 변경" 만.

When 진짜 bitemporal?

✓ Insurance (regulatory)
✓ Banking / accounting
✓ Healthcare (EHR — patient history vs DB record)
✓ Legal (contracts vs amendments)
✓ ERP (price catalog history)

✗ 일반 web app
✗ E-commerce
✗ Internal tool

→ "내가 X 시점 에 알았던 거?" 가 비즈니스 질문 일 때 만.

Performance

Composite index:
CREATE INDEX ON policies (policy_id, valid_from, valid_to, sys_from, sys_to);

→ Range query 빠름.
Storage 가 일반 의 N x (N = 평균 history depth).

🤔 의사결정 기준

상황 추천
일반 web No history
Audit log 필요 Audit table / trigger
현재 + 과거 valid SCD Type 2 (valid time only)
회계 / 보험 / 법 Full bitemporal
Event-driven Event sourcing
Functional / immutable Datomic / XTDB
Ad-hoc snapshot Daily backup

안티패턴

  • Bitemporal 없이 retroactive correction: 옛 truth 잃음.
  • Soft delete 만 + history 없음: 변경 추적 안 됨.
  • 모든 table 가 bitemporal: complexity.
  • Index 없이: query 가 full scan.
  • Trigger error → silent: data corrupt.
  • 9999-12-31 가 magic value: NULL 보다 좋지만 명시 필요.

🤖 LLM 활용 힌트

  • Bitemporal 진짜 필요 = 회계 / 법 / 의료.
  • 대부분 = valid time (SCD Type 2) 충분.
  • XTDB / Datomic 가 native 지원.
  • Audit log 가 cheap alternative.

🔗 관련 문서