0Pricing
AWS Solutions Architect · レッスン

キャッシュ戦略:レイジーローディングとライトスルー

キャッシュミス時にキャッシュへデータを投入するレイジーローディングと、データベースへの書き込みごとにキャッシュの整合性を保つライトスルーを実装します。

「キャッシュ戦略:レイジーローディングとライトスルー」はCoddyKit上の無料AWS Solutions Architectレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAWS Solutions Architect学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 AWS Solutions Architectコースには全4レッスンが含まれています。

キャッシュ戦略が重要な理由

アプリケーションとデータベースの間にキャッシュを配置するには、データをいつキャッシュに格納するか、いつキャッシュから読み取るか、いつ追い出すかを決める一連のルールであるキャッシュ戦略が必要です。誤った戦略を選ぶと、古いデータ(キャッシュが古い値を返す)、コールドキャッシュミス(キャッシュが空で、すべてのリクエストがデータベースに到達する)、またはキャッシュスタンピード(同じ欠落キーへの多数の同時リクエストが、すべて同時にデータベースへ到達する)が発生します。基本となる戦略は、遅延読み込みとWrite-Throughの2つです。

遅延読み込み(Cache-Aside)の仕組み

遅延読み込み(Cache-Asideとも呼ばれます)は、最も一般的なキャッシュパターンです。アプリケーションはまずキャッシュを確認します。キャッシュヒットの場合、データはキャッシュから直接返されます。これは高速な経路です。キャッシュミスの場合、アプリケーションはデータベースからデータを取得し、TTL付きで結果をキャッシュに書き込んでから、呼び出し元に返します。キャッシュには実際にリクエストされたデータだけが格納されるため、「遅延」読み込みと呼ばれます。同じキーへの次のリクエストでは、キャッシュ内のデータが見つかります。

# Lazy loading pattern (Python with redis-py)
def get_user(user_id, redis_client, db):
    cache_key = f'user:{user_id}'

    # 1. Check cache
    cached = redis_client.get(cache_key)
    if cached:
        return json.loads(cached)   # Cache HIT

    # 2. Cache MISS: fetch from DB
    user = db.query('SELECT * FROM users WHERE id = %s', user_id)

    # 3. Populate cache with TTL of 300 seconds
    redis_client.setex(cache_key, 300, json.dumps(user))

    return user

遅延読み込みのメリット

遅延読み込みには、主に3つのメリットがあります。リクエストされたデータだけがキャッシュされるため、誰も読み取らないデータでキャッシュが満たされず、メモリを効率的に使用できます。コールドキャッシュでもアプリケーションは停止しないため、キャッシュミス時にはアプリケーションがデータベースにフォールバックします。そのため、ElastiCacheが再起動した場合やノードに障害が発生した場合でも、レイテンシーは高くなりますがアプリケーションは動作を継続できます。キャッシュはデータベースと常に結果整合性を保つため、更新を取りこぼした場合でも、古いデータはTTLによって期限切れになります。

遅延読み込みのデメリット

遅延読み込みには、主に3つのデメリットがあります。キャッシュミスはコストが高いため、ヒット時の1回の操作に対して、キャッシュ確認、データベース読み取り、キャッシュ書き込みの3つの操作が必要になり、コールドリクエストのレイテンシーが高くなります。古いデータが残るため、データベースを更新した後も、TTLが期限切れになるかキーを明示的に無効化するまで、キャッシュは古い値を返します(キャッシュの不整合期間)。また、キャッシュスタンピードにより、人気のキーが期限切れになると、多数の同時リクエストがすべてキャッシュミスとなって同時にデータベースへ到達し、データベースに過大な負荷をかける可能性があります。

# Mitigating cache stampede with a probabilistic early expiration
# (Refresh the key before it expires to avoid simultaneous misses)
def get_with_stampede_protection(key, redis_client, db_fetch_fn, ttl=300):
    value = redis_client.get(key)
    ttl_remaining = redis_client.ttl(key)

    # Probabilistically refresh before expiry
    if value is None or (ttl_remaining < 30 and random.random() < 0.1):
        value = db_fetch_fn()
        redis_client.setex(key, ttl, json.dumps(value))
    return json.loads(value)

Write-Throughの仕組み

Write-Through戦略では、データベースへのすべての書き込みが同時にキャッシュにも書き込まれます。アプリケーションは同じ処理の一環としてキャッシュとデータベースの両方に書き込むか、データベースがキャッシュの更新をトリガーします。そのため、キャッシュは常にデータベースと同期され、古いデータが存在する期間はありません。Write-Throughでは、キャッシュ内のデータが常に最新であることが保証されるため、最近書き込まれたデータへの後続の読み取りは常にキャッシュヒットになります。

# Write-through pattern (Python)
def update_user(user_id, user_data, redis_client, db):
    # 1. Write to database FIRST
    db.execute('UPDATE users SET name=%s WHERE id=%s',
                (user_data['name'], user_id))

    # 2. Update cache immediately (write-through)
    cache_key = f'user:{user_id}'
    redis_client.setex(cache_key, 3600, json.dumps(user_data))

    return user_data

# Every read is now a cache hit for recently updated data

Write-Throughのメリット

Write-Throughのメリットは次のとおりです。キャッシュデータが常に最新であるため、書き込みのたびにキャッシュが更新され、古いデータが発生しません。読み取りが常に高速であるため、頻繁に書き込まれる人気のデータは常にキャッシュ内に存在します。読み取り時のキャッシュスタンピードが発生しないため、データは読み取られる前にあらかじめ格納され、最近書き込まれたデータでコールドミスが発生しません。この戦略は、商品カタログ、価格システム、ユーザープロファイルのキャッシュなど、データの鮮度が重要な頻繁な更新を伴う読み取り中心のワークロードに適しています。

Write-Throughのデメリット

Write-Throughのデメリットは次のとおりです。書き込みペナルティとして、すべての書き込みでデータベースとキャッシュの2つの操作が必要になり、書き込みのレイテンシーが増加します。キャッシュの汚染により、二度と読み取られないデータ(1回書き込まれた後、リクエストされないデータ)までキャッシュされ、キャッシュメモリを無駄に消費します。キャッシュの再起動によってコールドキャッシュになるため、キャッシュクラスターが再起動すると、事前に書き込まれていたデータがすべて失われ、書き込みまたはキャッシュウォームアップ処理によって再格納する必要があります。ほとんど読み取られないデータが無制限に増加するのを防ぐため、Write-ThroughはTTLと組み合わせて使用してください。

遅延読み込みとWrite-Throughの組み合わせ

実際の多くの本番システムでは、両方の戦略を組み合わせます。頻繁に更新され、頻繁に読み取られるデータ(ユーザーセッションや現在の価格など)にはWrite-Throughを使用し、更新頻度は低いが、頻繁に読み取られるデータ(商品説明や記事コンテンツなど)には遅延読み込みを使用します。どちらにも適切なTTLを設定します。Write-Throughのキーはデータが常に最新であるため長いTTLにし、遅延読み込みのキーは古いデータの期間を制限するため短いTTLにします。このハイブリッドアプローチにより、古さを最小限に抑えながらキャッシュヒット率を最大化できます。

# Hybrid: write-through for sessions, lazy loading for product data

# Write-through for session data (always fresh, critical)
def save_session(session_id, data, redis_client, db):
    db.upsert('sessions', session_id, data)
    redis_client.setex(f'session:{session_id}', 3600, json.dumps(data))

# Lazy loading for product catalog (infrequent updates OK)
def get_product(product_id, redis_client, db):
    cached = redis_client.get(f'product:{product_id}')
    if cached:
        return json.loads(cached)
    product = db.query('SELECT * FROM products WHERE id=%s', product_id)
    redis_client.setex(f'product:{product_id}', 86400, json.dumps(product))
    return product

TTL設計の原則

キャッシュデータのTime-To-Live(TTL)は、古いデータが存在できる最大期間とキャッシュのメモリ使用量を制御します。TTLは、データの更新頻度(セッションデータは頻繁に変わるため短いTTL、静的コンテンツはほとんど変わらないため長いTTL)、古さの許容度(金融価格は非常に短く、ブログ記事の本文は数時間から数日)、キャッシュメモリの容量(メモリが少ない場合は、古いデータをより速く追い出すため短いTTL)に基づいて設計します。必ずTTLを設定してください。期限を設定せずにデータをキャッシュすると、最終的にキャッシュが古いデータで満たされます。

# TTL examples by data type

# API rate limit counter: 60 seconds
redis_client.setex(f'ratelimit:{ip}', 60, count)

# User session: 30 minutes
redis_client.setex(f'session:{id}', 1800, json.dumps(session))

# Product catalog: 24 hours
redis_client.setex(f'product:{id}', 86400, json.dumps(product))

# Stock price: 10 seconds
redis_client.setex(f'price:{symbol}', 10, price)

# Static site content: 7 days
redis_client.setex(f'page:{slug}', 604800, html_content)

更新時のキャッシュ無効化

TTLの期限切れだけに頼るのではなく、基になるデータが変更されたときに、キャッシュキーを明示的に無効化(削除)することもできます。これにより、古いデータが存在する期間を完全になくせます。一般的なパターンには、書き込み時削除(データベースを更新するたびにキャッシュキーを削除し、次の読み取りで遅延読み込みによって再格納する)、イベント駆動型無効化(DynamoDB StreamsまたはRDSの変更キャプチャがLambdaをトリガーし、影響を受けるキーを削除する)があります。キャッシュの無効化は分散システムにおける最も難しい問題の1つです。無効化ロジックを単純にするほど、キャッシュの信頼性は高くなります。

# Delete-on-write invalidation pattern
def update_product(product_id, new_data, redis_client, db):
    # Update the database
    db.execute('UPDATE products SET ... WHERE id=%s', (product_id,))

    # Invalidate the cache key
    redis_client.delete(f'product:{product_id}')

    # Also invalidate any list/search caches that may include this product
    redis_client.delete('products:list:page:1')
    redis_client.delete(f'products:category:{new_data["category_id"]}')

    # Next read will trigger lazy loading with fresh data

Write-Behind(Write-Back)キャッシュ

あまり一般的ではありませんが強力なパターンとして、Write-Behind(Write-Back)があります。アプリケーションはキャッシュにのみ書き込み、キャッシュがバックグラウンドでデータベースに非同期でフラッシュします。これにより、メモリ内だけで処理する非常に高速な書き込みが可能になりますが、フラッシュ前にキャッシュに障害が発生するとデータが失われる可能性があります。Write-Behindは、データを再構築できる場合や、多少の損失が許容される高頻度・大量の書き込みに適しています。たとえば、ヒットカウンター、分析イベント、ゲームのスコア更新などです。ElastiCacheはWrite-Behindをネイティブにはサポートしていないため、アプリケーション層で実装する必要があります。

# Write-behind pattern: write to cache, flush to DB asynchronously
# Application writes:
# redis_client.incr('post:42:views')      # fast, in-memory only

# Background job (runs every 60 seconds):
def flush_view_counts(redis_client, db):
    for key in redis_client.scan_iter('post:*:views'):
        count = redis_client.getdel(key)  # atomic get-and-delete
        post_id = key.split(':')[1]
        db.execute('UPDATE posts SET views = views + %s WHERE id = %s',
                   (int(count), post_id))

クイックチェック

このレッスンで学んだAWS Solutions Architect(SAA-C03)の概念について理解度を確認します。

レッスンのまとめ

このレッスンでは、遅延読み込みが読み取り時のキャッシュミスでキャッシュにデータを格納し、メモリを効率的に使用できる一方、TTLの期限切れまで古いデータを返す可能性があること、Write-Throughが書き込みのたびにキャッシュを更新して古いデータをなくせる一方、読み取られないデータでメモリを無駄にすること、そして明示的な無効化がデータベースの更新時にキャッシュキーを削除して古いデータの期間をなくすことを学びました。次は、ElastiCacheを使用したセッションストレージとリーダーボードのパターンについて学びます。

よくある質問

「キャッシュ戦略:レイジーローディングとライトスルー」レッスンは無料ですか?

はい。「キャッシュ戦略:レイジーローディングとライトスルー」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、AWS Solutions Architectコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 AWS Solutions Architectコースには全4レッスンが含まれています。

「キャッシュ戦略:レイジーローディングとライトスルー」で何を学びますか?

キャッシュミス時にキャッシュへデータを投入するレイジーローディングと、データベースへの書き込みごとにキャッシュの整合性を保つライトスルーを実装します。 ブラウザで直接実行するハンズオンコードでAWS Solutions Architectを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

AWS Solutions Architectを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのAWS Solutions Architectは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。

「キャッシュ戦略:レイジーローディングとライトスルー」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このAWS Solutions Architectレッスンでコードを書いて実行できますか?

はい。すべてのAWS Solutions Architectレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. RedisとMemcached:適切なエンジンの選択
  2. ElastiCache Redisのレプリケーショングループとクラスター​​モード
  3. キャッシュ戦略:レイジーローディングとライトスルー
  4. セッションストレージとリーダーボードのパターン
← AWS Solutions Architectに戻る