feat: two-layer in-memory caching for backend (closes #12) #14

Merged
starkSV merged 1 commit from feat/caching-layer into main 2026-05-18 22:49:34 +05:30
starkSV commented 2026-05-18 22:49:18 +05:30 (Migrated from github.com)

Summary

Reduces Microsoft API traffic from thousands/day to ~50–100/day, eliminating the 715-123130 rate-limit block under real traffic.

What's implemented

# Sub-task Detail
1 Singleflight golang.org/x/sync/singleflight wraps all MS fetches — 500 concurrent cache misses → 1 API hit
2 SKU info cache 7-day TTL keyed by product_id — language lists are stable
3 Download link cache Dynamic TTL keyed by product_id:sku_id
4 Negative cache 60s TTL on 429/715-123130 — stops retry storms from worsening a block
5 Dynamic TTL Parses se (signed expiry) from CDN URL: (se - now) - 30min, falls back to 22h
6 Stale-on-failure Serves expired cache entry if refresh fails — no global outage on transient rate-limit
7 Jitter ±5min random offset on all TTLs — prevents synchronised mass-expiry spikes

Verified locally

2026/05/18 22:45:50 eval warm: server-2025 -> 8 links cached
2026/05/18 22:45:50 MS fetch /skuinfo: product_id=3113 -> 38 languages
2026/05/18 22:45:50 /skuinfo: product_id=3113 -> fetched and cached
2026/05/18 22:45:50 /skuinfo: product_id=3113 -> cache hit
2026/05/18 22:45:50 /skuinfo: product_id=3113 -> cache hit
2026/05/18 22:45:52 /proxy: product_id=3113 sku_id=18480 -> fetched and cached until 2026-05-19T20:48:14
2026/05/18 22:45:52 /proxy: product_id=3113 sku_id=18480 -> cache hit
2026/05/18 22:46:07 /evallinks: product=server-2019 -> cache hit (8 links)

Test plan

  • Deploy to Coolify, watch logs for cache hit/miss lines under real traffic
  • Confirm cached until <timestamp> shows dynamic TTL (~22h from fetch time)
  • Confirm eval warm lines fire at startup
  • Monitor for absence of 715-123130 errors under load

Summary by CodeRabbit

Release Notes

  • New Features

    • Intelligent multi-layer caching reduces response times and server load
    • Fallback mechanism serves cached data during service disruptions
    • Improved handling of concurrent requests for better efficiency
    • Enhanced rate-limit detection and error recovery
  • Chores

    • Updated runtime version for improved stability and performance

Review Change Stack

## Summary Reduces Microsoft API traffic from thousands/day to ~50–100/day, eliminating the 715-123130 rate-limit block under real traffic. ## What's implemented | # | Sub-task | Detail | |---|---|---| | 1 | **Singleflight** | `golang.org/x/sync/singleflight` wraps all MS fetches — 500 concurrent cache misses → 1 API hit | | 2 | **SKU info cache** | 7-day TTL keyed by `product_id` — language lists are stable | | 3 | **Download link cache** | Dynamic TTL keyed by `product_id:sku_id` | | 4 | **Negative cache** | 60s TTL on 429/715-123130 — stops retry storms from worsening a block | | 5 | **Dynamic TTL** | Parses `se` (signed expiry) from CDN URL: `(se - now) - 30min`, falls back to 22h | | 6 | **Stale-on-failure** | Serves expired cache entry if refresh fails — no global outage on transient rate-limit | | 7 | **Jitter** | ±5min random offset on all TTLs — prevents synchronised mass-expiry spikes | ## Verified locally 2026/05/18 22:45:50 eval warm: server-2025 -> 8 links cached 2026/05/18 22:45:50 MS fetch /skuinfo: product_id=3113 -> 38 languages 2026/05/18 22:45:50 /skuinfo: product_id=3113 -> fetched and cached 2026/05/18 22:45:50 /skuinfo: product_id=3113 -> cache hit 2026/05/18 22:45:50 /skuinfo: product_id=3113 -> cache hit 2026/05/18 22:45:52 /proxy: product_id=3113 sku_id=18480 -> fetched and cached until 2026-05-19T20:48:14 2026/05/18 22:45:52 /proxy: product_id=3113 sku_id=18480 -> cache hit 2026/05/18 22:46:07 /evallinks: product=server-2019 -> cache hit (8 links) ## Test plan - [ ] Deploy to Coolify, watch logs for cache hit/miss lines under real traffic - [ ] Confirm `cached until <timestamp>` shows dynamic TTL (~22h from fetch time) - [ ] Confirm eval warm lines fire at startup - [ ] Monitor for absence of 715-123130 errors under load <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Intelligent multi-layer caching reduces response times and server load * Fallback mechanism serves cached data during service disruptions * Improved handling of concurrent requests for better efficiency * Enhanced rate-limit detection and error recovery * **Chores** * Updated runtime version for improved stability and performance <!-- review_stack_entry_start --> [![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/starkSV/windows-iso-downloader/pull/14?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
coderabbitai[bot] commented 2026-05-18 22:49:27 +05:30 (Migrated from github.com)

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 28a1604d-fdea-4d32-8296-30f3122fbe04

📥 Commits

Reviewing files that changed from the base of the PR and between ddf96a873d and c6d8615cf1.

Files ignored due to path filters (1)
  • backend/go.sum is excluded by !**/*.sum
📒 Files selected for processing (2)
  • backend/go.mod
  • backend/main.go

📝 Walkthrough

Walkthrough

Refactored Microsoft API interactions in backend/main.go to add multi-layer caching (SKU info, download links, negative cache, eval links), singleflight deduplication for concurrent requests, structured rate-limit error classification, jittered cache TTLs, and stale-on-failure serving. Dependency added: golang.org/x/sync for singleflight.

Changes

Microsoft Caching and Concurrency

Layer / File(s) Summary
Dependency and import setup
backend/go.mod, backend/main.go (imports)
Go version updated to 1.25.0; added golang.org/x/sync v0.20.0 dependency; imported math/rand and golang.org/x/sync/singleflight for jitter and request deduplication.
Cache and synchronization infrastructure
backend/main.go (cache globals, TTL constants)
Added SKU info cache (7-day TTL), download link cache (dynamic expiry), negative cache (60-second rate-limit protection), session cache, and global singleflight.Group for per-key deduplication of concurrent Microsoft fetches.
Core helper utilities and error types
backend/main.go (helpers, types, constants)
Introduced jitter() for cache TTL variance, parseLinkExpiry() to extract expiry from Microsoft signed-URL se parameter, rateLimitError type to classify rate-limit signals (Type 9 / code 715-123130), regex patterns for link/error extraction, and eval cache storage with TTL.
Session setup and Microsoft fetch implementations
backend/main.go (setupSession, fetchSKUInfo, fetchDownloadLinksFromMS, helpers)
Reworked setupSession() to handle fingerprint-trigger requests and JS extraction; refactored fetchSKUInfo() to return structured errors and raw bytes for caching; implemented fetchDownloadLinksFromMS() to map TooManyRequests and non-OK responses to rateLimitError; added worker-secret header setter; refactored eval-links extraction with deduplication and language-first sorting.
Endpoint handlers with caching strategies
backend/main.go (/skuinfo, /proxy, /evallinks handlers)
/skuinfo checks negative cache, then SKU cache, singleflights fetch, stores negative entries on rate-limit, caches success with jitter, returns raw JSON. /proxy checks link cache, conditionally checks negative cache (only if no stale), singleflights per-product fetch, serves stale on failure, caches with dynamic expiry. /evallinks serves cached results when fetch fails or yields zero links.
Routing and server setup
backend/main.go (main function)
Updated main() to explicitly register /skuinfo, /proxy, /evallinks, and /health handlers; warmup and session cleanup logic retained.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • starkSV/windows-iso-downloader#12: This PR directly implements the two-tier SKU + download-link caching, singleflight deduplication, dynamic TTL parsing from Microsoft signed URLs, stale-on-failure behavior, and negative-cache rate-limit protection described in the issue.
  • starkSV/windows-iso-downloader#7: Both PRs modify the /skuinfo and /proxy Microsoft-fetch paths in backend/main.go; #7 wraps requests via Cloudflare proxy/X-Worker-Secret header, while this PR adds caching, singleflight, and structured error handling around those same fetch flows.

Poem

🐰 The caches leap and singleflight calls,
No more shall Microsoft rate-limits frustrate halls,
With jitter and stale, when fetches all fail,
The Windows ISO downloader prevails!

Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/caching-layer

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Comment @coderabbitai help to get the list of available commands and usage tips.

<!-- This is an auto-generated comment: summarize by coderabbit.ai --> <!-- This is an auto-generated comment: failure by coderabbit.ai --> > [!CAUTION] > ## Review failed > > The pull request is closed. <!-- end of auto-generated comment: failure by coderabbit.ai --> <details> <summary>ℹ️ Recent review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: defaults **Review profile**: CHILL **Plan**: Pro Plus **Run ID**: `28a1604d-fdea-4d32-8296-30f3122fbe04` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between ddf96a873df3ce10e30495206b7b6a5400de0a9a and c6d8615cf18d21c1e8e1e460f0fe163e9319a58d. </details> <details> <summary>⛔ Files ignored due to path filters (1)</summary> * `backend/go.sum` is excluded by `!**/*.sum` </details> <details> <summary>📒 Files selected for processing (2)</summary> * `backend/go.mod` * `backend/main.go` </details> </details> --- <!-- walkthrough_start --> <details> <summary>📝 Walkthrough</summary> ## Walkthrough Refactored Microsoft API interactions in `backend/main.go` to add multi-layer caching (SKU info, download links, negative cache, eval links), singleflight deduplication for concurrent requests, structured rate-limit error classification, jittered cache TTLs, and stale-on-failure serving. Dependency added: `golang.org/x/sync` for singleflight. ## Changes **Microsoft Caching and Concurrency** |Layer / File(s)|Summary| |---|---| |**Dependency and import setup** <br> `backend/go.mod`, `backend/main.go` (imports)|Go version updated to 1.25.0; added `golang.org/x/sync v0.20.0` dependency; imported `math/rand` and `golang.org/x/sync/singleflight` for jitter and request deduplication.| |**Cache and synchronization infrastructure** <br> `backend/main.go` (cache globals, TTL constants)|Added SKU info cache (7-day TTL), download link cache (dynamic expiry), negative cache (60-second rate-limit protection), session cache, and global `singleflight.Group` for per-key deduplication of concurrent Microsoft fetches.| |**Core helper utilities and error types** <br> `backend/main.go` (helpers, types, constants)|Introduced `jitter()` for cache TTL variance, `parseLinkExpiry()` to extract expiry from Microsoft signed-URL `se` parameter, `rateLimitError` type to classify rate-limit signals (Type 9 / code `715-123130`), regex patterns for link/error extraction, and eval cache storage with TTL.| |**Session setup and Microsoft fetch implementations** <br> `backend/main.go` (setupSession, fetchSKUInfo, fetchDownloadLinksFromMS, helpers)|Reworked `setupSession()` to handle fingerprint-trigger requests and JS extraction; refactored `fetchSKUInfo()` to return structured errors and raw bytes for caching; implemented `fetchDownloadLinksFromMS()` to map TooManyRequests and non-OK responses to `rateLimitError`; added worker-secret header setter; refactored eval-links extraction with deduplication and language-first sorting.| |**Endpoint handlers with caching strategies** <br> `backend/main.go` (/skuinfo, /proxy, /evallinks handlers)|`/skuinfo` checks negative cache, then SKU cache, singleflights fetch, stores negative entries on rate-limit, caches success with jitter, returns raw JSON. `/proxy` checks link cache, conditionally checks negative cache (only if no stale), singleflights per-product fetch, serves stale on failure, caches with dynamic expiry. `/evallinks` serves cached results when fetch fails or yields zero links.| |**Routing and server setup** <br> `backend/main.go` (main function)|Updated `main()` to explicitly register `/skuinfo`, `/proxy`, `/evallinks`, and `/health` handlers; warmup and session cleanup logic retained.| ## Estimated code review effort 🎯 4 (Complex) | ⏱️ ~50 minutes ## Possibly related issues - [starkSV/windows-iso-downloader#12](https://github.com/starkSV/windows-iso-downloader/issues/12): This PR directly implements the two-tier SKU + download-link caching, singleflight deduplication, dynamic TTL parsing from Microsoft signed URLs, stale-on-failure behavior, and negative-cache rate-limit protection described in the issue. ## Possibly related PRs - [starkSV/windows-iso-downloader#7](https://github.com/starkSV/windows-iso-downloader/pull/7): Both PRs modify the `/skuinfo` and `/proxy` Microsoft-fetch paths in `backend/main.go`; `#7` wraps requests via Cloudflare proxy/X-Worker-Secret header, while this PR adds caching, singleflight, and structured error handling around those same fetch flows. ## Poem > 🐰 *The caches leap and singleflight calls,* > *No more shall Microsoft rate-limits frustrate halls,* > *With jitter and stale, when fetches all fail,* > *The Windows ISO downloader prevails!* ✨ </details> <!-- walkthrough_end --> <!-- finishing_touch_checkbox_start --> <details> <summary>✨ Finishing Touches</summary> <details> <summary>📝 Generate docstrings</summary> - [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> Create stacked PR - [ ] <!-- {"checkboxId": "3e1879ae-f29b-4d0d-8e06-d12b7ba33d98"} --> Commit on current branch </details> <details> <summary>🧪 Generate unit tests (beta)</summary> - [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Create PR with unit tests - [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Commit unit tests in branch `feat/caching-layer` </details> </details> <!-- finishing_touch_checkbox_end --> <!-- This is an auto-generated comment: all tool run failures by coderabbit.ai --> > [!WARNING] > There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. > > <details> > <summary>🔧 golangci-lint (2.12.2)</summary> > > level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies" > > > > > </details> <!-- end of auto-generated comment: all tool run failures by coderabbit.ai --> <!-- tips_start --> --- <sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub> <!-- tips_end --> <!-- internal state start --> <!-- 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= --> <!-- internal state end -->
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: shekhar/windows-iso-downloader#14
No description provided.