Blog

Drizzle ORM, Neon Postgres, and typed publication state

Naly技術ノート:Drizzle ORM、Neon Postgres、および型付き公開状態

Nalyのコンテンツ公開レイヤーは、Drizzle ORMのスキーマを通じてすべてのワークフロー手順をNeon Postgresに保存することで決定論的に運用できます。この設計は、公開制御をインメモリ前提から、リトライ、クラッシュ、再デプロイでも保持される型付きレコードへ移します。これはNext.jsのサーバレス実行と相性が良い。

June 26, 20268 sources

要約

このスタックは、Nalyに型付きの公開制御プレーンを提供します。Drizzle ORMは記事、予測、ソース、ソーシャル投稿、報酬、cronの各レコードをNeon Postgres内のリレーショナル実体として保持し、散在する実行時状態ではなく管理します。各ワークフロー手順が明示的な行とenum状態として保存されるため、Nalyはcronパイプラインを安全に再実行でき、部分的障害から回復し、編集者向けUIデータをAPI公開状態と整合させたままにできます。2026年6月26日現在、これは予測公開に関するこのプロジェクトの実運用上の耐久契約です。

Nalyにおける位置づけ

現行スタックでは、公開挙動は複数のNext.jsアプリ経路とcronジョブで共有され、いずれも同じデータベース契約に解決されます。現在稼働しているパッケージセットは—next@16.0.7, react@19.2.1, drizzle-orm@^0.44.7, @neondatabase/serverless@^1.0.2, tsx@^4.21.0, typescript@^5.9.3—サーバーサイドのルート/アクションとスケジュールワーカー内で動作する、serverful寄りORMレイヤーに一致します。

Nalyではこれらの領域を第一級テーブルとして保存します:

  • 記事と予測レコード
  • ソースURLと由来スナップショット
  • ソーシャル投稿+配信メタデータ
  • 報酬スコア、キャリブレーションメタデータ、監査フィールド
  • cron実行メタデータと公開チェックポイント

価値は単なる永続化だけではありません。共有セマンティクスにあります。すべてのレンダラー、ワーカー、APIアクションが同じ公開状態モデルを読み取り、隠れたプロセス間フラグなしで連携できます。

技術的な仕組み

Drizzleはこの種の共有セマンティクスに対して、スキーマファーストのアプローチを提供します。Drizzleのガイドは標準的な流れを示します。TypeScriptでスキーマを定義し、Neonの DATABASE_URL, drizzleを初期化して、生成されたクエリ型をinserts/reads/updatesに使用する(概要ドキュメント, Neonチュートリアル)。

Naly向けの最小限のドライバー初期化パターンは次のとおりです:

import { drizzle } from 'drizzle-orm/neon-http';
import { pgTable, text, timestamp, pgEnum, integer } from 'drizzle-orm/pg-core';

export const publicationState = pgEnum('publication_state', [
  'queued', 'draft_ready', 'published', 'failed',
]);

export const publications = pgTable('publications', {
  id: text('id').primaryKey(),
  slug: text('slug').notNull().unique(),
  state: publicationState('state').notNull(),
  stateVersion: integer('state_version').notNull().default(1),
  stateChangedAt: timestamp('state_changed_at').notNull().defaultNow(),
});

const db = drizzle(process.env.DATABASE_URL!);

これは、Drizzleドキュメントで示される内容と一致します。 drizzle-orm/neon-http また drizzle-orm/neon-serverless は、Neon向けのトランスポート選択肢として、1回限りのワークロードにはHTTPを、必要時は対話的トランザクション作業のためにWebSocketライクなセッション挙動を提供します。Drizzleの pg-core 定義は型推論も有効にし($inferInsert, $inferSelect)ため、公開ペイロードはTypeScriptでコンパイル時に検証され、重要でないメタデータは柔軟なJSONを維持できます。

Nalyにとって重要なアーキテクチャパターンは次のとおりです:

  1. 状態遷移(queued -> drafted -> approved -> published -> archived)を明示的な行として定義し、
  2. 遷移ロジックを1つのサーバー専用モジュールに保持し、
  3. 各変更を冪等キー(job id + state hash)で記録し、
  4. 原子的整合性が重要な重要遷移はデータベーストランザクション内で実行し、
  5. 状態が進んだ後にのみ不変アーティファクト(例:blob URL、ソーシャルコピー、スキーマスナップショット)を生成します。

その効果は、cronワーカー内部の暗黙的な振る舞いではなく、厳密なスキーマ契約に基づくPostgres内の小さな有限状態機械と同等です。

文献的見解

主要なドキュメントは、この設計をサーバーレスシステム向けの実用的選択として位置づけています。

  • Drizzleは設計上SQLライクでサーバーレス対応として提示されており、直接SQLセマンティクスを求め、型安全性を維持したいチーム向けにORM抽象化のオーバーヘッドを減らします(Drizzle Overview)。
  • Drizzle/Neonチュートリアルは、ネイティブなNeon HTTP/WebSocketドライバーの組み合わせと型付きスキーマファーストのモデリングを明示的にサポートし、 @neondatabase/serverless 統合と型推論の例(Drizzle with Neon, Get Started with Drizzle + Neon)を提供します。
  • Drizzleの接続マトリクスは実行時ドライバーの明示的分離を示しており、チームは実行モードをワークロードと実行時制約に合わせて選択できます(Database connection docs)。
  • Neonのドライバードキュメントとエッジ向けガイダンスでは、サーバーレス/Postgresアクセスは多くの場合、HTTPまたはWebSocketベースのプロキシで達成されると明示されており、これがワークロード別にエッジ実行の判断を明確にする必要がある理由です(Neon serverless driver, How to use Postgres at the Edge)。

スキーマ側では、スキーマ進化の研究により、明示的な移行と状態モデルがなぜ重要かが示されています。Tesseractは、スキーマ進化は第一級のトランザクション操作として扱うべきで、堅牢なシステムは設計上停止時間を最小化すべきだと主張します(online schema evolution)。EvoSchemaは、スキーマ変更—特にテーブルレベルの揺らぎ—が下流の挙動を不安定化しうることを示し、公開/状態テーブルへの場当たり的な追加に対する強い警告を与えます(EvoSchema)。

設計トレードオフ

Nalyの選択は実質的に厳密性と摩擦のトレードオフです。強く型付けされたスキーマと明示的な状態enumは観測可能性と信頼性を高めますが、初期のモデリングコストを上げ、移行管理の厳格さを要求します。このトレードの曲線は、公開ロジックがcronワーカー、AIパイプライン、公開ページレンダラー全体で共有されるほど有利になります。

  • ドライバー選択: neon-http はよりシンプルで、1回限りの操作ではしばしば高速です。 neon-serverless は、対話的セッションが必要な場合に適しています。
  • スキーマファースト設計: コンパイル時の安全性により実行時エラーを削減できますが、スキーマ変更には移行計画が必要で、テストで状態遷移が網羅されていない場合はデプロイが停止することがあります。
  • 実行時移植性: Neonのエッジ対応ドライバーモデルはデプロイ選択肢を広げますが、トランスポートモードはセッション挙動、レイテンシ特性、認証/TLS経路に影響します。
  • 型忠実性対運用性: 明示的なenumは無効な状態を防ぎますが、移行スクリプトが事前承認されていないと夜間のホットフィックスが遅れることがあります。

障害モード

  • ワークロードに対する不適切なドライバー: マルチステップの状態遷移に1回限りのHTTPクエリを使うと、原子性の保証を失い、公開状態が部分的にしか成立しないことがあります。
  • ワーカーとデプロイ間のスキーマドリフト: publication_state 値が調整された移行なしで変更されると、古いcronコードが無効な状態を書き込む可能性があります。
  • 非冪等な再試行: cronの再起動では、チェックポイントがジョブIDごとに冪等かつ一意でないとソーシャル書き込みが重複する可能性があります。
  • 移行遅延: ローリングデプロイ時に特に、更新されていないスキーマスナップショットに対してスケジュールジョブが実行されると失敗することがあります。
  • エッジのコールドスタート+認証オーバーヘッド: 制約の厳しいエッジ層では、接続セットアップの反復がレイテンシを増加させ、タイムアウト予算とジョブの同時送出数を調整しないと誤ったタイムアウトを引き起こします。

このテーマでは、失敗分析において公開状態を実装の詳細ではなく正確性の成果物として扱うべきです。あらゆる状態遷移は再実行安全でなければなりません。

実装ノート

  • スキーマファイルを集中管理・バージョン管理し、移行アーティファクトは単一の信頼できる情報源から再生成します。
  • 可変ドメインテーブルと不変アーティファクトテーブルを分離します。
  • 公開遷移をトランザクション境界としてモデル化し、不変条件を強制します(例:直接 queued -> published ジャンプしない)。
  • スケジューラメタデータ(last_run, next_run_at, error_count)をアラートと監査用の独自テーブルに保存します。
  • DB初期化はサーバー専用モジュールで行うことを推奨します(DATABASE_URL cron/runtime環境内 .env.local で)。
  • ほとんどの操作では構造化クエリを使用し、移行シードまたはレポート生成にのみ生SQLを使用します。
  • スキーマが進化する場合は stateVersion スナップショットとイベントログを互換性のブリッジとして扱います。

参考文献

Sources