[{"data":1,"prerenderedAt":1156},["ShallowReactive",2],{"nav":3,"page-\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Frunning-arq-workers-with-fastapi\u002F":310,"surround-\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Frunning-arq-workers-with-fastapi\u002F":1154},[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":153,"body":312,"description":1108,"extension":1109,"meta":1110,"navigation":452,"path":154,"seo":1152,"stem":155,"__hash__":1153},"content\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Frunning-arq-workers-with-fastapi\u002Findex.md",{"type":313,"value":314,"toc":1095},"minimark",[315,319,326,353,367,372,375,379,390,394,399,642,646,750,754,895,899,916,920,944,948,1065,1069,1091],[316,317,153],"h1",{"id":318},"running-arq-workers-with-fastapi",[320,321,322],"p",{},[323,324,325],"strong",{},"Key takeaways:",[327,328,329,333,336,344,350],"ul",{},[330,331,332],"li",{},"ARQ tasks are async functions that take a context dict plus their arguments.",[330,334,335],{},"The FastAPI process enqueues jobs through a Redis pool opened in lifespan.",[330,337,338,339,343],{},"Pass an explicit ",[340,341,342],"code",{},"_job_id"," to deduplicate double submissions.",[330,345,346,349],{},[340,347,348],{},"WorkerSettings"," configures functions, retries, and cron jobs.",[330,351,352],{},"The worker runs as its own process, scaling independently of the web tier.",[320,354,355,356,361,362,366],{},"This guide implements the ARQ path from ",[357,358,360],"a",{"href":359},"\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002F","Background Task Processing"," and its ",[357,363,365],{"href":364},"\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Ffastapi-backgroundtasks-vs-celery-vs-arq\u002F","comparison with Celery",".",[368,369,371],"h2",{"id":370},"the-problem-this-solves","The Problem This Solves",[320,373,374],{},"You need durable, retryable background work in an async FastAPI codebase without the ceremony of Celery. ARQ is async-native and Redis-backed, so it fits an async stack with minimal moving parts while still giving you persistence, retries, and scheduling.",[368,376,378],{"id":377},"prerequisites","Prerequisites",[327,380,381,384],{},[330,382,383],{},"Redis reachable from both the web and worker processes.",[330,385,386,389],{},[340,387,388],{},"arq"," installed; Python 3.11+.",[368,391,393],{"id":392},"step-by-step-implementation","Step-by-Step Implementation",[395,396,398],"h3",{"id":397},"_1-define-tasks-and-worker-settings","1. Define tasks and worker settings",[400,401,406],"pre",{"className":402,"code":403,"language":404,"meta":405,"style":405},"language-python shiki shiki-themes github-light","# app\u002Fworker.py\nfrom arq import cron\nfrom arq.connections import RedisSettings\n\n\nasync def sync_invoice(ctx: dict, invoice_id: str) -> None:\n    # Idempotent: safe for ARQ to retry on failure.\n    await push_to_accounting(invoice_id)\n\n\nasync def nightly_cleanup(ctx: dict) -> None:\n    await purge_expired_tokens()\n\n\nclass WorkerSettings:\n    functions = [sync_invoice]\n    cron_jobs = [cron(nightly_cleanup, hour=3, minute=0)]   # Runs at 03:00 daily.\n    redis_settings = RedisSettings()\n    max_tries = 5                                           # Retry with backoff.\n","python","",[340,407,408,417,434,447,454,459,494,500,509,514,519,539,547,552,557,568,580,617,628],{"__ignoreMap":405},[409,410,413],"span",{"class":411,"line":412},"line",1,[409,414,416],{"class":415},"sAwPA","# app\u002Fworker.py\n",[409,418,420,424,428,431],{"class":411,"line":419},2,[409,421,423],{"class":422},"sD7c4","from",[409,425,427],{"class":426},"sgsFI"," arq ",[409,429,430],{"class":422},"import",[409,432,433],{"class":426}," cron\n",[409,435,437,439,442,444],{"class":411,"line":436},3,[409,438,423],{"class":422},[409,440,441],{"class":426}," arq.connections ",[409,443,430],{"class":422},[409,445,446],{"class":426}," RedisSettings\n",[409,448,450],{"class":411,"line":449},4,[409,451,453],{"emptyLinePlaceholder":452},true,"\n",[409,455,457],{"class":411,"line":456},5,[409,458,453],{"emptyLinePlaceholder":452},[409,460,462,465,468,472,475,479,482,485,488,491],{"class":411,"line":461},6,[409,463,464],{"class":422},"async",[409,466,467],{"class":422}," def",[409,469,471],{"class":470},"s7eDp"," sync_invoice",[409,473,474],{"class":426},"(ctx: ",[409,476,478],{"class":477},"sYu0t","dict",[409,480,481],{"class":426},", invoice_id: ",[409,483,484],{"class":477},"str",[409,486,487],{"class":426},") -> ",[409,489,490],{"class":477},"None",[409,492,493],{"class":426},":\n",[409,495,497],{"class":411,"line":496},7,[409,498,499],{"class":415},"    # Idempotent: safe for ARQ to retry on failure.\n",[409,501,503,506],{"class":411,"line":502},8,[409,504,505],{"class":422},"    await",[409,507,508],{"class":426}," push_to_accounting(invoice_id)\n",[409,510,512],{"class":411,"line":511},9,[409,513,453],{"emptyLinePlaceholder":452},[409,515,517],{"class":411,"line":516},10,[409,518,453],{"emptyLinePlaceholder":452},[409,520,522,524,526,529,531,533,535,537],{"class":411,"line":521},11,[409,523,464],{"class":422},[409,525,467],{"class":422},[409,527,528],{"class":470}," nightly_cleanup",[409,530,474],{"class":426},[409,532,478],{"class":477},[409,534,487],{"class":426},[409,536,490],{"class":477},[409,538,493],{"class":426},[409,540,542,544],{"class":411,"line":541},12,[409,543,505],{"class":422},[409,545,546],{"class":426}," purge_expired_tokens()\n",[409,548,550],{"class":411,"line":549},13,[409,551,453],{"emptyLinePlaceholder":452},[409,553,555],{"class":411,"line":554},14,[409,556,453],{"emptyLinePlaceholder":452},[409,558,560,563,566],{"class":411,"line":559},15,[409,561,562],{"class":422},"class",[409,564,565],{"class":470}," WorkerSettings",[409,567,493],{"class":426},[409,569,571,574,577],{"class":411,"line":570},16,[409,572,573],{"class":426},"    functions ",[409,575,576],{"class":422},"=",[409,578,579],{"class":426}," [sync_invoice]\n",[409,581,583,586,588,591,595,597,600,603,606,608,611,614],{"class":411,"line":582},17,[409,584,585],{"class":426},"    cron_jobs ",[409,587,576],{"class":422},[409,589,590],{"class":426}," [cron(nightly_cleanup, ",[409,592,594],{"class":593},"sqxcx","hour",[409,596,576],{"class":422},[409,598,599],{"class":477},"3",[409,601,602],{"class":426},", ",[409,604,605],{"class":593},"minute",[409,607,576],{"class":422},[409,609,610],{"class":477},"0",[409,612,613],{"class":426},")]   ",[409,615,616],{"class":415},"# Runs at 03:00 daily.\n",[409,618,620,623,625],{"class":411,"line":619},18,[409,621,622],{"class":426},"    redis_settings ",[409,624,576],{"class":422},[409,626,627],{"class":426}," RedisSettings()\n",[409,629,631,634,636,639],{"class":411,"line":630},19,[409,632,633],{"class":426},"    max_tries ",[409,635,576],{"class":422},[409,637,638],{"class":477}," 5",[409,640,641],{"class":415},"                                           # Retry with backoff.\n",[395,643,645],{"id":644},"_2-open-the-pool-in-the-fastapi-lifespan","2. Open the pool in the FastAPI lifespan",[400,647,649],{"className":402,"code":648,"language":404,"meta":405,"style":405},"from contextlib import asynccontextmanager\n\nfrom arq import create_pool\nfrom arq.connections import RedisSettings\nfrom fastapi import FastAPI\n\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    app.state.arq = await create_pool(RedisSettings())\n    yield\n    await app.state.arq.close()\n",[340,650,651,663,667,678,688,700,704,708,713,725,738,743],{"__ignoreMap":405},[409,652,653,655,658,660],{"class":411,"line":412},[409,654,423],{"class":422},[409,656,657],{"class":426}," contextlib ",[409,659,430],{"class":422},[409,661,662],{"class":426}," asynccontextmanager\n",[409,664,665],{"class":411,"line":419},[409,666,453],{"emptyLinePlaceholder":452},[409,668,669,671,673,675],{"class":411,"line":436},[409,670,423],{"class":422},[409,672,427],{"class":426},[409,674,430],{"class":422},[409,676,677],{"class":426}," create_pool\n",[409,679,680,682,684,686],{"class":411,"line":449},[409,681,423],{"class":422},[409,683,441],{"class":426},[409,685,430],{"class":422},[409,687,446],{"class":426},[409,689,690,692,695,697],{"class":411,"line":456},[409,691,423],{"class":422},[409,693,694],{"class":426}," fastapi ",[409,696,430],{"class":422},[409,698,699],{"class":426}," FastAPI\n",[409,701,702],{"class":411,"line":461},[409,703,453],{"emptyLinePlaceholder":452},[409,705,706],{"class":411,"line":496},[409,707,453],{"emptyLinePlaceholder":452},[409,709,710],{"class":411,"line":502},[409,711,712],{"class":470},"@asynccontextmanager\n",[409,714,715,717,719,722],{"class":411,"line":511},[409,716,464],{"class":422},[409,718,467],{"class":422},[409,720,721],{"class":470}," lifespan",[409,723,724],{"class":426},"(app: FastAPI):\n",[409,726,727,730,732,735],{"class":411,"line":516},[409,728,729],{"class":426},"    app.state.arq ",[409,731,576],{"class":422},[409,733,734],{"class":422}," await",[409,736,737],{"class":426}," create_pool(RedisSettings())\n",[409,739,740],{"class":411,"line":521},[409,741,742],{"class":422},"    yield\n",[409,744,745,747],{"class":411,"line":541},[409,746,505],{"class":422},[409,748,749],{"class":426}," app.state.arq.close()\n",[395,751,753],{"id":752},"_3-enqueue-from-a-route","3. Enqueue from a route",[400,755,757],{"className":402,"code":756,"language":404,"meta":405,"style":405},"from fastapi import Request\n\n\n@app.post(\"\u002Finvoices\u002F{invoice_id}\u002Fsync\")\nasync def sync(invoice_id: str, request: Request) -> dict[str, str]:\n    # _job_id dedupes repeat submissions of the same invoice.\n    await request.app.state.arq.enqueue_job(\"sync_invoice\", invoice_id,\n                                            _job_id=f\"sync:{invoice_id}\")\n    return {\"status\": \"queued\", \"invoice_id\": invoice_id}\n",[340,758,759,770,774,778,799,825,830,843,870],{"__ignoreMap":405},[409,760,761,763,765,767],{"class":411,"line":412},[409,762,423],{"class":422},[409,764,694],{"class":426},[409,766,430],{"class":422},[409,768,769],{"class":426}," Request\n",[409,771,772],{"class":411,"line":419},[409,773,453],{"emptyLinePlaceholder":452},[409,775,776],{"class":411,"line":436},[409,777,453],{"emptyLinePlaceholder":452},[409,779,780,783,786,790,793,796],{"class":411,"line":449},[409,781,782],{"class":470},"@app.post",[409,784,785],{"class":426},"(",[409,787,789],{"class":788},"sYBdl","\"\u002Finvoices\u002F",[409,791,792],{"class":477},"{invoice_id}",[409,794,795],{"class":788},"\u002Fsync\"",[409,797,798],{"class":426},")\n",[409,800,801,803,805,808,811,813,816,818,820,822],{"class":411,"line":456},[409,802,464],{"class":422},[409,804,467],{"class":422},[409,806,807],{"class":470}," sync",[409,809,810],{"class":426},"(invoice_id: ",[409,812,484],{"class":477},[409,814,815],{"class":426},", request: Request) -> dict[",[409,817,484],{"class":477},[409,819,602],{"class":426},[409,821,484],{"class":477},[409,823,824],{"class":426},"]:\n",[409,826,827],{"class":411,"line":461},[409,828,829],{"class":415},"    # _job_id dedupes repeat submissions of the same invoice.\n",[409,831,832,834,837,840],{"class":411,"line":496},[409,833,505],{"class":422},[409,835,836],{"class":426}," request.app.state.arq.enqueue_job(",[409,838,839],{"class":788},"\"sync_invoice\"",[409,841,842],{"class":426},", invoice_id,\n",[409,844,845,848,850,853,856,859,862,865,868],{"class":411,"line":502},[409,846,847],{"class":593},"                                            _job_id",[409,849,576],{"class":422},[409,851,852],{"class":422},"f",[409,854,855],{"class":788},"\"sync:",[409,857,858],{"class":477},"{",[409,860,861],{"class":426},"invoice_id",[409,863,864],{"class":477},"}",[409,866,867],{"class":788},"\"",[409,869,798],{"class":426},[409,871,872,875,878,881,884,887,889,892],{"class":411,"line":511},[409,873,874],{"class":422},"    return",[409,876,877],{"class":426}," {",[409,879,880],{"class":788},"\"status\"",[409,882,883],{"class":426},": ",[409,885,886],{"class":788},"\"queued\"",[409,888,602],{"class":426},[409,890,891],{"class":788},"\"invoice_id\"",[409,893,894],{"class":426},": invoice_id}\n",[395,896,898],{"id":897},"_4-run-the-worker-as-its-own-process","4. Run the worker as its own process",[400,900,904],{"className":901,"code":902,"language":903,"meta":405,"style":405},"language-dockerfile shiki shiki-themes github-light","# Separate process → workers scale independently of the web tier.\nCMD [\"arq\", \"app.worker.WorkerSettings\"]\n","dockerfile",[340,905,906,911],{"__ignoreMap":405},[409,907,908],{"class":411,"line":412},[409,909,910],{},"# Separate process → workers scale independently of the web tier.\n",[409,912,913],{"class":411,"line":419},[409,914,915],{},"CMD [\"arq\", \"app.worker.WorkerSettings\"]\n",[368,917,919],{"id":918},"edge-cases-and-gotchas","Edge Cases and Gotchas",[327,921,922,932,938],{},[330,923,924,927,928,366],{},[323,925,926],{},"Separate database pool."," The worker creates its own engine; budget its connections alongside the web tier, per ",[357,929,931],{"href":930},"\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002F","Async Database Sessions",[330,933,934,937],{},[323,935,936],{},"Large arguments."," Pass IDs, not ORM objects; arguments are serialized to Redis.",[330,939,940,943],{},[323,941,942],{},"Job result expiry."," Results expire by default; configure retention if you read them.",[368,945,947],{"id":946},"verification","Verification",[400,949,951],{"className":402,"code":950,"language":404,"meta":405,"style":405},"async def test_enqueue(client_app):\n    pool = client_app.state.arq\n    job = await pool.enqueue_job(\"sync_invoice\", \"inv-1\", _job_id=\"sync:inv-1\")\n    assert job is not None\n    # Enqueuing the same _job_id again returns None (deduplicated).\n    dup = await pool.enqueue_job(\"sync_invoice\", \"inv-1\", _job_id=\"sync:inv-1\")\n    assert dup is None\n",[340,952,953,965,975,1005,1022,1027,1054],{"__ignoreMap":405},[409,954,955,957,959,962],{"class":411,"line":412},[409,956,464],{"class":422},[409,958,467],{"class":422},[409,960,961],{"class":470}," test_enqueue",[409,963,964],{"class":426},"(client_app):\n",[409,966,967,970,972],{"class":411,"line":419},[409,968,969],{"class":426},"    pool ",[409,971,576],{"class":422},[409,973,974],{"class":426}," client_app.state.arq\n",[409,976,977,980,982,984,987,989,991,994,996,998,1000,1003],{"class":411,"line":436},[409,978,979],{"class":426},"    job ",[409,981,576],{"class":422},[409,983,734],{"class":422},[409,985,986],{"class":426}," pool.enqueue_job(",[409,988,839],{"class":788},[409,990,602],{"class":426},[409,992,993],{"class":788},"\"inv-1\"",[409,995,602],{"class":426},[409,997,342],{"class":593},[409,999,576],{"class":422},[409,1001,1002],{"class":788},"\"sync:inv-1\"",[409,1004,798],{"class":426},[409,1006,1007,1010,1013,1016,1019],{"class":411,"line":449},[409,1008,1009],{"class":422},"    assert",[409,1011,1012],{"class":426}," job ",[409,1014,1015],{"class":422},"is",[409,1017,1018],{"class":422}," not",[409,1020,1021],{"class":477}," None\n",[409,1023,1024],{"class":411,"line":456},[409,1025,1026],{"class":415},"    # Enqueuing the same _job_id again returns None (deduplicated).\n",[409,1028,1029,1032,1034,1036,1038,1040,1042,1044,1046,1048,1050,1052],{"class":411,"line":461},[409,1030,1031],{"class":426},"    dup ",[409,1033,576],{"class":422},[409,1035,734],{"class":422},[409,1037,986],{"class":426},[409,1039,839],{"class":788},[409,1041,602],{"class":426},[409,1043,993],{"class":788},[409,1045,602],{"class":426},[409,1047,342],{"class":593},[409,1049,576],{"class":422},[409,1051,1002],{"class":788},[409,1053,798],{"class":426},[409,1055,1056,1058,1061,1063],{"class":411,"line":496},[409,1057,1009],{"class":422},[409,1059,1060],{"class":426}," dup ",[409,1062,1015],{"class":422},[409,1064,1021],{"class":477},[368,1066,1068],{"id":1067},"related-reading","Related Reading",[327,1070,1071,1079],{},[330,1072,1073,1076,1077,366],{},[323,1074,1075],{},"Up to the topic:"," ",[357,1078,360],{"href":359},[330,1080,1081,1076,1084,1086,1087,366],{},[323,1082,1083],{},"Related guides:",[357,1085,147],{"href":364}," and ",[357,1088,1090],{"href":1089},"\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002F","Observability and Tracing",[1092,1093,1094],"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 .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}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);}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}",{"title":405,"searchDepth":419,"depth":419,"links":1096},[1097,1098,1099,1105,1106,1107],{"id":370,"depth":419,"text":371},{"id":377,"depth":419,"text":378},{"id":392,"depth":419,"text":393,"children":1100},[1101,1102,1103,1104],{"id":397,"depth":436,"text":398},{"id":644,"depth":436,"text":645},{"id":752,"depth":436,"text":753},{"id":897,"depth":436,"text":898},{"id":918,"depth":419,"text":919},{"id":946,"depth":419,"text":947},{"id":1067,"depth":419,"text":1068},"Run ARQ workers alongside FastAPI: define tasks, share a Redis pool, enqueue jobs from routes, configure retries and cron jobs, and deploy the worker as a separate process.","md",{"slug":318,"type":1111,"breadcrumb":1112,"datePublished":1122,"dateModified":1123,"howto":1124,"faq":1142},"long_tail",[1113,1116,1119,1120],{"label":1114,"path":1115},"Home","\u002F",{"label":1117,"path":1118},"Async, Background Tasks & Observability","\u002Fasync-background-tasks-observability\u002F",{"label":360,"path":359},{"label":153,"path":1121},"\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Frunning-arq-workers-with-fastapi\u002F","2026-02-20","2026-06-18",{"name":1125,"steps":1126},"Run ARQ workers with FastAPI",[1127,1130,1133,1136,1139],{"name":1128,"text":1129},"Define task functions","Write async task functions that take a context dict and the job arguments.",{"name":1131,"text":1132},"Create the Redis pool in lifespan","Open an ARQ Redis pool at startup and store it on app.state for enqueuing.",{"name":1134,"text":1135},"Enqueue from routes","Call enqueue_job with a dedupe job id so duplicate submissions collapse.",{"name":1137,"text":1138},"Configure WorkerSettings","List functions, set max_tries, and add cron jobs for scheduled work.",{"name":1140,"text":1141},"Deploy the worker separately","Run arq as its own process so workers scale independently of web workers.",[1143,1146,1149],{"q":1144,"a":1145},"How does FastAPI enqueue a job that an ARQ worker runs?","The FastAPI process opens an ARQ Redis pool at startup and calls enqueue_job on it from a route, which pushes the job onto Redis. A separately running ARQ worker process polls Redis, picks up the job, and executes the matching function. The two processes share only Redis, not memory.",{"q":1147,"a":1148},"How do I prevent duplicate ARQ jobs from a double-submitted request?","Pass an explicit _job_id when enqueuing, derived from the operation's natural key. ARQ deduplicates by job id within its window, so two identical submissions collapse into one job. Combine this with an idempotent task body for full safety.",{"q":1150,"a":1151},"Can ARQ run scheduled or recurring jobs?","Yes. Add cron jobs to WorkerSettings with the cron helper, specifying the function and schedule. The worker runs them on time without a separate scheduler process, which is simpler than Celery's separate beat service for many workloads.",{"title":153,"description":1108},"v2OJeua800N3SQeRmeebCWKrydzwDQ_0pkB4z6Oz4pk",[1155,1155],null,1781809863609]