추가 전용 이벤트 테이블
발생한 일을 기록하고 기존 데이터를 덮어쓰지 않습니다
추가 전용 이벤트 테이블은(는) CoddyKit의 무료 SQL Academy 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 SQL Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. SQL Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
추가 전용 테이블이란 무엇인가요
추가 전용 이벤트 테이블은 행을 삽입만 할 수 있고 갱신하거나 삭제할 수 없는 테이블입니다. 각 행은 특정 시점에 발생한 일을 나타냅니다.
이 패턴은 이벤트 소싱의 토대입니다. 현재 상태를 저장하는 대신 모든 변경 사항을 변경할 수 없는 이벤트로 저장하므로, 완전하고 감사 가능한 이력을 얻을 수 있습니다.
이벤트 테이블 만들기
잘 설계된 이벤트 테이블은 누가 무엇을 어떤 자원에 언제 수행했는지 기록합니다. occurred_at 열은 정확한 타임스탬프를 기록하고, DEFAULT NOW()는 이 값이 항상 자동으로 채워지도록 합니다.
이 설계에는 UPDATE나 DELETE가 없다는 점에 주목하세요. 한 번 기록된 행은 영구적으로 유지됩니다.
CREATE TABLE account_events (
id BIGSERIAL PRIMARY KEY,
account_id BIGINT NOT NULL,
event_type TEXT NOT NULL,
payload JSONB,
occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);이벤트 삽입하기
사용자의 모든 작업—로그인, 입금, 이메일 변경—은 새로운 행이 됩니다. 이전 이벤트로 돌아가 절대로 수정하지 않습니다. 수정해야 할 내용이 있으면 대신 보정 이벤트를 삽입합니다.
이렇게 하면 발생한 일의 전체 순서를 순서대로 보존할 수 있습니다.
INSERT INTO account_events (account_id, event_type, payload)
VALUES
(42, 'account_opened', '{"plan": "free"}'),
(42, 'email_verified', '{"email": "alice@example.com"}'),
(42, 'plan_upgraded', '{"from": "free", "to": "pro"}');전체 이력 읽기
모든 상태 변경이 행으로 저장되므로 계정의 전체 이력을 조회하는 일은 시간을 기준으로 정렬한 간단한 SELECT만으로 가능합니다. 첫 이벤트부터 최신 이벤트까지 기록의 전체 생애를 재생할 수 있습니다.
SELECT
id,
event_type,
payload,
occurred_at
FROM account_events
WHERE account_id = 42
ORDER BY occurred_at ASC;현재 상태 도출하기
추가 전용 테이블에서는 현재 상태를 직접 저장하지 않고, 관련된 최신 이벤트를 읽어 도출합니다. 여기서 계정 42의 현재 요금제는 가장 최근의 plan_upgraded 또는 account_opened 이벤트가 나타내는 값입니다.
ORDER BY occurred_at DESC LIMIT 1을 사용하면 가장 최근 스냅샷을 효율적으로 가져올 수 있습니다.
SELECT payload->>'to' AS current_plan
FROM account_events
WHERE account_id = 42
AND event_type IN ('account_opened', 'plan_upgraded')
ORDER BY occurred_at DESC
LIMIT 1;규칙으로 변경 불가성 적용하기
데이터베이스 수준에서 RULE을 사용해 테이블에 대한 UPDATE나 DELETE를 아무 알림 없이 무시하도록 하면 추가 전용 보장을 적용할 수 있습니다. 이렇게 하면 쓰기 권한이 있는 어떤 애플리케이션에서도 실수로 변경하는 일을 막을 수 있습니다.
예외를 발생시키는 트리거는 더 강력한 대안입니다. 작업을 오류와 함께 적극적으로 거부하기 때문입니다.
CREATE RULE no_update_events AS
ON UPDATE TO account_events
DO INSTEAD NOTHING;
CREATE RULE no_delete_events AS
ON DELETE TO account_events
DO INSTEAD NOTHING;트리거로 변경 불가성 적용하기
예외를 발생시키는 트리거는 조용히 처리하는 규칙보다 엄격합니다. 애플리케이션이 과거 이벤트를 변경하려고 하면 즉시 오류가 전달됩니다. 따라서 오류를 조용히 삼키지 않고 눈에 보이게 만들 수 있습니다.
CREATE OR REPLACE FUNCTION deny_event_mutation()
RETURNS TRIGGER AS $$
BEGIN
RAISE EXCEPTION 'Event table is append-only: % is not allowed', TG_OP;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_deny_event_mutation
BEFORE UPDATE OR DELETE ON account_events
FOR EACH ROW EXECUTE FUNCTION deny_event_mutation();시간에 따른 이벤트 수 세기
추가 전용 테이블을 사용하면 시간 기반 분석을 간단하게 수행할 수 있습니다. 모든 이벤트에 타임스탬프가 있으므로 별도의 열 없이도 일, 주, 월별로 그룹화할 수 있습니다. 아래 예제는 하루에 각 유형의 이벤트가 몇 건 발생했는지 셉니다.
SELECT
DATE_TRUNC('day', occurred_at) AS day,
event_type,
COUNT(*) AS total
FROM account_events
GROUP BY 1, 2
ORDER BY 1, 2;특정 시점의 상태 재구성
이벤트 로그의 가장 강력한 특성 중 하나는 어떤 기록이 과거의 특정 순간에 존재했던 상태든 재구성할 수 있다는 점입니다. 원하는 타임스탬프까지의 이벤트만 필터링하면 되며, 시간 여행 확장 기능도 필요하지 않습니다.
이는 디버깅, 감사, 규정 준수에 매우 중요합니다.
-- What plan was account 42 on at the end of last month?
SELECT payload->>'to' AS plan_at_snapshot
FROM account_events
WHERE account_id = 42
AND event_type IN ('account_opened', 'plan_upgraded')
AND occurred_at <= DATE_TRUNC('month', NOW()) - INTERVAL '1 second'
ORDER BY occurred_at DESC
LIMIT 1;수정 대신 보정 이벤트 사용하기
잘못된 청구와 같은 실수가 발견되어도 잘못된 이벤트를 삭제하지 않습니다. 대신 이를 취소하거나 되돌리는 보정 이벤트를 삽입합니다. 두 이벤트 모두 로그에 남으므로 정확히 무슨 일이 언제 발생했고 언제 수정되었는지 확인할 수 있습니다.
이렇게 하면 감사 추적 기록을 완전하게 유지하고 변조가 드러나도록 만들 수 있습니다.
-- A charge was applied by mistake; record a reversal
INSERT INTO account_events (account_id, event_type, payload)
VALUES (
42,
'charge_reversed',
'{"reason": "billing_error", "reverses_event_id": 17}'
);대규모 이벤트 테이블 분할하기
이벤트 테이블은 빠르게 커집니다. 시간 범위로 분할하면 개별 파티션을 작게 유지하고, 범위 조회를 빠르게 하며, 최근 데이터를 건드리지 않고 오래된 파티션을 보관하거나 삭제할 수 있습니다.
PostgreSQL의 선언적 파티셔닝을 사용하면 간단합니다. occurred_at에 RANGE 파티션을 정의하면 데이터베이스가 삽입 작업을 자동으로 분배합니다.
CREATE TABLE account_events_2025
PARTITION OF account_events
FOR VALUES FROM ('2025-01-01') TO ('2026-01-01');
CREATE TABLE account_events_2026
PARTITION OF account_events
FOR VALUES FROM ('2026-01-01') TO ('2027-01-01');추가 전용 테이블 이해도 확인
추가 전용 이벤트 테이블 설계를 얼마나 이해했는지 확인해 보세요.
복습: 추가 전용 이벤트 테이블
이 레슨에서는 추가 전용 이벤트 테이블을 설계하고 사용하는 방법을 배웠습니다.
- 변경 불가성 — 행은 한 번 삽입된 후 절대 수정되지 않습니다. 과거 이벤트는 사실입니다.
- 완전한 이력 — 모든 상태 변경이 보존되므로 전체 감사 추적 기록과 특정 시점 조회가 가능합니다.
- 보정 이벤트 — 실수는 기존 이벤트를 삭제하지 않고 새로운 되돌리기 이벤트를 추가하여 수정합니다.
- 적용 — 데이터베이스 수준의 규칙이나 트리거가 실수로 인한 변경을 막습니다.
- 확장성 — 범위별 분할을 사용하면 대규모 이벤트 로그의 성능을 시간이 지나도 유지할 수 있습니다.
추가 전용 테이블은 이벤트 소싱, CQRS 아키텍처, 그리고 감사 가능성과 이력의 정확성이 중요한 모든 시스템의 중추입니다.
자주 묻는 질문
“추가 전용 이벤트 테이블” 강의는 무료인가요?
네 — “추가 전용 이벤트 테이블” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 SQL Academy 강의 전체를 잠금 해제할 수 있습니다. SQL Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“추가 전용 이벤트 테이블”에서 뭘 배우나요?
발생한 일을 기록하고 기존 데이터를 덮어쓰지 않습니다 브라우저에서 직접 실행하는 실습 코드로 SQL Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
SQL Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 SQL Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.
“추가 전용 이벤트 테이블” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 SQL Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 SQL Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 이력을 보관해야 하는 이유
- 추가 전용 이벤트 테이블
- 시간 및 버전 관리 행
- 이벤트에서 상태 재구성하기