[{"data":1,"prerenderedAt":1198},["ShallowReactive",2],{"nav":3,"page-\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002F":310,"surround-\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002F":1196},[4,96,212],{"title":5,"path":6,"stem":7,"children":8},"Advanced Pydantic Validation Serialization","\u002Fadvanced-pydantic-validation-serialization","advanced-pydantic-validation-serialization",[9,12,30,42,54,66,90],{"title":10,"path":6,"stem":11},"Advanced Pydantic Validation and Serialization","advanced-pydantic-validation-serialization\u002Findex",{"title":13,"path":14,"stem":15,"children":16},"Custom Validators and Field Constraints in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Findex",[17,18,24],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":22},"Creating Reusable Custom Validators in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcreating-reusable-custom-validators-in-pydantic","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcreating-reusable-custom-validators-in-pydantic\u002Findex",[23],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":28},"Pydantic v2 Async Custom Validator: What to Do Instead","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fpydantic-v2-async-custom-validator","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fpydantic-v2-async-custom-validator\u002Findex",[29],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":34},"JSON Schema Customization in Pydantic and FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Findex",[35,36],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":40},"Customizing OpenAPI Schema Generation in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fcustomizing-openapi-schema-generation-in-fastapi","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fcustomizing-openapi-schema-generation-in-fastapi\u002Findex",[41],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":46},"Nested Model Serialization in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Findex",[47,48],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":52},"Handling Deeply Nested JSON Models Efficiently","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fhandling-deeply-nested-json-models-efficiently","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fhandling-deeply-nested-json-models-efficiently\u002Findex",[53],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":58},"Performance Optimization for Pydantic Models in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Findex",[59,60],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":64},"Pydantic Model Serialization Performance in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fpydantic-model-serialization-performance","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fpydantic-model-serialization-performance\u002Findex",[65],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69,"children":70},"Pydantic V2 Migration Guide for FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Findex",[71,72,78,84],{"title":67,"path":68,"stem":69},{"title":73,"path":74,"stem":75,"children":76},"Migrate @validator to @field_validator in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrate-validator-to-field-validator","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrate-validator-to-field-validator\u002Findex",[77],{"title":73,"path":74,"stem":75},{"title":79,"path":80,"stem":81,"children":82},"Migrating from Pydantic v1 to v2 Without Breaking APIs","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrating-from-pydantic-v1-to-v2-without-breaking-apis","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrating-from-pydantic-v1-to-v2-without-breaking-apis\u002Findex",[83],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87,"children":88},"model_config vs class Config in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmodel-config-vs-class-config","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmodel-config-vs-class-config\u002Findex",[89],{"title":85,"path":86,"stem":87},{"title":91,"path":92,"stem":93,"children":94},"Type Hinting and IDE Integration in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration","advanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration\u002Findex",[95],{"title":91,"path":92,"stem":93},{"title":97,"path":98,"stem":99,"children":100},"Async Background Tasks Observability","\u002Fasync-background-tasks-observability","async-background-tasks-observability",[101,104,122,140,158,176,194],{"title":102,"path":98,"stem":103},"Async, Background Tasks, and Observability in FastAPI","async-background-tasks-observability\u002Findex",{"title":105,"path":106,"stem":107,"children":108},"Async Correctness and Concurrency in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Findex",[109,110,116],{"title":105,"path":106,"stem":107},{"title":111,"path":112,"stem":113,"children":114},"FastAPI async def vs def: Performance and When to Use Each","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffastapi-async-def-vs-def-performance","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffastapi-async-def-vs-def-performance\u002Findex",[115],{"title":111,"path":112,"stem":113},{"title":117,"path":118,"stem":119,"children":120},"Fixing Blocking Calls in Async FastAPI Routes","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffixing-blocking-calls-in-async-routes","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffixing-blocking-calls-in-async-routes\u002Findex",[121],{"title":117,"path":118,"stem":119},{"title":123,"path":124,"stem":125,"children":126},"Async Database Sessions in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions","async-background-tasks-observability\u002Fasync-database-sessions\u002Findex",[127,128,134],{"title":123,"path":124,"stem":125},{"title":129,"path":130,"stem":131,"children":132},"Async SQLAlchemy Session per Request in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Fasync-sqlalchemy-session-per-request","async-background-tasks-observability\u002Fasync-database-sessions\u002Fasync-sqlalchemy-session-per-request\u002Findex",[133],{"title":129,"path":130,"stem":131},{"title":135,"path":136,"stem":137,"children":138},"Fixing asyncpg Connection Pool Exhaustion in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ffixing-asyncpg-pool-exhaustion","async-background-tasks-observability\u002Fasync-database-sessions\u002Ffixing-asyncpg-pool-exhaustion\u002Findex",[139],{"title":135,"path":136,"stem":137},{"title":141,"path":142,"stem":143,"children":144},"Background Task Processing in FastAPI","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing","async-background-tasks-observability\u002Fbackground-task-processing\u002Findex",[145,146,152],{"title":141,"path":142,"stem":143},{"title":147,"path":148,"stem":149,"children":150},"FastAPI BackgroundTasks vs Celery vs ARQ","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Ffastapi-backgroundtasks-vs-celery-vs-arq","async-background-tasks-observability\u002Fbackground-task-processing\u002Ffastapi-backgroundtasks-vs-celery-vs-arq\u002Findex",[151],{"title":147,"path":148,"stem":149},{"title":153,"path":154,"stem":155,"children":156},"Running ARQ Workers with FastAPI","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Frunning-arq-workers-with-fastapi","async-background-tasks-observability\u002Fbackground-task-processing\u002Frunning-arq-workers-with-fastapi\u002Findex",[157],{"title":153,"path":154,"stem":155},{"title":159,"path":160,"stem":161,"children":162},"Caching Strategies in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies","async-background-tasks-observability\u002Fcaching-strategies\u002Findex",[163,164,170],{"title":159,"path":160,"stem":161},{"title":165,"path":166,"stem":167,"children":168},"Cache Invalidation Patterns in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi","async-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi\u002Findex",[169],{"title":165,"path":166,"stem":167},{"title":171,"path":172,"stem":173,"children":174},"Redis Response Caching in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi","async-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi\u002Findex",[175],{"title":171,"path":172,"stem":173},{"title":177,"path":178,"stem":179,"children":180},"Observability and Tracing in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing","async-background-tasks-observability\u002Fobservability-and-tracing\u002Findex",[181,182,188],{"title":177,"path":178,"stem":179},{"title":183,"path":184,"stem":185,"children":186},"Instrumenting FastAPI with OpenTelemetry","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Finstrumenting-fastapi-with-opentelemetry","async-background-tasks-observability\u002Fobservability-and-tracing\u002Finstrumenting-fastapi-with-opentelemetry\u002Findex",[187],{"title":183,"path":184,"stem":185},{"title":189,"path":190,"stem":191,"children":192},"Structured JSON Logging with Request IDs in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fstructured-json-logging-with-request-ids","async-background-tasks-observability\u002Fobservability-and-tracing\u002Fstructured-json-logging-with-request-ids\u002Findex",[193],{"title":189,"path":190,"stem":191},{"title":195,"path":196,"stem":197,"children":198},"Rate Limiting and Throttling in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling","async-background-tasks-observability\u002Frate-limiting-throttling\u002Findex",[199,200,206],{"title":195,"path":196,"stem":197},{"title":201,"path":202,"stem":203,"children":204},"FastAPI Rate Limiting with Redis and SlowAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Ffastapi-rate-limiting-with-redis-slowapi","async-background-tasks-observability\u002Frate-limiting-throttling\u002Ffastapi-rate-limiting-with-redis-slowapi\u002Findex",[205],{"title":201,"path":202,"stem":203},{"title":207,"path":208,"stem":209,"children":210},"Per-User Token Bucket Throttling in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Fper-user-token-bucket-throttling","async-background-tasks-observability\u002Frate-limiting-throttling\u002Fper-user-token-bucket-throttling\u002Findex",[211],{"title":207,"path":208,"stem":209},{"title":213,"path":214,"stem":215,"children":216},"Core Architecture Routing Patterns","\u002Fcore-architecture-routing-patterns","core-architecture-routing-patterns",[217,220,232,250,268,280,292],{"title":218,"path":214,"stem":219},"FastAPI Core Architecture and Routing Patterns","core-architecture-routing-patterns\u002Findex",{"title":221,"path":222,"stem":223,"children":224},"Application Factory Patterns in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Findex",[225,226],{"title":221,"path":222,"stem":223},{"title":227,"path":228,"stem":229,"children":230},"FastAPI App Factory Pattern for Testing and Deployment","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Ffastapi-app-factory-pattern-for-testing-and-deployment","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Ffastapi-app-factory-pattern-for-testing-and-deployment\u002Findex",[231],{"title":227,"path":228,"stem":229},{"title":233,"path":234,"stem":235,"children":236},"Configuration Management in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management","core-architecture-routing-patterns\u002Fconfiguration-management\u002Findex",[237,238,244],{"title":233,"path":234,"stem":235},{"title":239,"path":240,"stem":241,"children":242},"Managing Environment Variables with Pydantic Settings","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fmanaging-environment-variables-with-pydantic-settings","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fmanaging-environment-variables-with-pydantic-settings\u002Findex",[243],{"title":239,"path":240,"stem":241},{"title":245,"path":246,"stem":247,"children":248},"Pydantic Settings vs Dynaconf vs python-decouple","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fpydantic-settings-vs-dynaconf-vs-python-decouple","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fpydantic-settings-vs-dynaconf-vs-python-decouple\u002Findex",[249],{"title":245,"path":246,"stem":247},{"title":251,"path":252,"stem":253,"children":254},"Dependency Injection Strategies in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Findex",[255,256,262],{"title":251,"path":252,"stem":253},{"title":257,"path":258,"stem":259,"children":260},"Best Practices for FastAPI Dependency Injection","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fbest-practices-for-fastapi-dependency-injection","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fbest-practices-for-fastapi-dependency-injection\u002Findex",[261],{"title":257,"path":258,"stem":259},{"title":263,"path":264,"stem":265,"children":266},"Fixing FastAPI Dependency Injection Circular Imports","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Ffastapi-dependency-injection-circular-import-fix","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Ffastapi-dependency-injection-circular-import-fix\u002Findex",[267],{"title":263,"path":264,"stem":265},{"title":269,"path":270,"stem":271,"children":272},"Error Handling and Global Exceptions in FastAPI","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Findex",[273,274],{"title":269,"path":270,"stem":271},{"title":275,"path":276,"stem":277,"children":278},"Global Exception Handlers for Consistent API Responses","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fglobal-exception-handlers-for-consistent-api-responses","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fglobal-exception-handlers-for-consistent-api-responses\u002Findex",[279],{"title":275,"path":276,"stem":277},{"title":281,"path":282,"stem":283,"children":284},"Middleware Implementation in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Findex",[285,286],{"title":281,"path":282,"stem":283},{"title":287,"path":288,"stem":289,"children":290},"Implementing Custom Middleware for Request Tracing","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fimplementing-custom-middleware-for-request-tracing","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fimplementing-custom-middleware-for-request-tracing\u002Findex",[291],{"title":287,"path":288,"stem":289},{"title":293,"path":294,"stem":295,"children":296},"Modular Router Organization in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Findex",[297,298,304],{"title":293,"path":294,"stem":295},{"title":299,"path":300,"stem":301,"children":302},"APIRouter Prefix vs Sub-Application Mounting in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fapirouter-prefix-vs-sub-application-mounting","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fapirouter-prefix-vs-sub-application-mounting\u002Findex",[303],{"title":299,"path":300,"stem":301},{"title":305,"path":306,"stem":307,"children":308},"How to Structure Large FastAPI Projects for Scale","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fhow-to-structure-large-fastapi-projects-for-scale","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fhow-to-structure-large-fastapi-projects-for-scale\u002Findex",[309],{"title":305,"path":306,"stem":307},{"id":311,"title":159,"body":312,"description":1165,"extension":1166,"meta":1167,"navigation":509,"path":160,"seo":1194,"stem":161,"__hash__":1195},"content\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Findex.md",{"type":313,"value":314,"toc":1157},"minimark",[315,319,323,342,474,479,482,672,676,679,929,936,940,943,947,950,1083,1087,1120,1124,1153],[316,317,159],"h1",{"id":318},"caching-strategies-in-fastapi",[320,321,322],"p",{},"Caching is trading freshness for speed: storing hot, rarely-changing data in a fast store such as Redis so most requests skip the database entirely, in exchange for managing staleness and invalidation.",[320,324,325,326,331,332,336,337,341],{},"This topic is part of ",[327,328,330],"a",{"href":329},"\u002Fasync-background-tasks-observability\u002F","Async, Background Tasks and Observability",". It relieves the ",[327,333,335],{"href":334},"\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002F","async database"," under read-heavy load and complements ",[327,338,340],{"href":339},"\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002F","serialization performance"," by avoiding repeated work for the same data.",[343,344,345,470],"figure",{},[346,347,355,356,355,360,355,364,355,373,355,380,355,385,355,391,355,396,355,403,355,409,355,412,355,417,355,421,355,426,355,432,355,439,355,444,355,451,355,454,355,458,355,461,355,466],"svg",{"viewBox":348,"role":349,"ariaLabelledBy":350,"xmlns":353,"style":354},"0 0 760 220","img",[351,352],"cache-title","cache-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;font-family:Inter,system-ui,sans-serif","\n  ",[357,358,359],"title",{"id":351},"The cache-aside read path",[361,362,363],"desc",{"id":352},"A request checks the cache. On a hit it returns immediately. On a miss it reads the database, writes the value into the cache with a TTL, and returns it.",[365,366],"rect",{"x":367,"y":368,"width":369,"height":370,"rx":371,"style":372},"24","90","130","46","8","fill:#E0F2F1;stroke:#009688;stroke-width:1.6px",[374,375,379],"text",{"x":376,"y":377,"style":378},"89","117","fill:#00695C;font-size:11.5px;font-weight:600;text-anchor:middle","Request",[365,381],{"x":382,"y":368,"width":383,"height":370,"rx":371,"style":384},"206","140","fill:#FFFFFF;stroke:#4DB6AC;stroke-width:1.6px",[374,386,390],{"x":387,"y":388,"style":389},"276","112","fill:#00695C;font-size:11.5px;text-anchor:middle","Cache (Redis)",[374,392,395],{"x":387,"y":393,"style":394},"127","fill:#6B7280;font-size:10px;text-anchor:middle","check key",[365,397],{"x":398,"y":399,"width":400,"height":401,"rx":371,"style":402},"430","26","150","44","fill:#F1FBF9;stroke:#B2DFDB;stroke-width:1.5px",[374,404,408],{"x":405,"y":406,"style":407},"505","53","fill:#4B5563;font-size:11.5px;text-anchor:middle","hit → return",[365,410],{"x":398,"y":400,"width":400,"height":401,"rx":371,"style":411},"fill:#F9FAFB;stroke:#D1D5DB;stroke-width:1.5px",[374,413,416],{"x":405,"y":414,"style":415},"172","fill:#374151;font-size:11.5px;text-anchor:middle","miss → DB",[374,418,420],{"x":405,"y":419,"style":394},"186","then populate",[365,422],{"x":423,"y":400,"width":424,"height":401,"rx":371,"style":425},"630","110","fill:#FFFFFF;stroke:#4DB6AC;stroke-width:1.5px",[374,427,431],{"x":428,"y":429,"style":430},"685","177","fill:#00695C;font-size:11px;text-anchor:middle","set TTL",[433,434],"line",{"x1":435,"y1":436,"x2":437,"y2":436,"style":438},"154","113","204","stroke:#00796B;stroke-width:1.8px",[440,441],"polygon",{"points":442,"style":443},"204,109 212,113 204,117","fill:#00796B",[433,445],{"x1":446,"y1":447,"x2":448,"y2":449,"style":450},"346","104","428","56","stroke:#00796B;stroke-width:1.5px",[440,452],{"points":453,"style":443},"424,52 430,55 425,62",[433,455],{"x1":446,"y1":456,"x2":448,"y2":457,"style":450},"122","168",[440,459],{"points":460,"style":443},"424,163 430,167 421,171",[433,462],{"x1":463,"y1":414,"x2":464,"y2":414,"style":465},"580","628","stroke:#4DB6AC;stroke-width:1.5px",[440,467],{"points":468,"style":469},"628,168 636,172 628,176","fill:#4DB6AC",[471,472,473],"figcaption",{},"Cache-aside: a hit returns immediately, a miss falls through to the database and populates the cache with a TTL for the next reader.",[475,476,478],"h2",{"id":477},"core-mechanics-cache-aside-with-redis","Core Mechanics: Cache-Aside with Redis",[320,480,481],{},"The application owns the cache logic: check first, fall through on a miss, populate, return. An async Redis client keeps the cache calls off the event loop just like the database.",[483,484,489],"pre",{"className":485,"code":486,"language":487,"meta":488,"style":488},"language-python shiki shiki-themes github-light","import json\n\nfrom redis.asyncio import Redis\n\n\nasync def get_product(redis: Redis, product_id: int) -> dict:\n    key = f\"product:{product_id}\"\n    if cached := await redis.get(key):\n        return json.loads(cached)              # Hit: skip the database entirely.\n    product = await load_product_from_db(product_id)\n    # Populate with a TTL so the value refreshes after it goes stale.\n    await redis.set(key, json.dumps(product), ex=300)\n    return product\n","python","",[490,491,492,504,511,525,530,535,564,592,610,623,636,642,663],"code",{"__ignoreMap":488},[493,494,496,500],"span",{"class":433,"line":495},1,[493,497,499],{"class":498},"sD7c4","import",[493,501,503],{"class":502},"sgsFI"," json\n",[493,505,507],{"class":433,"line":506},2,[493,508,510],{"emptyLinePlaceholder":509},true,"\n",[493,512,514,517,520,522],{"class":433,"line":513},3,[493,515,516],{"class":498},"from",[493,518,519],{"class":502}," redis.asyncio ",[493,521,499],{"class":498},[493,523,524],{"class":502}," Redis\n",[493,526,528],{"class":433,"line":527},4,[493,529,510],{"emptyLinePlaceholder":509},[493,531,533],{"class":433,"line":532},5,[493,534,510],{"emptyLinePlaceholder":509},[493,536,538,541,544,548,551,555,558,561],{"class":433,"line":537},6,[493,539,540],{"class":498},"async",[493,542,543],{"class":498}," def",[493,545,547],{"class":546},"s7eDp"," get_product",[493,549,550],{"class":502},"(redis: Redis, product_id: ",[493,552,554],{"class":553},"sYu0t","int",[493,556,557],{"class":502},") -> ",[493,559,560],{"class":553},"dict",[493,562,563],{"class":502},":\n",[493,565,567,570,573,576,580,583,586,589],{"class":433,"line":566},7,[493,568,569],{"class":502},"    key ",[493,571,572],{"class":498},"=",[493,574,575],{"class":498}," f",[493,577,579],{"class":578},"sYBdl","\"product:",[493,581,582],{"class":553},"{",[493,584,585],{"class":502},"product_id",[493,587,588],{"class":553},"}",[493,590,591],{"class":578},"\"\n",[493,593,595,598,601,604,607],{"class":433,"line":594},8,[493,596,597],{"class":498},"    if",[493,599,600],{"class":502}," cached ",[493,602,603],{"class":498},":=",[493,605,606],{"class":498}," await",[493,608,609],{"class":502}," redis.get(key):\n",[493,611,613,616,619],{"class":433,"line":612},9,[493,614,615],{"class":498},"        return",[493,617,618],{"class":502}," json.loads(cached)              ",[493,620,622],{"class":621},"sAwPA","# Hit: skip the database entirely.\n",[493,624,626,629,631,633],{"class":433,"line":625},10,[493,627,628],{"class":502},"    product ",[493,630,572],{"class":498},[493,632,606],{"class":498},[493,634,635],{"class":502}," load_product_from_db(product_id)\n",[493,637,639],{"class":433,"line":638},11,[493,640,641],{"class":621},"    # Populate with a TTL so the value refreshes after it goes stale.\n",[493,643,645,648,651,655,657,660],{"class":433,"line":644},12,[493,646,647],{"class":498},"    await",[493,649,650],{"class":502}," redis.set(key, json.dumps(product), ",[493,652,654],{"class":653},"sqxcx","ex",[493,656,572],{"class":498},[493,658,659],{"class":553},"300",[493,661,662],{"class":502},")\n",[493,664,666,669],{"class":433,"line":665},13,[493,667,668],{"class":498},"    return",[493,670,671],{"class":502}," product\n",[475,673,675],{"id":674},"production-implementation-invalidation-and-stampede-protection","Production Implementation: Invalidation and Stampede Protection",[320,677,678],{},"Invalidation is the hard half. Delete or update the key when the underlying data changes, and protect popular keys from stampedes with a short lock so only one request recomputes.",[483,680,682],{"className":485,"code":681,"language":487,"meta":488,"style":488},"async def update_product(redis: Redis, product_id: int, changes: dict) -> None:\n    await write_product_to_db(product_id, changes)\n    # Explicit invalidation: drop the key so the next read repopulates fresh.\n    await redis.delete(f\"product:{product_id}\")\n\n\nasync def get_with_lock(redis: Redis, key: str, loader) -> dict:\n    if cached := await redis.get(key):\n        return json.loads(cached)\n    # Only one caller computes; others briefly retry, preventing a stampede.\n    if await redis.set(f\"lock:{key}\", \"1\", nx=True, ex=5):\n        value = await loader()\n        await redis.set(key, json.dumps(value), ex=300)\n        await redis.delete(f\"lock:{key}\")\n        return value\n    await asyncio.sleep(0.05)\n    return await get_with_lock(redis, key, loader)\n",[490,683,684,709,716,721,744,748,752,774,786,793,798,849,861,877,898,906,919],{"__ignoreMap":488},[493,685,686,688,690,693,695,697,700,702,704,707],{"class":433,"line":495},[493,687,540],{"class":498},[493,689,543],{"class":498},[493,691,692],{"class":546}," update_product",[493,694,550],{"class":502},[493,696,554],{"class":553},[493,698,699],{"class":502},", changes: ",[493,701,560],{"class":553},[493,703,557],{"class":502},[493,705,706],{"class":553},"None",[493,708,563],{"class":502},[493,710,711,713],{"class":433,"line":506},[493,712,647],{"class":498},[493,714,715],{"class":502}," write_product_to_db(product_id, changes)\n",[493,717,718],{"class":433,"line":513},[493,719,720],{"class":621},"    # Explicit invalidation: drop the key so the next read repopulates fresh.\n",[493,722,723,725,728,731,733,735,737,739,742],{"class":433,"line":527},[493,724,647],{"class":498},[493,726,727],{"class":502}," redis.delete(",[493,729,730],{"class":498},"f",[493,732,579],{"class":578},[493,734,582],{"class":553},[493,736,585],{"class":502},[493,738,588],{"class":553},[493,740,741],{"class":578},"\"",[493,743,662],{"class":502},[493,745,746],{"class":433,"line":532},[493,747,510],{"emptyLinePlaceholder":509},[493,749,750],{"class":433,"line":537},[493,751,510],{"emptyLinePlaceholder":509},[493,753,754,756,758,761,764,767,770,772],{"class":433,"line":566},[493,755,540],{"class":498},[493,757,543],{"class":498},[493,759,760],{"class":546}," get_with_lock",[493,762,763],{"class":502},"(redis: Redis, key: ",[493,765,766],{"class":553},"str",[493,768,769],{"class":502},", loader) -> ",[493,771,560],{"class":553},[493,773,563],{"class":502},[493,775,776,778,780,782,784],{"class":433,"line":594},[493,777,597],{"class":498},[493,779,600],{"class":502},[493,781,603],{"class":498},[493,783,606],{"class":498},[493,785,609],{"class":502},[493,787,788,790],{"class":433,"line":612},[493,789,615],{"class":498},[493,791,792],{"class":502}," json.loads(cached)\n",[493,794,795],{"class":433,"line":625},[493,796,797],{"class":621},"    # Only one caller computes; others briefly retry, preventing a stampede.\n",[493,799,800,802,804,807,809,812,814,817,819,821,824,827,829,832,834,837,839,841,843,846],{"class":433,"line":638},[493,801,597],{"class":498},[493,803,606],{"class":498},[493,805,806],{"class":502}," redis.set(",[493,808,730],{"class":498},[493,810,811],{"class":578},"\"lock:",[493,813,582],{"class":553},[493,815,816],{"class":502},"key",[493,818,588],{"class":553},[493,820,741],{"class":578},[493,822,823],{"class":502},", ",[493,825,826],{"class":578},"\"1\"",[493,828,823],{"class":502},[493,830,831],{"class":653},"nx",[493,833,572],{"class":498},[493,835,836],{"class":553},"True",[493,838,823],{"class":502},[493,840,654],{"class":653},[493,842,572],{"class":498},[493,844,845],{"class":553},"5",[493,847,848],{"class":502},"):\n",[493,850,851,854,856,858],{"class":433,"line":644},[493,852,853],{"class":502},"        value ",[493,855,572],{"class":498},[493,857,606],{"class":498},[493,859,860],{"class":502}," loader()\n",[493,862,863,866,869,871,873,875],{"class":433,"line":665},[493,864,865],{"class":498},"        await",[493,867,868],{"class":502}," redis.set(key, json.dumps(value), ",[493,870,654],{"class":653},[493,872,572],{"class":498},[493,874,659],{"class":553},[493,876,662],{"class":502},[493,878,880,882,884,886,888,890,892,894,896],{"class":433,"line":879},14,[493,881,865],{"class":498},[493,883,727],{"class":502},[493,885,730],{"class":498},[493,887,811],{"class":578},[493,889,582],{"class":553},[493,891,816],{"class":502},[493,893,588],{"class":553},[493,895,741],{"class":578},[493,897,662],{"class":502},[493,899,901,903],{"class":433,"line":900},15,[493,902,615],{"class":498},[493,904,905],{"class":502}," value\n",[493,907,909,911,914,917],{"class":433,"line":908},16,[493,910,647],{"class":498},[493,912,913],{"class":502}," asyncio.sleep(",[493,915,916],{"class":553},"0.05",[493,918,662],{"class":502},[493,920,922,924,926],{"class":433,"line":921},17,[493,923,668],{"class":498},[493,925,606],{"class":498},[493,927,928],{"class":502}," get_with_lock(redis, key, loader)\n",[320,930,931,932,935],{},"The Redis response-caching build-out is detailed in ",[327,933,171],{"href":934},"\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi\u002F",".",[475,937,939],{"id":938},"async-and-performance-notes","Async and Performance Notes",[320,941,942],{},"Use an async Redis client so cache access yields the loop. Cache the representation that removes the most repeated work — a serialized response skips re-serialization for large nested payloads, while a cached model is more reusable across endpoints. Keep values small and set a TTL on every key so a forgotten invalidation cannot serve stale data forever.",[475,944,946],{"id":945},"testing-strategy","Testing Strategy",[320,948,949],{},"Assert hit, miss, and invalidation behavior with a fake or test Redis:",[483,951,953],{"className":485,"code":952,"language":487,"meta":488,"style":488},"async def test_cache_aside(redis, db):\n    first = await get_product(redis, 1)       # Miss: loads from DB, populates.\n    db.calls.clear()\n    second = await get_product(redis, 1)      # Hit: no DB call.\n    assert second == first and not db.calls\n    await update_product(redis, 1, {\"name\": \"new\"})\n    assert await redis.get(\"product:1\") is None   # Invalidated.\n",[490,954,955,967,988,993,1012,1035,1059],{"__ignoreMap":488},[493,956,957,959,961,964],{"class":433,"line":495},[493,958,540],{"class":498},[493,960,543],{"class":498},[493,962,963],{"class":546}," test_cache_aside",[493,965,966],{"class":502},"(redis, db):\n",[493,968,969,972,974,976,979,982,985],{"class":433,"line":506},[493,970,971],{"class":502},"    first ",[493,973,572],{"class":498},[493,975,606],{"class":498},[493,977,978],{"class":502}," get_product(redis, ",[493,980,981],{"class":553},"1",[493,983,984],{"class":502},")       ",[493,986,987],{"class":621},"# Miss: loads from DB, populates.\n",[493,989,990],{"class":433,"line":513},[493,991,992],{"class":502},"    db.calls.clear()\n",[493,994,995,998,1000,1002,1004,1006,1009],{"class":433,"line":527},[493,996,997],{"class":502},"    second ",[493,999,572],{"class":498},[493,1001,606],{"class":498},[493,1003,978],{"class":502},[493,1005,981],{"class":553},[493,1007,1008],{"class":502},")      ",[493,1010,1011],{"class":621},"# Hit: no DB call.\n",[493,1013,1014,1017,1020,1023,1026,1029,1032],{"class":433,"line":532},[493,1015,1016],{"class":498},"    assert",[493,1018,1019],{"class":502}," second ",[493,1021,1022],{"class":498},"==",[493,1024,1025],{"class":502}," first ",[493,1027,1028],{"class":498},"and",[493,1030,1031],{"class":498}," not",[493,1033,1034],{"class":502}," db.calls\n",[493,1036,1037,1039,1042,1044,1047,1050,1053,1056],{"class":433,"line":537},[493,1038,647],{"class":498},[493,1040,1041],{"class":502}," update_product(redis, ",[493,1043,981],{"class":553},[493,1045,1046],{"class":502},", {",[493,1048,1049],{"class":578},"\"name\"",[493,1051,1052],{"class":502},": ",[493,1054,1055],{"class":578},"\"new\"",[493,1057,1058],{"class":502},"})\n",[493,1060,1061,1063,1065,1068,1071,1074,1077,1080],{"class":433,"line":566},[493,1062,1016],{"class":498},[493,1064,606],{"class":498},[493,1066,1067],{"class":502}," redis.get(",[493,1069,1070],{"class":578},"\"product:1\"",[493,1072,1073],{"class":502},") ",[493,1075,1076],{"class":498},"is",[493,1078,1079],{"class":553}," None",[493,1081,1082],{"class":621},"   # Invalidated.\n",[475,1084,1086],{"id":1085},"failure-modes-and-debugging","Failure Modes and Debugging",[1088,1089,1090,1098,1104,1110],"ul",{},[1091,1092,1093,1097],"li",{},[1094,1095,1096],"strong",{},"No TTL."," A key with no expiry serves stale data indefinitely after a missed invalidation; always set one.",[1091,1099,1100,1103],{},[1094,1101,1102],{},"Stampedes."," A hot key expiring under load floods the database; add a lock or pre-refresh.",[1091,1105,1106,1109],{},[1094,1107,1108],{},"Caching user-specific data globally."," A shared key leaking per-user data is a security bug; include the identity in the key.",[1091,1111,1112,1115,1116,935],{},[1094,1113,1114],{},"Sync Redis client."," Blocks the loop; use the async client, per ",[327,1117,1119],{"href":1118},"\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002F","Async Correctness and Concurrency",[475,1121,1123],{"id":1122},"related-reading","Related Reading",[1088,1125,1126,1134,1141],{},[1091,1127,1128,1131,1132,935],{},[1094,1129,1130],{},"Up to the section:"," ",[327,1133,330],{"href":329},[1091,1135,1136,1131,1139,935],{},[1094,1137,1138],{},"Hands-on guide:",[327,1140,171],{"href":934},[1091,1142,1143,1131,1146,1149,1150,935],{},[1094,1144,1145],{},"Composes with:",[327,1147,1148],{"href":334},"Async Database Sessions"," and ",[327,1151,1152],{"href":339},"Performance Optimization for Models",[1154,1155,1156],"style",{},"html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":488,"searchDepth":506,"depth":506,"links":1158},[1159,1160,1161,1162,1163,1164],{"id":477,"depth":506,"text":478},{"id":674,"depth":506,"text":675},{"id":938,"depth":506,"text":939},{"id":945,"depth":506,"text":946},{"id":1085,"depth":506,"text":1086},{"id":1122,"depth":506,"text":1123},"Cache effectively in FastAPI: the cache-aside pattern with Redis, choosing TTLs, cache invalidation, stampede protection, and caching serialized responses for hot endpoints.","md",{"slug":1168,"type":1169,"breadcrumb":1170,"datePublished":1179,"dateModified":1180,"faq":1181},"caching-strategies","cluster",[1171,1174,1176],{"label":1172,"path":1173},"Home","\u002F",{"label":1175,"path":329},"Async, Background Tasks & Observability",{"label":1177,"path":1178},"Caching Strategies","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002F","2026-02-15","2026-06-18",[1182,1185,1188,1191],{"q":1183,"a":1184},"What is the cache-aside pattern?","The application checks the cache first, and on a miss it loads from the database, stores the result in the cache, and returns it. The cache sits beside the database rather than in front of it, so the application controls exactly what is cached and for how long. It is the default pattern for read-heavy endpoints with tolerable staleness.",{"q":1186,"a":1187},"How do I choose a TTL?","Set the TTL to the longest staleness your use case tolerates. A short TTL keeps data fresh but cuts the hit rate and offers less database relief; a long TTL maximizes hit rate but serves older data. For data that changes on a known event, prefer explicit invalidation over a long TTL.",{"q":1189,"a":1190},"What is a cache stampede and how do I prevent it?","A stampede happens when a popular key expires and many concurrent requests all miss and hit the database at once. Prevent it with a short lock so only one request recomputes the value while others wait or serve a slightly stale copy, or by refreshing hot keys before they expire.",{"q":1192,"a":1193},"Should I cache the database row or the serialized response?","Cache whichever removes the most repeated work. Caching the serialized JSON response also skips re-serialization, which matters for large nested payloads, but it is harder to reuse across endpoints. Caching the row or model is more reusable. Choose based on where your CPU actually goes.",{"title":159,"description":1165},"jhgfVUHBCrF4fUdWJMpF9_drwZPkCyxpp8jfrjpIPko",[1197,1197],null,1781809863387]