スケーラブルなデータストレージ:SQLとNoSQL
アクセスパターン、一貫性、スケール要件に基づいて、リレーショナルデータベース、キーバリューストア、ドキュメントデータベース、ワイドカラムストアから適切なものを選びます。
「スケーラブルなデータストレージ:SQLとNoSQL」はCoddyKit上の無料DSA Interview Prepレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはDSA Interview Prep学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 DSA Interview Prepコースには全4レッスンが含まれています。
ストレージの選択はトレードオフ
データストアの選択は、システム設計における最も重要な決定の一つです。あらゆる用途で最適なデータベースはありません。それぞれの種類が、異なるアクセスパターン、整合性の保証、規模の特性に合わせて最適化されています。本番環境でこの選択を誤ると、何か月にも及ぶ困難な移行作業が発生します。
面接では、面接官は基本的な違いを理解し、問題の要件に合ったストレージエンジンを選べるかどうかを確認します。問われているのは「どちらが優れているか」ではなく、「この特定のワークロードに対してどちらが適しているか」です。選択の理由は、具体的な要件に基づいて必ず説明してください。
# Storage decision matrix summary
factors = [
'Data structure (tabular, documents, key-value, graph, time-series)',
'Read vs write ratio (read-heavy, write-heavy, balanced)',
'Query patterns (point lookups, range scans, aggregations, joins)',
'Consistency requirements (ACID vs eventual consistency)',
'Scale requirements (single node, sharding, global distribution)',
'Latency requirements (milliseconds vs microseconds)',
'Team familiarity and operational complexity',
]
print('Key factors for storage selection:')
for f in factors:
print(f' - {f}')リレーショナルデータベース(SQL)の長所
リレーショナルデータベース(PostgreSQL、MySQL、SQLite)は、固定スキーマのテーブルにデータを格納し、ACIDトランザクション(Atomicity:原子性、Consistency:一貫性、Isolation:分離性、Durability:永続性)をサポートします。結合、集約、フィルタリングを含む複雑なクエリを得意とするため、構造化され、関係が明確に定義されたデータに適しています。
主な長所は、SQLによる複数テーブルの複雑なクエリ、データ整合性を保証する外部キー制約、強力なインデックス(B-tree、ハッシュ、全文検索)、レプリケーションやバックアップを備えた成熟したエコシステムです。データのリレーションが多く、整合性が重要で、クエリが複雑かつ多様な場合はSQLを使用してください。
# SQL excels at: complex queries, joins, transactions
# Example: find top 5 products by revenue this month
sql_query = '''
SELECT p.name, SUM(oi.quantity * oi.price) AS revenue
FROM orders o
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id
WHERE o.created_at >= DATE_TRUNC('month', NOW())
GROUP BY p.id, p.name
ORDER BY revenue DESC
LIMIT 5;
'''
print('SQL shines for relational queries with JOINs:')
print(sql_query)
print('ACID guarantees example (transfer $100 between accounts):')
transfer_sql = '''
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT; -- either both succeed or neither does
'''
print(transfer_sql)SQLのスケーリングにおける課題
SQLデータベースは、より大きなサーバーや多くのCPU・RAMを用意する垂直スケーリングには自然に対応できますが、水平スケーリングは複雑です。リードレプリカを使うと、読み取りをレプリカノードに振り分けることで、読み取りの多いワークロードに対応できます。ただし、シャーディングを導入しない限り、書き込みスループットは単一のプライマリノードに制限されます。
シャーディングでは、シャードキー(例:user_id mod N)に基づいて複数のDBインスタンスにデータを分割します。これにより書き込みをスケールできますが、SQLの大きな強みであるシャードをまたいだ結合やトランザクションが使えなくなります。多くのWebアプリケーションでは、データ量が約5~10TB、または書き込みが毎秒約100K件に達すると、単一ノードのSQLでは対応しきれなくなります。
# SQL scaling strategies
strategies = {
'Read replicas': {
'how': 'One primary (writes), multiple replicas (reads)',
'scales': 'Read throughput (10x+)',
'limit': 'Write throughput still bounded by single primary',
},
'Connection pooling (PgBouncer)': {
'how': 'Pool of persistent DB connections shared among app servers',
'scales': 'Connection count (PostgreSQL max ~500 connections)',
'limit': 'Does not increase query throughput',
},
'Horizontal sharding': {
'how': 'Partition rows by shard key across N database instances',
'scales': 'Both reads and writes (N×)',
'limit': 'Cross-shard joins and transactions broken; complex routing',
},
'CQRS': {
'how': 'Separate write model (SQL) from read model (denormalised/NoSQL)',
'scales': 'Optimise each path independently',
'limit': 'Eventual consistency between write and read models',
},
}
for strategy, info in strategies.items():
print(f'{strategy}:\n How: {info["how"]}\n Scales: {info["scales"]}\n Limit: {info["limit"]}\n')キーバリューストア:RedisとDynamoDB
キーバリューストアは、データをキーと値のペアとして格納し、キーを指定した読み取りと書き込みをO(1)で実行します。クエリの柔軟性を犠牲にする代わりに、非常に高い性能と水平スケーラビリティを実現します。インメモリのRedisはマイクロ秒単位のレイテンシを実現し、マネージドサービスのDynamoDBは自動スケーリングによって、どのようなスループットにも対応しながら1桁ミリ秒のレイテンシを実現します。
キーバリューストアは、セッションストレージ、キャッシュ、機能フラグ、URL短縮マッピング、ショッピングカート、リアルタイムランキングに使用してください。使用しないでください:複雑なクエリ、エンティティ間のリレーション、アドホックなフィルタリングが必要な場合。完全一致のキーによる検索しかできません。
# Key-value store use cases
kv_operations = {
'GET key': 'O(1) point lookup — the core operation',
'SET key value': 'O(1) insert or update',
'DEL key': 'O(1) delete',
'EXPIRE key ttl': 'Set time-to-live; key auto-deleted after ttl seconds',
'INCR key': 'Atomic increment — useful for counters and rate limiting',
'LPUSH/LRANGE': 'List operations — useful for queues and recent-items feeds',
'ZADD/ZRANGE': 'Sorted set — leaderboards, rate limiting with sliding window',
}
print('Redis operation set:')
for op, desc in kv_operations.items():
print(f' {op:25s}: {desc}')
print('\nDynamoDB vs Redis:')
print(' Redis: microsecond latency, in-memory, needs persistence config')
print(' DynamoDB: single-digit ms, managed, auto-scaling, durable by default')ドキュメントストア:MongoDB
ドキュメントストア(MongoDB、Couchbase)は、JSONに似たドキュメントとしてデータを格納し、柔軟なスキーマを実現します。同じコレクション内でも、ドキュメントごとにフィールドが異なって構いません。任意のフィールドへのインデックスと、比較的豊富なクエリ(フィルター、プロジェクション、集約)をサポートしますが、複数ドキュメントにまたがるトランザクションはSQLよりも制限されています。
ドキュメントストアは、エンティティごとにデータの形が異なる場合(属性が異なるユーザープロファイル)、スキーマが頻繁に変化することが予想される場合(データモデルを頻繁に変更するスタートアップ)、またはテーブルを結合するよりもエンティティ全体を取得する読み取りが中心の場合に適しています。
# MongoDB document example
user_doc = {
'_id': 'user123',
'name': 'Alice',
'email': 'alice@example.com',
'preferences': {
'theme': 'dark',
'language': 'en',
'notifications': ['email', 'push']
},
'addresses': [
{'type': 'home', 'city': 'Berlin', 'country': 'DE'},
{'type': 'work', 'city': 'Munich', 'country': 'DE'}
],
'subscription_tier': 'pro',
# Note: not all users have all fields -- flexible schema!
}
import json
print('Document structure (flexible schema):')
print(json.dumps(user_doc, indent=2))
print('\nDocument store strengths:')
print(' - Nested/array fields without joins')
print(' - Flexible schema (different fields per document)')
print(' - Scales horizontally by sharding on _id')ワイドカラムストア:Cassandra
ワイドカラムストア(Apache Cassandra、HBase)はデータを行と列に格納しますが、行ごとに異なる列の組み合わせを持たせることができます。多数のノードに分散した大規模な書き込みスループット向けに設計されており、デフォルトでは結果整合性を採用します。Cassandraは書き込みを線形にスケールできるため、ノードを2倍にすると書き込みスループットも2倍になります。
トレードオフとして、クエリはパーティションキーを中心に設計する必要があります。任意の列で効率的にフィルタリングしたりソートしたりすることはできません。まずクエリパターンを定義し、それに合わせてテーブルを設計する必要があります。これは、「データを設計してから任意のクエリを書く」というSQLのアプローチとは正反対です。
# Cassandra table design for time-series events
# Design around the query: 'give me all events for user X, most recent first'
cassandra_table = '''
CREATE TABLE user_events (
user_id UUID,
event_time TIMESTAMP,
event_type TEXT,
metadata MAP<TEXT, TEXT>,
PRIMARY KEY (user_id, event_time)
) WITH CLUSTERING ORDER BY (event_time DESC);
-- Query (matches partition key exactly):
SELECT * FROM user_events WHERE user_id = ? LIMIT 100;
'''
print('Cassandra wide-column design:')
print(cassandra_table)
print('Properties:')
print(' - user_id = partition key (all rows for one user on same node)')
print(' - event_time = clustering key (sorted within partition)')
print(' - Very fast writes: append-only, no locking')
print(' - Cannot query by event_type alone (no partition key)')CAP定理:一貫性、可用性、分断耐性
CAP定理は、分散システムが次の3つのうち最大2つまでしか保証できないと示します。Consistency(一貫性:すべての読み取りが最新の書き込みを返す)、Availability(可用性:すべてのリクエストにエラーではない応答を返す)、Partition tolerance(分断耐性:ネットワーク分断が発生してもシステムが動作を継続すること)です。
分散システムではネットワーク分断が必ず発生するため、実際の選択肢はCP(一貫性を優先し、分断中はリクエストを拒否する場合がある)とAP(可用性を優先し、古いデータを返す場合がある)のどちらかです。SQLデータベースは一般にCP、CassandraとDynamoDBはAP(デフォルトでは結果整合性)です。Redis ClusterはCPです。
# CAP theorem applied to common databases
databases = {
'PostgreSQL (single node)': {'C': True, 'A': True, 'P': False, 'note': 'Not distributed; CA'},
'PostgreSQL (multi-AZ)': {'C': True, 'A': False, 'P': True, 'note': 'CP: primary fails over, brief downtime'},
'MySQL Cluster': {'C': True, 'A': False, 'P': True, 'note': 'CP'},
'Cassandra': {'C': False, 'A': True, 'P': True, 'note': 'AP: eventual consistency default'},
'DynamoDB (default)': {'C': False, 'A': True, 'P': True, 'note': 'AP: eventual consistency'},
'DynamoDB (strong read)': {'C': True, 'A': False, 'P': True, 'note': 'CP: strongly consistent reads'},
'Redis Cluster': {'C': True, 'A': False, 'P': True, 'note': 'CP'},
'MongoDB (default)': {'C': True, 'A': False, 'P': True, 'note': 'CP: reads from primary'},
}
for db, caps in databases.items():
c_str = 'C' if caps['C'] else '-'
a_str = 'A' if caps['A'] else '-'
p_str = 'P' if caps['P'] else '-'
print(f'{db:35s} [{c_str}{a_str}{p_str}] {caps["note"]}')ストレージを選ぶための判断フレームワーク
面接でストレージを選ぶ際に使える実践的な判断フレームワークは次のとおりです。
- ACIDトランザクションが必要ですか? → リレーショナルDB(PostgreSQL、MySQL)
- サブミリ秒の検索やキャッシュが必要ですか? → キーバリューストア(Redis)
- 柔軟なスキーマやドキュメント指向のデータが必要ですか? → ドキュメントストア(MongoDB)
- 時系列データやイベントデータに対して、大規模な書き込みスループット(>100K/秒)が必要ですか? → ワイドカラムストア(Cassandra)
- マネージドスケーリングによるグローバル分散が必要ですか? → DynamoDBまたはCosmos DB
- グラフの探索が必要ですか? → グラフDB(Neo4j)
# Decision tree in code form
def choose_storage(needs_acid, high_write_throughput, flexible_schema,
sub_ms_latency, graph_queries, global_scale):
if sub_ms_latency:
return 'Redis (in-memory key-value)'
if graph_queries:
return 'Neo4j (graph database)'
if needs_acid:
return 'PostgreSQL / MySQL (relational)'
if high_write_throughput and not flexible_schema:
return 'Cassandra (wide-column, write-optimised)'
if flexible_schema:
return 'MongoDB (document store)'
if global_scale:
return 'DynamoDB or Cosmos DB (managed global KV/document)'
return 'PostgreSQL (safe default for most web apps)'
# Example scenarios
scenarios = [
{'needs_acid': True, 'high_write_throughput': False, 'flexible_schema': False,
'sub_ms_latency': False, 'graph_queries': False, 'global_scale': False},
{'needs_acid': False, 'high_write_throughput': True, 'flexible_schema': False,
'sub_ms_latency': False, 'graph_queries': False, 'global_scale': False},
{'needs_acid': False, 'high_write_throughput': False, 'flexible_schema': False,
'sub_ms_latency': True, 'graph_queries': False, 'global_scale': False},
]
for s in scenarios:
print(f'{choose_storage(**s)}')ポリグロット永続化:複数のストアを使う
本番システムで、すべての用途に単一のデータベースを使うことはほとんどありません。ポリグロット永続化とは、システムの各部分に適したストレージ技術を使うことです。典型的なWebアプリケーションでは、ユーザーと注文の正本データにPostgreSQL、セッションストレージとキャッシュにRedis、全文検索にElasticsearch、ファイルストレージにS3、イベントログと分析にCassandraを使うことがあります。
トレードオフとして、データベースの種類を増やすほど運用の複雑さが増します。チームは複数のシステムを維持、監視、バックアップしなければなりません。設計上の理由は、各ワークロードに適したストレージを割り当てることで得られる性能とスケーラビリティの向上が、大規模環境では運用コストを上回るためです。
# Polyglot persistence in an e-commerce system
components = {
'User accounts, orders, payments': {
'storage': 'PostgreSQL',
'reason': 'ACID transactions (payment integrity), complex queries',
},
'Product catalogue': {
'storage': 'MongoDB or PostgreSQL with JSONB',
'reason': 'Flexible product attributes vary by category',
},
'Session tokens': {
'storage': 'Redis (with TTL)',
'reason': 'O(1) lookup, automatic expiry, high throughput',
},
'Product search': {
'storage': 'Elasticsearch',
'reason': 'Full-text search, faceted filtering, relevance scoring',
},
'Activity/event log': {
'storage': 'Cassandra or Kafka + S3',
'reason': 'High write throughput, append-only, time-series queries',
},
'Product images / videos': {
'storage': 'S3 + CloudFront CDN',
'reason': 'Cheap object storage, global distribution via CDN',
},
}
for component, info in components.items():
print(f'{component}:\n {info["storage"]}: {info["reason"]}\n')ストレージの種類をまたいだインデックス戦略
すべてのストレージシステムでは、インデックスを使って読み取りを高速化しますが、その代わりに書き込みが遅くなり、追加のストレージも必要になります。システム設計では、ストレージの種類ごとのインデックスを理解することが重要です。
- SQL:任意の列にB-treeインデックスを作成可能。複数列のクエリには複合インデックスを使用し、カバリングインデックスによってテーブル参照を回避
- MongoDB:任意のフィールドへのインデックス、複合インデックス、データを自動的に期限切れにするTTLインデックスをサポート
- Cassandra:ネイティブにインデックス化できるのはパーティションキーとクラスタリング列のみ。セカンダリインデックスはコストが高い
- Redis:範囲クエリのインデックスとしてソート済みセットを使用。キーバリューでは従来型のインデックスは不要
# Indexing examples across storage types
# PostgreSQL: B-tree composite index
postgres_index = '''
CREATE INDEX idx_orders_user_created
ON orders(user_id, created_at DESC);
-- Optimises: SELECT * FROM orders WHERE user_id=? ORDER BY created_at DESC
'''
# MongoDB: compound index
mongo_index = '''
db.products.createIndex({ category: 1, price: -1 })
// Optimises: db.products.find({category:'Electronics'}).sort({price:-1})
'''
# Cassandra: cluster key ordering (built into table design)
cassandra_index = '''
-- No separate index needed; clustering key IS the index:
PRIMARY KEY (user_id, event_time) WITH CLUSTERING ORDER BY (event_time DESC)
'''
print('PostgreSQL:', postgres_index)
print('MongoDB:', mongo_index)
print('Cassandra:', cassandra_index)面接でのSQLとNoSQLの答え方
システム設計面接で「SQLとNoSQLのどちらを使いますか?」と聞かれたときは、一言で答えないでください。代わりに、次の構成に従って答えます。
- ワークロードを述べる:「これは複雑なフィルタリングを伴う読み取り中心の処理なので……」
- 要件を述べる:「金融取引には強い整合性が必要なので……」
- 選択を述べる:「PostgreSQLにリードレプリカを組み合わせます」
- トレードオフを述べる:「水平スケーリングによって書き込みを拡張するにはシャーディングが必要になり、複雑さが増すというトレードオフがあります」
- 代替案に触れる:「書き込み量がさらに多い場合は、DynamoDBを検討する可能性があります」
# Sample answer structure for 'SQL or NoSQL?'
def answer_storage_question(workload, consistency_need, scale):
print(f'Workload: {workload}')
print(f'Consistency: {consistency_need}')
print(f'Scale: {scale}')
print()
if 'financial' in workload.lower() or consistency_need == 'strong':
choice = 'PostgreSQL (ACID, strong consistency)'
tradeoff = 'Horizontal write scaling requires sharding'
alternative = 'Google Spanner for global transactions'
elif 'event' in workload.lower() or 'log' in workload.lower():
choice = 'Cassandra (high write throughput, time-series)'
tradeoff = 'Eventual consistency; queries limited to partition key'
alternative = 'Kafka + S3 for long-term event archival'
else:
choice = 'DynamoDB (managed, auto-scale, low latency)'
tradeoff = 'Limited query flexibility; cross-item transactions limited'
alternative = 'PostgreSQL if complex queries emerge'
print(f'Choice: {choice}\nTrade-off: {tradeoff}\nAlternative: {alternative}')
answer_storage_question('Social media feed', 'eventual', '10M users')理解度チェック
このレッスンで学んだ Data Structures & Algorithms — Coding Interview Prep の概念を理解できているか確認しましょう。
レッスンのまとめ
このレッスンでは、SQLデータベースはACIDトランザクションと複雑なクエリを提供しますが、書き込みのスケーリングは難しく、一方NoSQLデータベースは、極端な規模や可用性を得るために整合性またはクエリの柔軟性を犠牲にすること、CAP定理によって、分散システムはネットワーク分断時に一貫性と可用性のどちらかを選択せざるを得ないこと、そして本番システムでは通常、各ワークロードに適したストレージエンジンを割り当てるポリグロット永続化を採用することを学びました。次は、キャッシュレイヤー、CDN、ロードバランシングを使って、読み取りの多いシステムをさらにスケールする方法を見ていきます。
よくある質問
「スケーラブルなデータストレージ:SQLとNoSQL」レッスンは無料ですか?
はい。「スケーラブルなデータストレージ:SQLとNoSQL」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、DSA Interview Prepコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 DSA Interview Prepコースには全4レッスンが含まれています。
「スケーラブルなデータストレージ:SQLとNoSQL」で何を学びますか?
アクセスパターン、一貫性、スケール要件に基づいて、リレーショナルデータベース、キーバリューストア、ドキュメントデータベース、ワイドカラムストアから適切なものを選びます。 ブラウザで直接実行するハンズオンコードでDSA Interview Prepを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
DSA Interview Prepを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのDSA Interview Prepは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。
「スケーラブルなデータストレージ:SQLとNoSQL」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このDSA Interview Prepレッスンでコードを書いて実行できますか?
はい。すべてのDSA Interview Prepレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- システム設計面接のフレームワーク
- スケーラブルなデータストレージ:SQLとNoSQL
- キャッシュ、CDN、ロードバランシング
- Rate Limiterの設計とTwitterフィードの設計