Jelajahi Sumber

feat: add Redis key inventory and spec documentation for ads and videos

Dave 1 bulan lalu
induk
melakukan
3825263d50

+ 1 - 0
.gitignore

@@ -67,3 +67,4 @@ src/tmp/*
 # scripts/start-db.sh
 # scripts/stop-db.sh
 docker/mongo/mongo-keyfile
+action-plans/20251222-ACT-01.md

+ 9 - 0
docs/ads-redis-key-inventory.md

@@ -0,0 +1,9 @@
+# Ads Redis Key Inventory
+
+| Key pattern | Writer(s) | Reader(s) | Value type | TTL | Notes |
+| --- | --- | --- | --- | --- | --- |
+| `box:app:adpool:{adType}` | `AdPoolService.rebuildPoolForType` (via `CacheSyncService.rebuildAdsCacheByType`/warm cache) | `readAdPoolEntriesWithLegacySupport` → used by `AdService.getAdForPlacement` and `HomepageService.getAdsByType` | JSON array of `AdPoolEntry` | none | Canonical pool keyed by `AdType`. Writers select `[id,adType,advertiser,title,adsContent,adsCoverImg,adsUrl,imgSource,startDt,expiryDt,seq]` and preserve `seq`-ascending order. Legacy module-keyed pool is rehydrated once and deleted after a warm rebuild.
+| `app:adpool:{adsModuleId}` | *None (legacy)* | `readAdPoolEntriesWithLegacySupport` (fallback path that looks up `AdsModule` by `adType`) | JSON array (legacy schema) | none | Transitional key used only when the new `box:app:adpool` pool is missing. The helper logs a warning, writes the normalized entries into the `adType` pool, and never falls back again.
+| `app:ad:by-id:{adId}` | `CacheSyncService.rebuildSingleAdCache` (triggered by `AdsService.scheduleAdRefresh`) | `AdService.fetchAdDetails` (placement), `HomepageService.fetchAdDetails` | JSON object (per-ad payload: `id,advertiser,title,adsContent,adsCoverImg,adsUrl,imgSource,adType,startDt,expiryDt,seq`) | ~300s (`CacheSyncService.AD_CACHE_TTL`) | Per-ad cache entry with short TTL to keep metadata fresh. Writers delete the key when the ad is inactive or missing.
+
+No Ads keys currently live without writers/readers except for the legacy `app:adpool:{adsModuleId}` fallback. The canonical `box:app:adpool:{adType}` should remain the single source of truth once caches rebuild and the legacy key is pruned.

+ 19 - 0
docs/ads-redis-key-spec.md

@@ -0,0 +1,19 @@
+# Ads Redis Key Spec (new adType schema)
+
+## Key naming
+- `box:app:adpool:{adType}` – JSON array scoped exclusively by the Prisma `AdType` enum (no `adsModuleId`).  
+- `app:ad:by-id:{adId}` – JSON object for a single ad, used by placement services and homepage caches.
+
+## Payload schema
+- All cached entries must consist of the following fields only:  
+  `id`, `adType`, `advertiser`, `title`, `adsContent`, `adsCoverImg`, `adsUrl`, `imgSource`, `startDt`, `expiryDt`, `seq`.
+- Records are included only when `status === 1` and the current epoch-second window satisfies `startDt <= now` and `(expiryDt === 0 || expiryDt >= now)`.
+- Ordering inside the pool is strictly ascending by `seq`; the array represents that ordering so readers can respect ad priority.
+- Timestamps (`startDt`, `expiryDt`) stay as BigInt epoch seconds.
+
+## Value type & TTL
+- `box:app:adpool:{adType}` is stored as a JSON string (array) and has no TTL; cache rebuilds overwrite and regenerate it via the ad-pool service.
+- `app:ad:by-id:{adId}` is stored as JSON (object) with `EX` of 300 seconds (set from the mgnt cache-sync/warmup services). Readers expect this short TTL to keep per-ad payloads fresh.
+
+## Migration notes
+- Writers must drop `adsModuleId` completely and rely on `adType` for both pool entries and per-ad caches. Legacy code should map module IDs to ad types only during migration, never store them in Redis.

+ 12 - 0
docs/video-redis-key-inventory.md

@@ -0,0 +1,12 @@
+# Video Redis Key Inventory
+
+| Key pattern | Writer(s) | Reader(s) | Value type | TTL | Notes |
+| --- | --- | --- | --- | --- | --- |
+| `box:app:video:category:list:{categoryId}` | `VideoCategoryCacheBuilder.buildCategoryVideoListForCategory` | `VideoService.getLatestVideosByCategory`, `VideoService.getVideosByCategoryListFallback`, `VideoService.getVideoList` (category-only flow), `searchVideosByTagName` fallback when tag list missing | LIST (video IDs) | none | Canonical list that stores only IDs. `VideoCacheHelper` ensures legacy `box:box:...` keys are handled.
+| `box:app:video:tag:list:{categoryId}:{tagId}` | `VideoCategoryCacheBuilder.buildTagFilteredVideoListForTag` | `VideoService.getVideoList` (tag filter), `VideoService.getVideosByTagListFallback`, `searchVideosByTagName` | LIST (video IDs) | none | Readers rely on the list ordering preserved by the builder.
+| `box:app:tag:list:{categoryId}` | `VideoCategoryCacheBuilder.buildTagMetadataListForCategory` | `VideoService.getTagListForCategory`, `VideoService.getCategoriesWithTags`, `VideoService.getVideoList` (tag lookup), `searchVideosByTagName`, admin diagnostics | LIST (Tag JSON strings) | none | Helper parses JSON into `TagMetadata`. Legacy double-prefixed keys are touched by `VideoCacheHelper` fallback and admin cleanup.
+| `box:app:video:recommended` | `RecommendedVideosCacheBuilder.buildAll` | `VideoService.getRecommendedVideos` | JSON array (`RecommendedVideoItem[]`) | 3600s | TTL enforced by builder; refreshed via cache sync.
+| `box:app:video:detail:{videoId}` | *None (ghost key)* | `VideoService.getVideoDetail` | JSON object (full VideoDetailDto) | unspecified | Readers still reference this key even though no builders populate it; treat as a legacy/ghost key and consider migrating to the payload cache spec.
+| `box:app:video:payload:{videoId}` | *(planned, follows spec)* | *(future readers)* | JSON object with only `{id,title,coverImg,coverImgNew,videoTime,country,firstTag,secondTags,preFileName,desc,size,updatedAt,filename,fieldNameFs,ext}` | TBD (align with writer) | New minimal payload key described in `docs/video-redis-key-spec.md`. Use `CacheKeys.appVideoPayloadKey` / `tsCacheKeys.video.payload` for generation.
+
+The inventory above complements the `VideoCacheHelper` fallback logic: all LIST readers now go through helpers that try the correct `app:` key first and rewrite any legacy `box:box:` entries encountered.

+ 19 - 0
docs/video-redis-key-spec.md

@@ -0,0 +1,19 @@
+# Video Redis Key Spec (new semantics)
+
+## Key naming & payload rules
+- `box:app:video:category:list:{categoryId}` – Redis **LIST** of video IDs scoped by category. Readers fetch IDs (via `VideoCacheHelper`) and hydrate details from MongoDB; writers are the category cache builder.
+- `box:app:video:tag:list:{categoryId}:{tagId}` – Redis **LIST** of video IDs filtered by tag membership. Writers are the tag-filtered builder, readers reuse the same helpers as category lists when `tagName` filters are requested.
+- `box:app:tag:list:{categoryId}` – Redis **LIST** of stringified Tag metadata objects (`{id,name,seq,status,categoryId,channelId,createAt,updateAt}`). Only the tag metadata builder writes here; readers rely on `VideoCacheHelper.getTagListForCategory` for typed access.
+- `box:app:video:recommended` – Redis **STRING** (JSON) storing an array of `RecommendedVideoItem` structures produced by `RecommendedVideosCacheBuilder` and consumed by `VideoService.getRecommendedVideos`.
+- `box:app:video:payload:{videoId}` – Redis **STRING** (JSON) intended to hold a **minimal VideoMedia payload** with exactly these fields: `id`, `title`, `coverImg`, `coverImgNew`, `videoTime`, `country`, `firstTag`, `secondTags`, `preFileName`, `desc`, `size`, `updatedAt`, `filename`, `fieldNameFs`, `ext`. `size` remains a BigInt serialized via `RedisService.setJson`. This key mirrors the detail cache but keeps the payload minimal, and caches should only include active videos sourced from the latest Mongo data.
+
+## Value types & TTL
+- Category/tag lists: Redis **LIST** of Mongo ObjectId strings. No TTL is set (lists persist until the next rebuild).
+- Tag metadata lists: Redis **LIST** of JSON strings; TTL is unset so the data lives until the next rebuild.
+- Recommended videos: JSON array stored via `RedisService.setJson` with a **1-hour TTL** (`RecommendedVideosCacheBuilder.CACHE_TTL`).
+- Payload key: JSON string stored via `RedisService.setJson`. TTL stays in sync with the builder that writes it (none by default, but can be added when the payload writer is implemented).
+
+## Migration & hygiene
+- All helpers that read video lists or tag metadata go through `VideoCacheHelper`, which attempts the canonical key (`app:...`) and falls back once to the legacy double-prefixed form (`box:app:...`) before logging a warning.
+- Admin/developer endpoints now only delete canonical patterns (`app:...`) and also sweep legacy `box:app:...` keys so old double-prefixed data can be removed after the new prefixes are safely draining.
+- New constants in `CacheKeys`/`tsCacheKeys` expose `appVideoPayloadKey(videoId)` so builders/services can stay aligned with the spec while Redis’ configured `box:` prefix is always applied exactly once.