**PostgreSQL をイベント駆動型システムのデッドレターキューとして活用する**

---

### イントロダクション

イベント駆動アーキテクチャは信頼性の高いメッセージ配信に依存しますが、失敗は避けられません。  
**デッドレターレイヤ(DLQ)** は、何度も試みた結果処理できなかったメッセージを保持する仕組みです。専門の DLQ サービスは存在しますが、PostgreSQL を使えば効率的かつコスト効果の高い実装が可能です。

---

### PostgreSQL を選ぶ理由

| 機能 | メリット |
|------|----------|
| ACID 対応 | 失敗したイベントのデータ整合性を保証 |
| 成熟したツール(pg_dump、論理レプリケーション) | バックアップ・移行が容易 |
| 強力なインデックスとクエリ性能 | 問題メッセージの高速検索 |
| JSONB サポート | イベントペイロードをネイティブに格納可能 |
| 柔軟なアクセス制御 | DLQ テーブルへの厳密な権限付与が実現 |

---

### 一般的なアーキテクチャ

1. **プロデューサ** が `events` というプライマリーテーブルにイベントを書き込みます。  
2. **コンシューマ** は `SELECT FOR UPDATE SKIP LOCKED` を用いて行を取得し処理します。  
3. 処理失敗時、コンシューマは該当行を `dead_letter` テーブルへ挿入し、必要に応じて `events` から削除します。

```sql
INSERT INTO dead_letter (event_id, payload, error_message, attempted_at)
SELECT id, data, 'Processing timeout', NOW()
FROM events
WHERE id = $1;

DELETE FROM events WHERE id = $1;
```

4. **監視** は `dead_letter` テーブルをクエリしてアラートやダッシュボードへ情報を提供します。

---

### ベストプラクティス

- **`attempted_at` にインデックス** を張ることで古い DLQ エントリのクリーンアップが高速化。  
- **日付でパーティション分割** すると各パーティションを小さく保ち、VACUUM の性能向上に寄与。  
- **保持ポリシー** を設定し、X 日より古いレコードは定期的(例:毎夜)にアーカイブまたは削除。  
- **スキーマバージョニング** で DLQ スキーマをメインイベントスキーマと同期。

---

### サンプルスキーマ

```sql
CREATE TABLE dead_letter (
    id          BIGSERIAL PRIMARY KEY,
    event_id    BIGINT NOT NULL,
    payload     JSONB  NOT NULL,
    error_message TEXT   NOT NULL,
    attempted_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
    CONSTRAINT fk_event FOREIGN KEY (event_id) REFERENCES events(id)
);
```

---

### モニタリングとアラート

- **Prometheus エクスポーター** を用い、`dead_letter_total` や `dead_letter_age_seconds` などのメトリクスを公開。  
- カウントが閾値を超えた場合や age > 24 時間になった場合にアラートを発火。

---

### 結論

PostgreSQL を DLQ として利用することで、既存インフラをそのまま活用でき、運用負荷を低減しつつ強力なクエリ機能を手に入れられます。すでに PostgreSQL にコミットしているチームにとっては、イベント駆動型システムの失敗イベントを安全かつ簡潔に扱うための実用的で信頼性の高いソリューションとなります。

2026/01/26 0:51

**PostgreSQL をイベント駆動型システムのデッドレターキューとして活用する** --- ### イントロダクション イベント駆動アーキテクチャは信頼性の高いメッセージ配信に依存しますが、失敗は避けられません。 **デッドレターレイヤ(DLQ)** は、何度も試みた結果処理できなかったメッセージを保持する仕組みです。専門の DLQ サービスは存在しますが、PostgreSQL を使えば効率的かつコスト効果の高い実装が可能です。 --- ### PostgreSQL を選ぶ理由 | 機能 | メリット | |------|----------| | ACID 対応 | 失敗したイベントのデータ整合性を保証 | | 成熟したツール(pg_dump、論理レプリケーション) | バックアップ・移行が容易 | | 強力なインデックスとクエリ性能 | 問題メッセージの高速検索 | | JSONB サポート | イベントペイロードをネイティブに格納可能 | | 柔軟なアクセス制御 | DLQ テーブルへの厳密な権限付与が実現 | --- ### 一般的なアーキテクチャ 1. **プロデューサ** が `events` というプライマリーテーブルにイベントを書き込みます。 2. **コンシューマ** は `SELECT FOR UPDATE SKIP LOCKED` を用いて行を取得し処理します。 3. 処理失敗時、コンシューマは該当行を `dead_letter` テーブルへ挿入し、必要に応じて `events` から削除します。 ```sql INSERT INTO dead_letter (event_id, payload, error_message, attempted_at) SELECT id, data, 'Processing timeout', NOW() FROM events WHERE id = $1; DELETE FROM events WHERE id = $1; ``` 4. **監視** は `dead_letter` テーブルをクエリしてアラートやダッシュボードへ情報を提供します。 --- ### ベストプラクティス - **`attempted_at` にインデックス** を張ることで古い DLQ エントリのクリーンアップが高速化。 - **日付でパーティション分割** すると各パーティションを小さく保ち、VACUUM の性能向上に寄与。 - **保持ポリシー** を設定し、X 日より古いレコードは定期的(例:毎夜)にアーカイブまたは削除。 - **スキーマバージョニング** で DLQ スキーマをメインイベントスキーマと同期。 --- ### サンプルスキーマ ```sql CREATE TABLE dead_letter ( id BIGSERIAL PRIMARY KEY, event_id BIGINT NOT NULL, payload JSONB NOT NULL, error_message TEXT NOT NULL, attempted_at TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_event FOREIGN KEY (event_id) REFERENCES events(id) ); ``` --- ### モニタリングとアラート - **Prometheus エクスポーター** を用い、`dead_letter_total` や `dead_letter_age_seconds` などのメトリクスを公開。 - カウントが閾値を超えた場合や age > 24 時間になった場合にアラートを発火。 --- ### 結論 PostgreSQL を DLQ として利用することで、既存インフラをそのまま活用でき、運用負荷を低減しつつ強力なクエリ機能を手に入れられます。すでに PostgreSQL にコミットしているチームにとっては、イベント駆動型システムの失敗イベントを安全かつ簡潔に扱うための実用的で信頼性の高いソリューションとなります。

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

Wayfairは、処理できなかったKafkaイベントをDLQトピックから専用のPostgreSQLテーブル(

dlq_events
)に移動しました。この変更により、エンジニアは単純なSQLで失敗を確認し、ステータスや再試行時間でインデックス化でき、CloudSQL の耐久性を活用できます。ShedLockで保護されたスケジューラが6時間ごとに実行され、
SELECT … FOR UPDATE SKIP LOCKED
クエリ(ロックタイムアウトヒント付き)を使用して最大50件の
PENDING
行を選択し、各イベントを 240 回まで 再試行します。成功した場合はステータスを
SUCCEEDED
に更新し、失敗したものは後続実行時に再び
PENDING
のまま残ります。テーブル構造には、イベントタイプ、ペイロード、エラー理由/スタックトレース、再試行回数、再試行期限、タイムスタンプが格納されており、
status
(status,retry_after)
event_type
created_at
にインデックスがあります。このアーキテクチャにより、Kafka は高スループットの取り込みバックボーンとして機能しつつ、エンジニアはPostgreSQL内で直接クエリ可能な予測可能かつ観測可能な失敗処理を実現できます。

本文

Wayfair のプロジェクトに取り組んでいたとき、複数のデータソースから流れるイベントストリームを集約し、日次ビジネスレポートを生成するシステムを構築する機会がありました。大まかな仕組みとしては、Kafka コンシューマがイベントを受け取り、下流サービスへ呼び出して追加データで補完(ハイドレーション)し、最終的に Enriched なイベントを耐久性のあるストレージ―GCP 上の CloudSQL PostgreSQL―に永続化するというものです。

すべてが正常な状態ではパイプラインは想定通り動作しました。イベントが流れ込み、補完され、確実に保存されます。しかし、分散システムにおいて問題が起きることは例外ではなく常態です。そのため次のような複数の障害シナリオに対処する必要がありました。

  • 補完に依存している API が停止または遅延
  • コンシューマ自体が途中でクラッシュ
  • イベントが欠損や不正フォーマットで到着し、安全に処理できない

これらは私たちの直接的な制御外ですが、優雅に対処する必要があります。そこで Dead Letter Queue(DLQ)の概念を導入しました。イベントが正常に処理できないことが判明した場合、破棄せず、全コンシューマをブロックせずに DLQ へリダイレクトし、後で検査・再処理できるようにします。

最初は Kafka 自体を DLQ として利用する案を試みました。これは一般的なパターンですが、我々のニーズには合わないことが早く分かりました。Kafka はデータ移動に優れていますが、一旦 DLQ トピックにメッセージが届いてしまうと検査が難しくなります。失敗理由でクエリしたり、特定サブセットを再試行したり、「昨日何が失敗して何故か?」といった簡単な質問にも追加ツールやカスタムコンシューマが必要です。ビジネスクリティカルな日次レポートを支えるシステムにとって、この可視性の欠如は重大なデメリットでした。

PostgreSQL を DLQ として活用

我々は PostgreSQL 自体を Dead Letter Queue として扱うことにしました。

  • 失敗したイベントを別の Kafka トピックへ送る代わりに、直接
    dlq_events
    テーブルに永続化
  • CloudSQL を耐久ストアとして既に利用していたため、運用上ほとんど追加負担がない
  • 概念的にも失敗を「流れの中で見えなくなるメッセージ」ではなく、システム内で明示的な存在にする

イベントが API の失敗・コンシューマクラッシュ・スキーマ不一致・検証エラーなどで処理できなかった場合、原始的なペイロードとともに失敗の文脈情報を保存しました。各レコードはシンプルな状態フィールドを持ちます。

ステータス意味
PENDING
DLQ に最初に到着した時
SUCCEEDED
再処理が成功した時

ステートモデルを意図的に minimal に保つことで、失敗イベントのライフサイクルを簡潔に理解できます。

DLQ テーブルスキーマ

CREATE TABLE dlq_events (
    id              BIGSERIAL PRIMARY KEY,
    event_type      VARCHAR(255) NOT NULL,
    payload         JSONB        NOT NULL,
    error_reason    TEXT         NOT NULL,
    error_stacktrace TEXT,
    status          VARCHAR(20)  NOT NULL, -- PENDING / SUCCEEDED
    retry_count     INT           NOT NULL DEFAULT 0,
    retry_after     TIMESTAMP WITH TIME ZONE NOT NULL,
    created_at      TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
    updated_at      TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);

主な設計考慮点

  • payload
    を JSONB で保存し、厳格なスキーマを強制せず原始データを保持
  • status
    によりライフサイクルを明示的に管理
  • retry_after
    は下流が不安定なときの過度な再試行を防止
  • retry_count
    で外部状態を持たずにリトライ制限を実装
  • タイムスタンプは監査・運用分析を容易に

インデックス

CREATE INDEX idx_dlq_status ON dlq_events (status);
CREATE INDEX idx_dlq_status_retry_after ON dlq_events (status, retry_after);
CREATE INDEX idx_dlq_event_type ON dlq_events (event_type);
CREATE INDEX idx_dlq_created_at ON dlq_events (created_at);

これらのインデックスにより、リトライスケジューラは適格なイベントを効率的に探しつつ、全テーブル走査なしで高速デバッグや時間ベース分析も可能です。

ShedLock を利用した DLQ リトライ機構

可視性の問題は解決しましたが、リトライ自体を安全かつ信頼できる方法で行う必要があります。そこで ShedLock による DLQ リトライスケジューラを導入しました。このスケジューラは定期的に DLQ テーブルから

PENDING
かつリトライ可能なイベントを取得し、再処理を試みます。サービスが複数インスタンスで稼働しているため、ShedLock は「いつも一つのインスタンスだけがリトライジョブを実行する」ことを保証し、重複リトライを排除します。

リトライ設定

dlq:
  retry:
    enabled: true
    max-retries: 240
    batch-size: 50
    fixed-rate: 21600000   # 6 時間(ミリ秒)

リトライの流れ

  1. スケジューラは毎 6 時間に実行
  2. 一回の実行で最大 50 件までの適格イベントを取得
  3. 最大リトライ数を超えたものはスキップ
  4. 成功したリトライは即座に
    SUCCEEDED
    にステータス変更
  5. 失敗は
    PENDING
    のまま次回実行で再試行

クエリ実装

スケジューラは

FOR UPDATE SKIP LOCKED
を用いた SQL クエリで、複数インスタンス間で安全に適格イベントを選択します。

@QueryHints(@QueryHint(name = "jakarta.persistence.lock.timeout", value = "-2"))
@Query(
    value = """
        SELECT *
          FROM dlq_events
         WHERE event_type = :messageType
           AND retry_count < :maxRetries
           AND (status IS NULL OR status NOT IN ('SUCCEEDED'))
         ORDER BY created_at ASC
         FOR UPDATE SKIP LOCKED""",
    nativeQuery = true)

FOR UPDATE SKIP LOCKED
は、同時に実行されるトランザクション間で重複処理を防ぎつつスループットを確保します。ロックタイムアウト
-2
(永遠に待機)は
SKIP LOCKED
と組み合わせることで「既に他のトランザクションがロックしている行はスキップ」する意味になります。

この構成により、下流障害が長時間続いてもリトライストームや不要な負荷を抑えつつ、システムは耐久性を保てました。

運用上のメリット

  • 失敗が予測可能かつ観察可能になり、破壊的ではなくなる
  • エンジニアは単純な SQL で失敗を検査し、必要なイベントだけ再処理できる
  • 下流依存サービスが数時間・数日停止しても、DLQ に安全に蓄積され、後で再試行
  • 根本的に不良なイベントは見えたままで、静かに捨てられない

最も重要なのは、運用ストレスを大幅に軽減したことです。失敗が「恐れるべきもの」ではなく、明確で監査可能な回復経路を持つシステムの一部となりました。

私の考え

Kafka を PostgreSQL に置き換えることは決して目的ではありませんでした。Kafka は高スループットのイベント取り込みに引け目なし、PostgreSQL は耐久性・クエリ性能・失敗時の可視化を担いました。各システムが得意分野で役割を果たすことで、レジリエントかつデバッグしやすく、運用も楽なパイプラインを実現できました。

結局、PostgreSQL を Dead Letter Queue として使うことは、失敗処理を「退屈で予測可能」に変え、プロダクション環境において正に望ましい結果を生み出しました。

同じ日のほかのニュース

一覧に戻る →

2026/01/26 4:03

まず、私に関心を持っていただけるようにしてほしいです。

## Japanese Translation: **改訂要約:** 著者は、ウェブサイト、個人の「Me」セクション、ブログを組み合わせた新しいプラットフォームを立ち上げました。外部リソースへのリンクとファンサポート用のPatreonページが特徴です。今後の計画としては、さらに多くのブログ投稿を追加し、リンク統合を拡大し、場合によっては追加のPatreonティアを作成することがあります。これによりユーザーはキュレーションされたコンテンツへ簡単にアクセスでき、クリエイターには追加収益源が提供されます。 (このバージョンではすべての主要ポイントを保持し、推測された業界全体のトレンド表現を削除しています。)

2026/01/26 9:10

**科学者たちが「あなた」の限界を決める脳波を特定**

## Japanese Translation: > 本研究は、頭頂皮質におけるアルファ振動のリズムが、人々がゴム手を所有していると感じる強さを因果的に形成することを示しています。スウェーデンの研究者66名とフランスの研究者43名が合計106名の被験者から脳波(EEG)を記録し、古典的なゴム手錯覚を体験させました。ロボットアームは実際の手と偽の手の両方にタップを打ち、タップの同期性は最大500 msまで遅延させられました。 > > 被験者はタップが同期しているときに最も強い所有感を報告し、遅延が増すにつれてその感覚が弱まりました。EEGからは、頭頂部のアルファ周波数が被験者の遅延検知能力と相関していることが明らかになりました:高速なアルファ波は小さな時間ギャップに対する敏感性を高め、一方で低速なアルファ波は大きなギャップが存在しても錯覚を強化しました。 > > 頭頂部のアルファ波を人工的に高速化または遅延させる経頭蓋交流電流刺激(tACS)を用いて、研究者たちは因果関係を確認しました——アルファ波を加速すると所有感が増し、逆に減速すると錯覚が弱まり、自分の手と偽手との区別が難しくなりました。 > > これらの結果は *Nature Communications* に掲載され、頭頂部のアルファ活動が身体所有感を構築する上で因果的役割を果たすことを示唆しています。精神疾患(統合失調症や幻肢痛など)の治療に寄与したり、ターゲットとした脳刺激によってよりリアルな義手や仮想現実インターフェースの設計指針となる可能性があります。

2026/01/26 0:34

**タイトル** 「姿勢が悪くなると画面をぼかす macOS アプリ」

## Japanese Translation: Posturrは、MacのカメラとAppleのVisionフレームワークを使用してリアルタイムで姿勢を監視する軽量macOSアプリです。鼻と肩の角度を測定し、全身ランドマークが利用できない場合は顔検出にフォールバックします。前かがみが検知されると、画面が徐々にぼかれます(デフォルトではmacOSのプライベートCoreGraphics APIを使用し、互換モードではNSVisualEffectViewにフォールバック)。良い姿勢が回復するとぼかしは即座に消えます。ぼかしの強度は「Low」から「Very High」に段階的に上昇し、デッドゾーン設定で軽微な前かがみを無視できます。 Posturrは完全にローカルで動作します:動画データはマシンを離れず、オンラインアカウントやクラウドサービスも不要です。MITライセンスのソースコードはコミュニティへの貢献を歓迎しています。メニューバーには小さなアイコンがあり、ステータス表示、監視の有効/無効化、再校正、感度調整、互換モード切替、デッドゾーン設定、またアプリ終了などが可能です。また、外部制御用に`/tmp/posturr-command`というファイルベースのコマンドインターフェイス(`capture`、`blur <0‑64>`、`quit`)も公開しています。 インストールは簡単で、Homebrew(`brew install --cask posturr`)を使用するか、リリースページから署名済みのDMG/ZIPをダウンロードします。ノタリゼーションされており、システム設定でカメラ権限が必要です。最適な結果を得るためには、カメラを目線レベルに位置させ、十分な照明を確保し、画面を向いて座り続け、肩が見えるようにしてください。Posturrはマルチディスプレイ環境にも対応しており、macOS 13+でXcode Command Line Toolsを使用してソースからビルドできます。 すべての処理をデバイス上で完結させ、プライバシー優先設計を提供することで、Posturrは姿勢モニタリングツールがユーザーのプライバシーを侵害せずにエルゴノミック支援を行う方法を示しています。

**PostgreSQL をイベント駆動型システムのデッドレターキューとして活用する** --- ### イントロダクション イベント駆動アーキテクチャは信頼性の高いメッセージ配信に依存しますが、失敗は避けられません。 **デッドレターレイヤ(DLQ)** は、何度も試みた結果処理できなかったメッセージを保持する仕組みです。専門の DLQ サービスは存在しますが、PostgreSQL を使えば効率的かつコスト効果の高い実装が可能です。 --- ### PostgreSQL を選ぶ理由 | 機能 | メリット | |------|----------| | ACID 対応 | 失敗したイベントのデータ整合性を保証 | | 成熟したツール(pg_dump、論理レプリケーション) | バックアップ・移行が容易 | | 強力なインデックスとクエリ性能 | 問題メッセージの高速検索 | | JSONB サポート | イベントペイロードをネイティブに格納可能 | | 柔軟なアクセス制御 | DLQ テーブルへの厳密な権限付与が実現 | --- ### 一般的なアーキテクチャ 1. **プロデューサ** が `events` というプライマリーテーブルにイベントを書き込みます。 2. **コンシューマ** は `SELECT FOR UPDATE SKIP LOCKED` を用いて行を取得し処理します。 3. 処理失敗時、コンシューマは該当行を `dead_letter` テーブルへ挿入し、必要に応じて `events` から削除します。 ```sql INSERT INTO dead_letter (event_id, payload, error_message, attempted_at) SELECT id, data, 'Processing timeout', NOW() FROM events WHERE id = $1; DELETE FROM events WHERE id = $1; ``` 4. **監視** は `dead_letter` テーブルをクエリしてアラートやダッシュボードへ情報を提供します。 --- ### ベストプラクティス - **`attempted_at` にインデックス** を張ることで古い DLQ エントリのクリーンアップが高速化。 - **日付でパーティション分割** すると各パーティションを小さく保ち、VACUUM の性能向上に寄与。 - **保持ポリシー** を設定し、X 日より古いレコードは定期的(例:毎夜)にアーカイブまたは削除。 - **スキーマバージョニング** で DLQ スキーマをメインイベントスキーマと同期。 --- ### サンプルスキーマ ```sql CREATE TABLE dead_letter ( id BIGSERIAL PRIMARY KEY, event_id BIGINT NOT NULL, payload JSONB NOT NULL, error_message TEXT NOT NULL, attempted_at TIMESTAMP WITH TIME ZONE DEFAULT now(), CONSTRAINT fk_event FOREIGN KEY (event_id) REFERENCES events(id) ); ``` --- ### モニタリングとアラート - **Prometheus エクスポーター** を用い、`dead_letter_total` や `dead_letter_age_seconds` などのメトリクスを公開。 - カウントが閾値を超えた場合や age > 24 時間になった場合にアラートを発火。 --- ### 結論 PostgreSQL を DLQ として利用することで、既存インフラをそのまま活用でき、運用負荷を低減しつつ強力なクエリ機能を手に入れられます。すでに PostgreSQL にコミットしているチームにとっては、イベント駆動型システムの失敗イベントを安全かつ簡潔に扱うための実用的で信頼性の高いソリューションとなります。 | そっか~ニュース