[{"data":1,"prerenderedAt":940},["ShallowReactive",2],{"nav":3,"page-\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi\u002F":310,"surround-\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi\u002F":938},[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":165,"body":312,"description":891,"extension":892,"meta":893,"navigation":606,"path":166,"seo":936,"stem":167,"__hash__":937},"content\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi\u002Findex.md",{"type":313,"value":314,"toc":878},"minimark",[315,319,326,345,354,359,362,366,378,382,387,431,435,516,520,648,652,696,700,724,728,846,850,874],[316,317,165],"h1",{"id":318},"cache-invalidation-patterns-in-fastapi",[320,321,322],"p",{},[323,324,325],"strong",{},"Key takeaways:",[327,328,329,333,336,339,342],"ul",{},[330,331,332],"li",{},"Always set a TTL so a missed invalidation eventually self-corrects.",[330,334,335],{},"Invalidate in the same code path that mutates the data.",[330,337,338],{},"Use versioned key prefixes to invalidate a whole group with one increment.",[330,340,341],{},"Propagate invalidation across instances with events.",[330,343,344],{},"Test that a write is visible on the next read.",[320,346,347,348,353],{},"This guide tackles the hard half of ",[349,350,352],"a",{"href":351},"\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002F","Caching Strategies",": keeping cached data correct.",[355,356,358],"h2",{"id":357},"the-problem-this-solves","The Problem This Solves",[320,360,361],{},"Caching is easy until data changes. The failure mode is silent — a cache that keeps serving an old value after an update — and it erodes trust in the API. Disciplined invalidation keeps the cache correct without throwing away its benefit.",[355,363,365],{"id":364},"prerequisites","Prerequisites",[327,367,368,375],{},[330,369,370,371,374],{},"A working cache-aside setup (see ",[349,372,171],{"href":373},"\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi\u002F",").",[330,376,377],{},"An async Redis client.",[355,379,381],{"id":380},"step-by-step-implementation","Step-by-Step Implementation",[383,384,386],"h3",{"id":385},"_1-ttl-as-the-safety-net","1. TTL as the safety net",[388,389,394],"pre",{"className":390,"code":391,"language":392,"meta":393,"style":393},"language-python shiki shiki-themes github-light","# Every cached value expires, so a missed explicit invalidation self-heals.\nawait redis.set(key, json.dumps(value), ex=300)\n","python","",[395,396,397,406],"code",{"__ignoreMap":393},[398,399,402],"span",{"class":400,"line":401},"line",1,[398,403,405],{"class":404},"sAwPA","# Every cached value expires, so a missed explicit invalidation self-heals.\n",[398,407,409,413,417,421,424,428],{"class":400,"line":408},2,[398,410,412],{"class":411},"sD7c4","await",[398,414,416],{"class":415},"sgsFI"," redis.set(key, json.dumps(value), ",[398,418,420],{"class":419},"sqxcx","ex",[398,422,423],{"class":411},"=",[398,425,427],{"class":426},"sYu0t","300",[398,429,430],{"class":415},")\n",[383,432,434],{"id":433},"_2-write-path-invalidation","2. Write-path invalidation",[388,436,438],{"className":390,"code":437,"language":392,"meta":393,"style":393},"async def update_product(redis, product_id: int, changes: dict) -> None:\n    await write_product(product_id, changes)\n    # Same code path that mutates also drops the stale key.\n    await redis.delete(f\"product:{product_id}\")\n",[395,439,440,473,481,487],{"__ignoreMap":393},[398,441,442,445,448,452,455,458,461,464,467,470],{"class":400,"line":401},[398,443,444],{"class":411},"async",[398,446,447],{"class":411}," def",[398,449,451],{"class":450},"s7eDp"," update_product",[398,453,454],{"class":415},"(redis, product_id: ",[398,456,457],{"class":426},"int",[398,459,460],{"class":415},", changes: ",[398,462,463],{"class":426},"dict",[398,465,466],{"class":415},") -> ",[398,468,469],{"class":426},"None",[398,471,472],{"class":415},":\n",[398,474,475,478],{"class":400,"line":408},[398,476,477],{"class":411},"    await",[398,479,480],{"class":415}," write_product(product_id, changes)\n",[398,482,484],{"class":400,"line":483},3,[398,485,486],{"class":404},"    # Same code path that mutates also drops the stale key.\n",[398,488,490,492,495,498,502,505,508,511,514],{"class":400,"line":489},4,[398,491,477],{"class":411},[398,493,494],{"class":415}," redis.delete(",[398,496,497],{"class":411},"f",[398,499,501],{"class":500},"sYBdl","\"product:",[398,503,504],{"class":426},"{",[398,506,507],{"class":415},"product_id",[398,509,510],{"class":426},"}",[398,512,513],{"class":500},"\"",[398,515,430],{"class":415},[383,517,519],{"id":518},"_3-versioned-keys-for-group-invalidation","3. Versioned keys for group invalidation",[388,521,523],{"className":390,"code":522,"language":392,"meta":393,"style":393},"async def catalog_key(redis, suffix: str) -> str:\n    version = await redis.get(\"catalog:version\") or \"1\"\n    return f\"catalog:v{version}:{suffix}\"\n\n\nasync def invalidate_catalog(redis) -> None:\n    # One atomic increment retires every key in the group.\n    await redis.incr(\"catalog:version\")\n",[395,524,525,546,571,602,608,613,630,636],{"__ignoreMap":393},[398,526,527,529,531,534,537,540,542,544],{"class":400,"line":401},[398,528,444],{"class":411},[398,530,447],{"class":411},[398,532,533],{"class":450}," catalog_key",[398,535,536],{"class":415},"(redis, suffix: ",[398,538,539],{"class":426},"str",[398,541,466],{"class":415},[398,543,539],{"class":426},[398,545,472],{"class":415},[398,547,548,551,553,556,559,562,565,568],{"class":400,"line":408},[398,549,550],{"class":415},"    version ",[398,552,423],{"class":411},[398,554,555],{"class":411}," await",[398,557,558],{"class":415}," redis.get(",[398,560,561],{"class":500},"\"catalog:version\"",[398,563,564],{"class":415},") ",[398,566,567],{"class":411},"or",[398,569,570],{"class":500}," \"1\"\n",[398,572,573,576,579,582,584,587,589,592,594,597,599],{"class":400,"line":483},[398,574,575],{"class":411},"    return",[398,577,578],{"class":411}," f",[398,580,581],{"class":500},"\"catalog:v",[398,583,504],{"class":426},[398,585,586],{"class":415},"version",[398,588,510],{"class":426},[398,590,591],{"class":500},":",[398,593,504],{"class":426},[398,595,596],{"class":415},"suffix",[398,598,510],{"class":426},[398,600,601],{"class":500},"\"\n",[398,603,604],{"class":400,"line":489},[398,605,607],{"emptyLinePlaceholder":606},true,"\n",[398,609,611],{"class":400,"line":610},5,[398,612,607],{"emptyLinePlaceholder":606},[398,614,616,618,620,623,626,628],{"class":400,"line":615},6,[398,617,444],{"class":411},[398,619,447],{"class":411},[398,621,622],{"class":450}," invalidate_catalog",[398,624,625],{"class":415},"(redis) -> ",[398,627,469],{"class":426},[398,629,472],{"class":415},[398,631,633],{"class":400,"line":632},7,[398,634,635],{"class":404},"    # One atomic increment retires every key in the group.\n",[398,637,639,641,644,646],{"class":400,"line":638},8,[398,640,477],{"class":411},[398,642,643],{"class":415}," redis.incr(",[398,645,561],{"class":500},[398,647,430],{"class":415},[383,649,651],{"id":650},"_4-cross-service-propagation","4. Cross-service propagation",[388,653,655],{"className":390,"code":654,"language":392,"meta":393,"style":393},"# On a write, publish so other instances drop their local copies.\nawait redis.publish(\"invalidate\", json.dumps({\"key\": f\"product:{product_id}\"}))\n",[395,656,657,662],{"__ignoreMap":393},[398,658,659],{"class":400,"line":401},[398,660,661],{"class":404},"# On a write, publish so other instances drop their local copies.\n",[398,663,664,666,669,672,675,678,681,683,685,687,689,691,693],{"class":400,"line":408},[398,665,412],{"class":411},[398,667,668],{"class":415}," redis.publish(",[398,670,671],{"class":500},"\"invalidate\"",[398,673,674],{"class":415},", json.dumps({",[398,676,677],{"class":500},"\"key\"",[398,679,680],{"class":415},": ",[398,682,497],{"class":411},[398,684,501],{"class":500},[398,686,504],{"class":426},[398,688,507],{"class":415},[398,690,510],{"class":426},[398,692,513],{"class":500},[398,694,695],{"class":415},"}))\n",[355,697,699],{"id":698},"edge-cases-and-gotchas","Edge Cases and Gotchas",[327,701,702,708,718],{},[330,703,704,707],{},[323,705,706],{},"Race between read and write."," A read that repopulates just after a delete can re-cache stale data; a short lock or versioned keys avoids it.",[330,709,710,713,714,717],{},[323,711,712],{},"Partial keys."," Deleting ",[395,715,716],{},"product:1"," but not a list that embeds product 1 leaves the list stale; invalidate every key that contains the data.",[330,719,720,723],{},[323,721,722],{},"Over-invalidation."," Dropping too broadly on every write erases the cache's benefit; scope invalidation to what changed.",[355,725,727],{"id":726},"verification","Verification",[388,729,731],{"className":390,"code":730,"language":392,"meta":393,"style":393},"async def test_write_visible_on_next_read(client, redis):\n    client.get(\"\u002Fproducts\u002F1\")                    # Populate.\n    client.patch(\"\u002Fproducts\u002F1\", json={\"name\": \"new\"})\n    assert await redis.get(\"product:1\") is None  # Invalidated by the write.\n    body = client.get(\"\u002Fproducts\u002F1\").json()\n    assert body[\"name\"] == \"new\"                 # Next read is fresh.\n",[395,732,733,745,759,787,810,825],{"__ignoreMap":393},[398,734,735,737,739,742],{"class":400,"line":401},[398,736,444],{"class":411},[398,738,447],{"class":411},[398,740,741],{"class":450}," test_write_visible_on_next_read",[398,743,744],{"class":415},"(client, redis):\n",[398,746,747,750,753,756],{"class":400,"line":408},[398,748,749],{"class":415},"    client.get(",[398,751,752],{"class":500},"\"\u002Fproducts\u002F1\"",[398,754,755],{"class":415},")                    ",[398,757,758],{"class":404},"# Populate.\n",[398,760,761,764,766,769,772,774,776,779,781,784],{"class":400,"line":483},[398,762,763],{"class":415},"    client.patch(",[398,765,752],{"class":500},[398,767,768],{"class":415},", ",[398,770,771],{"class":419},"json",[398,773,423],{"class":411},[398,775,504],{"class":415},[398,777,778],{"class":500},"\"name\"",[398,780,680],{"class":415},[398,782,783],{"class":500},"\"new\"",[398,785,786],{"class":415},"})\n",[398,788,789,792,794,796,799,801,804,807],{"class":400,"line":489},[398,790,791],{"class":411},"    assert",[398,793,555],{"class":411},[398,795,558],{"class":415},[398,797,798],{"class":500},"\"product:1\"",[398,800,564],{"class":415},[398,802,803],{"class":411},"is",[398,805,806],{"class":426}," None",[398,808,809],{"class":404},"  # Invalidated by the write.\n",[398,811,812,815,817,820,822],{"class":400,"line":610},[398,813,814],{"class":415},"    body ",[398,816,423],{"class":411},[398,818,819],{"class":415}," client.get(",[398,821,752],{"class":500},[398,823,824],{"class":415},").json()\n",[398,826,827,829,832,834,837,840,843],{"class":400,"line":615},[398,828,791],{"class":411},[398,830,831],{"class":415}," body[",[398,833,778],{"class":500},[398,835,836],{"class":415},"] ",[398,838,839],{"class":411},"==",[398,841,842],{"class":500}," \"new\"",[398,844,845],{"class":404},"                 # Next read is fresh.\n",[355,847,849],{"id":848},"related-reading","Related Reading",[327,851,852,861],{},[330,853,854,857,858,860],{},[323,855,856],{},"Up to the topic:"," ",[349,859,352],{"href":351},".",[330,862,863,857,866,868,869,873],{},[323,864,865],{},"Related guides:",[349,867,171],{"href":373}," and ",[349,870,872],{"href":871},"\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002F","Background Task Processing"," for event-driven invalidation.",[875,876,877],"style",{},"html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}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 .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}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);}html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}",{"title":393,"searchDepth":408,"depth":408,"links":879},[880,881,882,888,889,890],{"id":357,"depth":408,"text":358},{"id":364,"depth":408,"text":365},{"id":380,"depth":408,"text":381,"children":883},[884,885,886,887],{"id":385,"depth":483,"text":386},{"id":433,"depth":483,"text":434},{"id":518,"depth":483,"text":519},{"id":650,"depth":483,"text":651},{"id":698,"depth":408,"text":699},{"id":726,"depth":408,"text":727},{"id":848,"depth":408,"text":849},"Keep FastAPI caches correct: TTL expiry, write-through and explicit deletion, key tagging for grouped invalidation, versioned keys, and event-driven invalidation across services.","md",{"slug":318,"type":894,"breadcrumb":895,"datePublished":906,"dateModified":907,"howto":908,"faq":926},"long_tail",[896,899,902,903],{"label":897,"path":898},"Home","\u002F",{"label":900,"path":901},"Async, Background Tasks & Observability","\u002Fasync-background-tasks-observability\u002F",{"label":352,"path":351},{"label":904,"path":905},"Cache Invalidation Patterns","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi\u002F","2026-02-26","2026-06-18",{"name":909,"steps":910},"Apply cache invalidation patterns in FastAPI",[911,914,917,920,923],{"name":912,"text":913},"Set a TTL on every key","Give each cached value an expiry so a missed invalidation self-corrects.",{"name":915,"text":916},"Invalidate on write","Delete or rewrite affected keys in the same code path that mutates the data.",{"name":918,"text":919},"Group keys with a version","Prefix keys with a version counter so bumping the counter invalidates a whole group at once.",{"name":921,"text":922},"Propagate across services","Publish invalidation events so other instances drop their copies.",{"name":924,"text":925},"Verify freshness","Test that a write makes the next read return updated data.",[927,930,933],{"q":928,"a":929},"Why is cache invalidation considered hard?","Because correctness depends on every code path that changes the data also updating the cache, and on doing so without races. A single forgotten write path serves stale data indefinitely. TTLs bound the damage, but precise invalidation requires discipline and, across services, coordination through events or versioned keys.",{"q":931,"a":932},"What is versioned-key invalidation?","You prefix cache keys with a version number stored in the cache, such as catalog:v7:item:1. To invalidate the whole group, increment the version to v8; every old key is now unreachable and expires naturally. It avoids scanning or deleting many keys and makes group invalidation a single atomic increment.",{"q":934,"a":935},"Should invalidation be synchronous with the write or eventual?","Delete the affected key synchronously in the write path so the next read is correct immediately. For cross-service propagation, publish an event and let other instances invalidate eventually. Combining an immediate local delete with an eventual broadcast gives correctness locally and convergence globally.",{"title":165,"description":891},"OcIC0t7-qwipEqL26DooEWpRJB2RauC32v0T6kaCNQ8",[939,939],null,1781809863622]