Firebase脱却戦略:SupabaseとRLSで構築するスケーラブルなバックエンド

Database tutorial - IT technology blog
Database tutorial - IT technology blog

スケーリングの壁:Firebaseが苦痛に変わる時

Firebaseは開発者にとって究極の「ハネムーン」と言えます。データベース、認証、ホスティングを10分足らずで構築できるのは、信じられないほど快適な体験です。

しかし、プロジェクトが成長するにつれて、その関係が悪化することが少なくありません。最初はシンプルなチャットアプリから始まりますが、突然、5つの異なるデータコレクションにまたがる結合(Join)が必要な複雑なダッシュボードをクライアントが要求してきます。FirestoreのようなNoSQLの世界では、クライアント側で処理するために数千件のレコードを取得してパフォーマンスを低下させるか、あちこちに重複したデータを保持するという煩雑な管理を強いられます。

最近、ある本番アプリを移行しましたが、比較的少数のユーザー数にもかかわらず、Firebaseの読み取りコストが月額400ドルに達していました。データの一貫性は悪夢のような状態でした。金曜の夜に、ユーザーのプロフィールのアバターは更新されたのに、投稿のアバターが古いままなのはなぜかとデバッグしたことがあるなら、あなたは「スケーリングの壁」に突き当たっています。

なぜリレーショナルデータはNoSQLで停滞するのか

問題はFirebaseの作りが悪いことではなく、構造化されたリレーショナルデータに対してNoSQLが誤ったツールであることが多い点にあります。ほとんどのアプリは本質的にリレーショナルです。ユーザーには投稿があり、投稿にはコメントがあり、コメントには「いいね」があります。

Firestoreでは、ネスト(入れ子)にするか、フラットなコレクションにするかという, 2つの不自由な選択を迫られます。ネストは独立したクエリをほぼ不可能にし、一方でフラットなコレクションはフロントエンド側で手動の「結合」コードを書く必要があります。このアーキテクチャの不一致は、以下の3つの苦痛を生みます。

  • 冗長性のコスト(税金): 追加の読み取りを避けるために同じユーザー名を10個の異なるドキュメントに保存することになり、ストレージと帯域幅を浪費します。
  • ゴーストデータ: 複雑でコストのかかるトランザクションなしに、10箇所の情報を一括更新して100%成功させることは困難です。
  • セキュリティルールの肥大化: Firebaseのセキュリティルールは独自の仕様です。500行を超えると, 検索不能なブラックボックスとなり、監査するのが恐ろしくなります。

解決策:SQLの頭脳を持つBaaS의シンプルさ

Firebaseで対応できなくなった時の従来の解決策は、Node.jsやGoを使用し、PostgreSQLデータベースを組み合わせたカスタムバックエンドを構築することでした。これは完全な制御を可能にしますが、Firebaseのような「ライブ感」や、すぐに使える認証機能という生産性は失われます。また、サーバーインフラの管理という負担も引き受けることになります。

Supabaseは、よりスマートな「第三の道」を提示します。これは独自のクローズドな環境ではなく、PostgreSQL、認証用のGoTrue、Realtimeといったオープンソースの強力なツール群を一つの体験としてパッケージ化したものです。SQLの堅牢な構造と、モダンなBaaSの流れるような開発体験を同時に得ることができます。

Supabaseの柱:セキュリティとライブアップデート

Firebaseを置き換えるには、単なるテーブル構造以上のものが必要です。強固なセキュリティ層と、ユーザーに即座に更新をプッシュする機能が求められます。

1. PostgreSQLと行レベルセキュリティ(RLS)

独自のJSON風言語でルールを書く必要はありません。SupabaseはPostgreSQLのネイティブ機能である行レベルセキュリティ(RLS)を利用します。セキュリティはアプリケーションコードではなく、データベース内部に存在します。たとえ悪意のある者が公開APIキーを盗んだとしても、データベース自体が認証されたユーザーIDと一致しない行の返却を拒否します。

-- 例:シンプルで堅牢なセキュリティポリシー
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;

CREATE POLICY "ユーザーは自身のデータのみ閲覧可能" 
ON profiles FOR SELECT 
USING (auth.uid() = id);

これは安心感という面で大きな利点です。セキュリティロジックは、クラウドプロバイダー独自のスクリプトではなく、30年以上の実績を持つデータベースエンジンであるPostgresによって処理されます。

2. スケーラブルなリアルタイムサブスクリプション

多くの開発者が、リアルタイム同期のためだけにFirebaseを使い続けています。Supabaseは、PostgreSQLのレプリケーションストリーム(WAL)を監視することでこれに対抗します。行が変更されると、サーバーはWebSocketを介してその変更差分をブロードキャストします。以下は、チャットや通知フィードの典型的な実装例です。

import { createClient } from '@supabase/supabase-client'

const supabase = createClient('PROJECT_URL', 'ANON_KEY')

const channel = supabase
  .channel('room-1')
  .on('postgres_changes', 
      { event: 'INSERT', schema: 'public', table: 'messages' }, 
      payload => console.log('新しいメッセージを受信しました!', payload)
  )
  .subscribe()

複雑なJOIN操作や全文検索といった機能を損なうことなく、瞬時のレスポンス性を手に入れることができます。

プロのヒント:移行前のデータクレンジング

移行はデータ負債を解消する絶好の機会です。NoSQLやレガシーシステムからの移行では、雑然としたCSVファイルに苦労することがよくあります。データを素早く準備するために、私は toolcraft.app/ja/tools/data/csv-to-json を使用しています。これはブラウザ上で完全に動作するため、機密データがローカルマシンから離れることはありません。レガシーなエクスポートデータをSupabaseのダッシュボードにインポートする前にフォーマットする最速の方法です。

より良い実装のブループリント

新規プロジェクトを開始する場合は、以下のワークフローに従って、バックエンドを長年管理可能な状態に保ちましょう。

  1. 最初にスキーマを定義する: 適当に進めないでください。外部キーを使用して参照整合性を維持します。これにより、NoSQLアプリを悩ませる「孤立データ(Orphan Data)」のバグを防ぐことができます。
  2. すぐに施錠する: RLSポリシーのないテーブルを放置しないでください。テーブルを作成した瞬間にRLSを有効にします。
  3. 型生成を利用する: SupabaseはスキーマからTypeScriptの型を自動生成できます。これにより、型のないFirebaseドキュメントを扱う際によく発生する「undefined is not a function」エラーを実質的に排除できます。
  4. ロジックをEdge Functionsにオフロードする: Stripeの支払い処理やサードパーティAPIの呼び出しなど、クライアント側で行うべきではないタスクにはSupabase Edge Functionsを使用します。これらは数秒でグローバルにデプロイされるサーバーレスのDeno関数です。

SQLの初期学習曲線はNoSQLよりもわずかに急ですが、長期的な見返りは膨大です。データの関係性が複雑になったからといって、アーキテクチャ全体を書き直す必要はありません。Supabaseは、素早く開始し、かつ正しくスケールさせるための架け橋となります。

Share: