データベースのスケーリング戦略: リードレプリカ、シャーディングなど

リードレプリカ、水平シャーディング、接続プーリング、キャッシュ戦略を使用してデータベースを拡張します。 PostgreSQL、MySQL、およびマネージド データベース サービスをカバーします。

E
ECOSIRE Research and Development Team
|2026年3月16日4 分で読める849 語数|

データベースのスケーリング戦略: リードレプリカ、シャーディングなど

Web アプリケーションのスケーリング問題の 78% において、データベースのパフォーマンスがボトルネックとなっています。 アプリケーションは最小限の労力で水平方向にスケーリングできますが、データベースは水平方向のスケーリングに抵抗します。データベースのスケーリングのために選択した戦略によって、アプリケーションが許容可能なパフォーマンスで 100 ユーザーにサービスを提供するか、100,000 ユーザーにサービスを提供できるかが決まります。

このガイドでは、スケーリングの必要性を遅らせる単純な最適化から、水平シャーディングなどの高度な技術に至るまで、データベースのスケーリング戦略の全範囲をカバーしています。

重要なポイント

  • インフラストラクチャを追加する前にクエリを最適化し、インデックスを追加します --- これにより、データベースのパフォーマンスの問題の 60% が解決されます
  • リードレプリカは最もリスクの低いスケーリング戦略であり、読み取り負荷の高いワークロードの 80% を処理します
  • アプリケーションが 10 を超えるインスタンスを実行する場合、接続プーリングは必須です
  • 水平シャーディングは、アプリケーションが大幅に複雑になる最後の手段です

スケーリングラダー

この順序でスケールします。各ステップは次のステップよりも安価でリスクが低くなります。

ステップ 1: クエリの最適化 (無料)

インフラストラクチャを追加する前に、既存のデータベースが最適に実行されていることを確認してください。

-- Find slow queries in PostgreSQL
SELECT
  calls,
  mean_exec_time::numeric(10,2) AS avg_ms,
  total_exec_time::numeric(10,2) AS total_ms,
  query
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 20;

一般的な最適化:

  • 頻繁にフィルターされる列に欠落しているインデックスを追加する
  • SELECT * を特定の列リストに置き換えます
  • EXPLAIN ANALYZE を使用して、大きなテーブルの順次スキャンを識別します
  • 複数列の WHERE 句に複合インデックスを追加する
  • OFFSET の代わりにキーセット ページネーションを使用してページネーションを実装します。
-- Bad: OFFSET pagination (scans all skipped rows)
SELECT * FROM orders ORDER BY created_at DESC LIMIT 20 OFFSET 10000;

-- Good: Keyset pagination (index-only scan)
SELECT * FROM orders
WHERE created_at < '2026-03-15T10:00:00Z'
ORDER BY created_at DESC
LIMIT 20;

ステップ 2: 垂直スケーリング ($)

既存のデータベース サーバーの CPU、RAM、ストレージを増設します。これにより時間を稼ぐことができ、アプリケーションの変更は必要ありません。

インスタンスのサイズvCPURAM接続月額費用 (RDS)
db.t3.medium24GB100$65
db.r6g.large216GB200$175
db.r6g.xlarge432GB400$350
db.r6g.2xlarge864GB800700ドル

ほとんどのアプリケーションは、64 GB RAM と 8 vCPU で制限に達します。それを超えると、垂直方向のスケーリングには法外なコストがかかります。

ステップ 3: 接続プーリング ($)

Application (50 pods x 20 connections = 1,000 connections)
    |
    v
PgBouncer (25 database connections, transaction pooling)
    |
    v
PostgreSQL (25 active connections, manageable)

PgBouncer の構成:

[databases]
app = host=db.example.com port=5432 dbname=production

[pgbouncer]
listen_port = 6432
listen_addr = 0.0.0.0
auth_type = md5
pool_mode = transaction
default_pool_size = 25
max_client_conn = 1000
min_pool_size = 5
reserve_pool_size = 5
reserve_pool_timeout = 3

ステップ 4: リードレプリカ ($$)

リードレプリカは SELECT クエリを処理し、データベース負荷の 60 ~ 90% をプライマリからオフロードします。

アーキテクチャ:

Write queries --> Primary database
                      |
              Replication (async)
                      |
                 +----+----+
                 |         |
Read queries --> Replica 1  Replica 2

アプリケーションレベルのルーティング (Drizzle ORM の例):

import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';

const primaryPool = new Pool({ connectionString: process.env.DATABASE_URL });
const replicaPool = new Pool({ connectionString: process.env.DATABASE_REPLICA_URL });

export const primaryDb = drizzle(primaryPool);
export const replicaDb = drizzle(replicaPool);

// In service code:
// Write operations use primaryDb
async createOrder(data: OrderInput) {
  return primaryDb.insert(orders).values(data).returning();
}

// Read operations use replicaDb
async getOrders(organizationId: string) {
  return replicaDb.select().from(orders)
    .where(eq(orders.organizationId, organizationId))
    .orderBy(desc(orders.createdAt));
}

レプリケーションの遅延に関する考慮事項: 非同期レプリケーションでは遅延が発生します (通常は 10 ~ 100 ミリ秒)。書き込み直後にレプリカから読み取ると、古いデータが返される場合があります。同じユーザー フロー内の書き込みに続く読み取りにはプライマリを使用します。

ステップ 5: キャッシュ ($$)

Redis キャッシュにより、データベース クエリの繰り返しが完全に排除されます。

async getProduct(id: string): Promise&lt;Product> {
  const cacheKey = `product:${id}`;

  // Check cache first
  const cached = await this.redis.get(cacheKey);
  if (cached) return JSON.parse(cached);

  // Cache miss: query database
  const product = await this.db.select().from(products)
    .where(eq(products.id, id))
    .limit(1);

  // Cache for 5 minutes
  await this.redis.setex(cacheKey, 300, JSON.stringify(product[0]));

  return product[0];
}

キャッシュ無効化戦略: 書き込み時に無効化します。製品をアップデートした場合は、キャッシュキーを削除してください。ライトスルー (データベースがキャッシュを管理する) ではなく、キャッシュアサイド パターン (アプリケーションがキャッシュを管理する) を使用します。

ステップ 6: 水平シャーディング ($$$)

シャーディングは、シャード キーに基づいて複数のデータベース インスタンスにデータを分散します。

シャーディング戦略説明最適な用途
ハッシュベースシャード キーをハッシュし、均等に分散します。均一なデータ分布
範囲ベースシャードに範囲を割り当てる (例: A-M、N-Z)時系列、地理データ
テナントベーステナント/組織ごとに 1 つのシャードマルチテナントSaaS

シャードを行う場合:

  • 単一データベースが 1 TB を超え、増加中
  • 書き込みスループットが単一のプライマリで処理できる量を超えています
  • 垂直スケーリングのコストが月額 2,000 ドルを超え、余裕がない

シャードしない場合:

  • ステップ 1 ~ 5 をまだ完了していません
  • データは単一の 500 GB データベースに収まります
  • アプリケーションではクロスシャードクエリが一般的です

PostgreSQL 固有の最適化

パーティショニング (シャーディング前)

PostgreSQL のテーブル パーティショニングは、単一の論理テーブルを維持しながら、大きなテーブルを小さな物理テーブルに分割します。

-- Partition orders by month
CREATE TABLE orders (
    id UUID PRIMARY KEY,
    organization_id UUID NOT NULL,
    created_at TIMESTAMP NOT NULL,
    total DECIMAL(10,2)
) PARTITION BY RANGE (created_at);

CREATE TABLE orders_2026_01 PARTITION OF orders
    FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');

CREATE TABLE orders_2026_02 PARTITION OF orders
    FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');

CREATE TABLE orders_2026_03 PARTITION OF orders
    FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');

PostgreSQL は関連するパーティションのみをスキャンするため、パーティショニングにより、大きなテーブルでの時間範囲クエリのパフォーマンスが 10 ~ 100 倍向上します。

掃除機とメンテナンス

-- Check table bloat
SELECT
  schemaname,
  relname,
  n_live_tup,
  n_dead_tup,
  round(n_dead_tup::numeric / greatest(n_live_tup, 1) * 100, 2) AS dead_pct
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;

書き込み量の多いテーブルに対して自動バキュームを積極的に構成します。

ALTER TABLE orders SET (
  autovacuum_vacuum_threshold = 100,
  autovacuum_vacuum_scale_factor = 0.05,
  autovacuum_analyze_threshold = 50,
  autovacuum_analyze_scale_factor = 0.02
);

データベースのパフォーマンスの監視

これらのメトリクスを追跡して、いつ、どのようにスケールするかを理解します。

メトリックツールアラートしきい値
クエリレイテンシ (P95)pg_stat_statements>500ミリ秒
アクティブな接続pg_stat_activity最大値の >80%
キャッシュ ヒット率pg_stat_データベース<95%
レプリケーションの遅延pg_stat_replication>1秒
テーブルの肥大化pg_stat_user_tables>20% のデッドタプル
ディスク I/O 待機iostat / CloudWatch>20ms

キャッシュ ヒット率が 95% を下回る場合は、より多くのメモリが必要であることを示す最も強力な指標です。 shared_bufferseffective_cache_size を増やすことは、多くの場合、リードレプリカを追加するよりも安価で高速です。

クエリパフォーマンスの追跡

-- Enable pg_stat_statements (postgresql.conf)
-- shared_preload_libraries = 'pg_stat_statements'

-- Find the top 10 most time-consuming queries
SELECT
  queryid,
  calls,
  mean_exec_time::numeric(10,2) AS avg_ms,
  total_exec_time::numeric(10,2) AS total_ms,
  rows,
  query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;

上位 10 のクエリを毎週確認します。頻繁に実行される単一のクエリを最適化するだけでも、データベース全体の負荷を 10 ~ 30% 削減できます。


よくある質問

スケーリングのタイミングをどのように判断すればよいでしょうか?

クエリ遅延 P95 (500 ミリ秒でアラート)、接続使用率 (80% でアラート)、および CPU 使用率 (70% でアラートが継続) の 3 つのメトリクスを監視します。定期的にこれらのしきい値に達している場合は、スケーリングのはしごの次のステップに進んでください。事前に最適化しないでください --- データから指示されたときにスケーリングします。

リードレプリカとキャッシュ --- どちらが先ですか?

まずはキャッシングから始めます。 Redis キャッシュは実装が簡単で、より多くの負荷が排除され (キャッシュ ヒットがデータベースを完全にスキップします)、コストが低くなります。キャッシュ ヒット率がすでに 80% を超えているにもかかわらず、プライマリ データベースが依然としてキャッシュ ミスや書き込み操作による負荷を受けている場合は、リード レプリカを追加します。

データベースのスケーリングは Odoo でどのように機能しますか?

Odoo は PostgreSQL のみを使用します。クエリの最適化から始めます (Odoo はレポート用に複雑なクエリを生成します)。同時ユーザーが 50 人を超えた場合は、接続プール用に PgBouncer を追加します。クエリのレポートにはリードレプリカを使用します (Odoo の --db-replica オプションを構成します)。 ECOSIRE は、データベースのチューニングを含む Odoo パフォーマンスの最適化 を提供します。

マネージド データベース (RDS/Cloud SQL) にはプレミアムを利用する価値がありますか?

はい、ほとんどのビジネスに当てはまります。管理されたデータベースは、自動バックアップ、パッチ適用、フェイルオーバー、監視を処理します。セルフマネージド PostgreSQL に比べて 30 ~ 40% のコスト割増は、節約されたエンジニアリング時間によって相殺されます。例外は、大規模なインスタンスのコストプレミアムがパートタイムの DBA のコストを超える大規模なデプロイメントです。


次に何が起こるか

データベースのスケーリングは、より広範なインフラストラクチャのスケーリング戦略の 1 つのコンポーネントです。静的アセットの場合は CDN 最適化、アプリケーション ポッドの場合は Kubernetes 自動スケーリング、および現実的な条件下でスケーリングの決定を検証するには 負荷テスト と組み合わせます。

データベース最適化のコンサルティングについては ECOSIRE にお問い合わせ、完全なインフラストラクチャ ロードマップについては DevOps ガイド を参照してください。


ECOSIRE が発行 -- 企業が自信を持ってデータ インフラストラクチャを拡張できるように支援します。

E

執筆者

ECOSIRE Research and Development Team

ECOSIREでエンタープライズグレードのデジタル製品を開発。Odoo統合、eコマース自動化、AI搭載ビジネスソリューションに関するインサイトを共有しています。

WhatsAppでチャット