Experimental Django Recommender + Search Backend Best Practices
Implementation patterns for a Django backend serving mixed-results recommendations (Personalize / Databricks / microservice fan-out) and OpenSearch-backed search/feeds. 48 rules across 8 categories, ordered by execution lifecycle impact — earlier categories cascade through everything downstream.
This is the backend peer of the react-fetch-cache-patterns skill. React handles client-side waterfalls and caching; this skill handles server-side fan-out, downstream protection, OpenSearch query design, and ML-blend orchestration.
When to Apply
- Building or reviewing Django views that fan out to AWS Personalize, Databricks Model Serving, internal microservices, or any ML inference downstream
- Designing OpenSearch query endpoints (search results, infinite feeds, faceted search)
- Implementing a recommendations endpoint that blends multiple ranker outputs
- Investigating "Django backend slow when downstream is degraded" or "Personalize quota exhausted"
- Adding caching, retry, circuit breakers, or rate limiting to outbound calls
- Choosing between sync and async Django views, configuring uvicorn vs gunicorn
- Designing DRF response shapes for paginated feeds, partial results, or degraded paths
Rule Categories by Priority
| # | Category | Impact | Prefix | Rules |
|---|---|---|---|---|
| 1 | Fan-out Orchestration | CRITICAL | orch- |
8 |
| 2 | External Service Protection | CRITICAL | protect- |
7 |
| 3 | OpenSearch Query Patterns | CRITICAL | search- |
8 |
| 4 | Result Blending & Personalization | HIGH | blend- |
5 |
| 5 | Caching Strategy | HIGH | cache- |
5 |
| 6 | Resilience & Partial Results | HIGH | resilience- |
5 |
| 7 | Async & Concurrency | MEDIUM-HIGH | async- |
5 |
| 8 | API Response Design | MEDIUM | api- |
5 |
Quick Reference
1. Fan-out Orchestration (CRITICAL)
orch-parallel-fanout-asyncio-gather— Useasyncio.gatherfor independent downstream calls; never await sequentiallyorch-return-exceptions-on-fanout—return_exceptions=Trueso one failure doesn't poison the whole gatherorch-propagate-request-deadline— Pass a deadline through every downstream call to bound whole-request latencyorch-reuse-async-clients— Onehttpx.AsyncClientper downstream at module scope; never per-requestorch-bounded-fanout-concurrency— Cap per-request fan-out withasyncio.Semaphoreto protect the poolorch-no-blocking-in-async-view— Never block the event loop with sync ORM/IO in async viewsorch-avoid-await-in-loop—for item in items: await ...is serial; useasyncio.gatherwith comprehensionorch-batch-with-bulk-endpoint— Bulk endpoint over N parallel calls; DataLoader pattern for batchers
2. External Service Protection (CRITICAL)
protect-per-downstream-timeout-budget— Different timeouts per service matched to each downstream's p99protect-circuit-breaker-per-downstream— One breaker per downstream so failures stay isolatedprotect-jittered-retry-backoff— Full-jitter exponential backoff to prevent thundering-herd recoveryprotect-no-retry-on-4xx— Skip retry on 4xx and non-idempotent failures; distinguish connect vs read errorsprotect-bulkhead-connection-pool— One connection pool per downstream so one slow service doesn't starve othersprotect-client-side-rate-limit— Token bucket toward each downstream to stay under their quotaprotect-honor-retry-after-header— ParseRetry-After(seconds or HTTP-date) on 429/503
3. OpenSearch Query Patterns (CRITICAL)
search-use-search-after-not-from—search_aftercursor instead offrom/sizefor any paginated endpointsearch-filter-source-fields— Restrict_sourceto fields you render; usedocvalue_fieldsfor sortablesearch-bool-filter-vs-must— Non-scoring clauses infilter(cacheable), scoring clauses inmustsearch-function-score-for-blending— Usefunction_scoreto blend personalization signals in-enginesearch-stable-tiebreaker-sort— Always end sort with_id(or unique numeric field) for stable cursorssearch-alias-for-blue-green-reindex— Query through aliases; never direct index namessearch-enable-request-cache—request_cache=truefor hit-returning queries; canonicalize request bodysearch-shard-aware-routing— Use routing keys to limit per-query shard fan-out
4. Result Blending & Personalization (HIGH)
blend-normalize-scores-across-sources— Min-max or RRF normalize before blending Personalize/Databricks/OpenSearchblend-mmr-for-diversity— Maximal Marginal Relevance to avoid monocultures in top-Kblend-dedup-across-sources— Canonical item ID dedup; bonus for cross-source corroborationblend-cold-start-fallback— Popular/editorial fallback for new users; tiered personalizationblend-anonymous-vs-personalized-paths— Cheap segment-keyed cache for anon traffic; ML only for logged-in
5. Caching Strategy (HIGH)
cache-redis-with-stampede-protection—SETNXlock + jittered TTL + probabilistic early refreshcache-version-on-model-deploy— Bake model version into cache keys; no flush needed on retraincache-segment-keyed-isolation— Include auth/role/locale/segment in keys to prevent cross-context leakagecache-two-tier-process-and-redis— Process LRU in front of Redis for the hottest keyscache-negative-results— Cache absences and empty results with shorter TTL
6. Resilience & Partial Results (HIGH)
resilience-partial-response-envelope—partial: true+sources_used+failed_sourcesin responseresilience-serve-stale-from-redis— Two TTLs (fresh + stale); serve stale on origin failureresilience-default-ranking-fallback— Precomputed default ranking when all ML sources are downresilience-per-source-observability— Tag every downstream call with structured source/outcome metadataresilience-degrade-search-gracefully— Tier 1 → tier 2 → tier 3 fallback for OpenSearch outages
7. Async & Concurrency (MEDIUM-HIGH)
async-sync-to-async-orm— Use Django 4.1+ async ORM (aget,afilter) orsync_to_asyncwiththread_sensitive=Trueasync-worker-model-uvicorn-vs-gunicorn— Run ASGI (uvicorn or gunicorn+UvicornWorker) for true async concurrencyasync-fire-and-forget-with-create-task—create_taskfor analytics/audit; add error handler; hold task referencesasync-context-vars-for-request-scope—contextvars.ContextVarfor per-request state; notthreading.localasync-cancel-on-client-disconnect— Checkawait request.is_disconnected(); propagate cancellation
8. API Response Design (MEDIUM)
api-cursor-pagination-in-drf— Cursor pagination over page-number; opaque base64 cursorsapi-serializer-perf-select-related—select_related/prefetch_related/onlyto eliminate N+1api-etag-and-cache-control-headers—ETag+Cache-Control+Varyfor CDN/client reuseapi-compression-and-payload-shaping— gzip/brotli; sparse fieldsets; msgpack for internal APIsapi-throttle-per-user-and-endpoint— DRF throttle classes per user/anon and per expensive endpoint
How to Use
- Open references/_sections.md for category definitions and impact rationale
- Read individual rule files for incorrect-vs-correct code examples (each ~150-300 lines with Python code)
- For ready-to-use scaffolds, see scaffolding templates
- The AGENTS.md navigation document (auto-generated) provides a TOC for browsing
Scaffolding Templates
Five ready-to-adapt Python templates under assets/templates/:
| Template | Purpose |
|---|---|
fanout_recommender_service.py.template |
Async fan-out client to Personalize/Databricks/microservice with per-downstream circuit breaker, bounded timeout, partial-result return |
opensearch_search_view.py.template |
DRF view + OpenSearch search_after cursor + function_score blending + _source filtering |
result_blender.py.template |
Score normalization + MMR diversity + canonical-ID dedup + cold-start fallback |
redis_cache_with_stampede.py.template |
Stampede-safe cached function decorator with SETNX lock and jittered TTL |
degraded_response.py.template |
Partial-results envelope with per-source status flags + tier-based fallback |
Reference Files
| File | Description |
|---|---|
| references/_sections.md | Category definitions, ordering, impact rationale, tier definitions |
| assets/templates/_template.md | Template for authoring new rules |
| metadata.json | Version, references, abstract |
Related Skills
react-fetch-cache-patterns— Client-side peer covering React data fetching/caching (Suspense, query libraries, prefetch)io-bound-data-processing— Python async patterns for batch and pipeline workloadsinngest-nextjs-patterns— Workflow patterns for server-side step functions