0Pricing
SQL Academy · レッスン

追記専用イベントテーブル

起きたことを記録し、決して上書きしません

「追記専用イベントテーブル」はCoddyKit上の無料SQL Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応の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;

ルールによる不変性の強制

追記専用であることは、テーブルに対するUPDATEやDELETEを静かに無視するRULEを使用して、データベースレベルで強制できます。これにより、書き込み権限を持つどのアプリケーションからの誤った変更も防止できます。

例外を発生させるトリガーは、さらに強力な代替手段です。操作をエラーとして明示的に拒否できるためです。

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();

時間経過に伴うイベント数の集計

追記専用テーブルを使うと、時間ベースの分析を簡単に実行できます。すべてのイベントにタイムスタンプがあるため、追加の列なしで日、週、月ごとにグループ化できます。次の例では、1日あたりに発生したイベントの種類ごとの件数を集計します。

SELECT
  DATE_TRUNC('day', occurred_at) AS day,
  event_type,
  COUNT(*)                        AS total
FROM account_events
GROUP BY 1, 2
ORDER BY 1, 2;

時点での状態の再構築

イベントログの最も強力な特性の1つは、任意のレコードが過去のある時点で存在していた状態を再構築できることです。目的のタイムスタンプまでのイベントに絞り込むだけでよく、タイムトラベル拡張は必要ありません。

これはデバッグ、監査、規制遵守に非常に役立ちます。

-- 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時間対応のAIチューター)、SQL Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 SQL Academyコースには全4レッスンが含まれています。

「追記専用イベントテーブル」で何を学びますか?

起きたことを記録し、決して上書きしません ブラウザで直接実行するハンズオンコードでSQL Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

SQL Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのSQL Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「追記専用イベントテーブル」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このSQL Academyレッスンでコードを書いて実行できますか?

はい。すべてのSQL Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. 履歴を保持する理由
  2. 追記専用イベントテーブル
  3. 時間的行とバージョン管理された行
  4. イベントから状態を再構築する
← SQL Academyに戻る