|
|
@@ -1,292 +0,0 @@
|
|
|
-# Redis Cache Development Checklist
|
|
|
-
|
|
|
-Use this checklist when reading from or writing to Redis cache keys for videos, categories, and tags.
|
|
|
-
|
|
|
-## ✅ Before Writing to Cache
|
|
|
-
|
|
|
-### Understand Your Key Type
|
|
|
-
|
|
|
-- [ ] I've identified which key I'm writing to
|
|
|
-- [ ] I've read the JSDoc in `cache-keys.ts` for this key
|
|
|
-- [ ] I understand the Redis type (LIST, ZSET, STRING)
|
|
|
-- [ ] I know what elements should go in this key
|
|
|
-
|
|
|
-### Video ID Keys (Video Lists & Pools)
|
|
|
-
|
|
|
-- [ ] **My key contains VIDEO IDs only** (strings like `"64a2b3c4..."`)
|
|
|
-- [ ] **NOT JSON objects** with title, description, etc.
|
|
|
-- [ ] **NOT Tag data**
|
|
|
-- [ ] I'm using `rpushList()` for LIST keys (atomic DEL + RPUSH)
|
|
|
-- [ ] I'm using `zadd()` for ZSET keys (atomic DEL + ZADD)
|
|
|
-- [ ] Verify: Example key is `box:app:video:*` (not `box:app:tag:list:*`)
|
|
|
-
|
|
|
-### Tag JSON Keys
|
|
|
-
|
|
|
-- [ ] **My key contains TAG JSON objects** (stringified with id, name, seq, etc.)
|
|
|
-- [ ] **NOT video IDs**
|
|
|
-- [ ] Each element is valid JSON or full array for STRING type
|
|
|
-- [ ] I'm parsing each element if it's a LIST
|
|
|
-- [ ] For `box:app:tag:all`, I'm storing the full JSON array as one STRING
|
|
|
-
|
|
|
-### Atomic Operations
|
|
|
-
|
|
|
-- [ ] I'm using pipeline for all writes: `DEL` then `RPUSH`/`ZADD`/`SET`
|
|
|
-- [ ] I'm NOT doing multiple separate Redis commands
|
|
|
-- [ ] Atomicity prevents readers from seeing partial data
|
|
|
-
|
|
|
-### Key Naming
|
|
|
-
|
|
|
-- [ ] My key uses the format from `cache-keys.ts` (not custom format)
|
|
|
-- [ ] I'm using `tsCacheKeys` provider for generation
|
|
|
-- [ ] I'm including all required parameters (channelId, categoryId, etc.)
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## ✅ Before Reading from Cache
|
|
|
-
|
|
|
-### Understand Your Key Type
|
|
|
-
|
|
|
-- [ ] I've identified which key I'm reading from
|
|
|
-- [ ] I've read the JSDoc in `cache-keys.ts` for this key
|
|
|
-- [ ] I understand the Redis type (LIST, ZSET, STRING)
|
|
|
-- [ ] I know what elements to expect
|
|
|
-
|
|
|
-### Video ID Keys (Video Lists & Pools)
|
|
|
-
|
|
|
-- [ ] **I expect STRING values** (video IDs, not JSON)
|
|
|
-- [ ] I'm using `lrange()` for LIST keys (returns `string[]`)
|
|
|
-- [ ] I'm using `zrevrange()` for ZSET keys (returns `string[]`)
|
|
|
-- [ ] I'm NOT parsing each element as JSON
|
|
|
-- [ ] I'm NOT expecting object properties (id, title, etc.)
|
|
|
-- [ ] Verify: Example key is `box:app:video:*` (not `box:app:tag:list:*`)
|
|
|
-
|
|
|
-### Tag JSON Keys
|
|
|
-
|
|
|
-- [ ] **I expect JSON content** to parse
|
|
|
-- [ ] For LIST keys (`box:app:tag:list:*`), each element is JSON
|
|
|
-- [ ] For STRING keys (`box:app:tag:all`), the whole value is a JSON array
|
|
|
-- [ ] I'm calling `JSON.parse()` on the result
|
|
|
-- [ ] I'm extracting fields like `id`, `name`, `seq` from parsed objects
|
|
|
-
|
|
|
-### Pagination (ZSET Keys)
|
|
|
-
|
|
|
-- [ ] I'm using `ZREVRANGE` (reverse order = newest first)
|
|
|
-- [ ] Offset calculation: `(page - 1) * pageSize`
|
|
|
-- [ ] Limit calculation: `offset + pageSize - 1`
|
|
|
-- [ ] Example: Page 1, size 20 → ZREVRANGE key 0 19
|
|
|
-
|
|
|
-### Non-Pagination (LIST Keys)
|
|
|
-
|
|
|
-- [ ] I'm using `LRANGE` with `0 -1` to get all
|
|
|
-- [ ] Or specific range: `LRANGE key offset offset+limit-1`
|
|
|
-- [ ] Results are in insertion order, not sorted
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## ✅ Common Operations
|
|
|
-
|
|
|
-### Write Video IDs to Category List
|
|
|
-
|
|
|
-```typescript
|
|
|
-import { tsCacheKeys } from '@box/common/cache/ts-cache-key.provider';
|
|
|
-
|
|
|
-const key = tsCacheKeys.video.categoryList('channel-1');
|
|
|
-const videoIds = ['video-001', 'video-002', 'video-003'];
|
|
|
-
|
|
|
-// ✅ CORRECT: Use rpushList (atomic DEL + RPUSH)
|
|
|
-await redis.rpushList(key, videoIds);
|
|
|
-
|
|
|
-// ❌ WRONG: Direct push without clearing
|
|
|
-// await redis.rpush(key, ...videoIds);
|
|
|
-```
|
|
|
-
|
|
|
-### Read Video IDs from Category List
|
|
|
-
|
|
|
-```typescript
|
|
|
-const key = tsCacheKeys.video.categoryList('channel-1');
|
|
|
-
|
|
|
-// ✅ CORRECT: LRANGE returns string[]
|
|
|
-const videoIds = await redis.lrange(key, 0, -1);
|
|
|
-// Result: ['video-001', 'video-002', 'video-003']
|
|
|
-
|
|
|
-// ❌ WRONG: Parsing as JSON
|
|
|
-// const data = await redis.lrange(key, 0, -1);
|
|
|
-// const parsed = data.map(item => JSON.parse(item)); // ERROR!
|
|
|
-```
|
|
|
-
|
|
|
-### Write Video Pool (ZSET)
|
|
|
-
|
|
|
-```typescript
|
|
|
-const key = tsCacheKeys.video.categoryPool('channel-1', 'category-1', 'latest');
|
|
|
-const members = [
|
|
|
- { member: 'video-001', score: 1700000000 },
|
|
|
- { member: 'video-002', score: 1700000001 },
|
|
|
-];
|
|
|
-
|
|
|
-// ✅ CORRECT: Use zadd (atomic DEL + ZADD)
|
|
|
-await redis.zadd(key, members);
|
|
|
-```
|
|
|
-
|
|
|
-### Read Video Pool with Pagination
|
|
|
-
|
|
|
-```typescript
|
|
|
-const key = tsCacheKeys.video.categoryPool('channel-1', 'category-1', 'latest');
|
|
|
-const page = 1;
|
|
|
-const pageSize = 20;
|
|
|
-
|
|
|
-// ✅ CORRECT: ZREVRANGE for pagination
|
|
|
-const videoIds = await redis.zrevrange(
|
|
|
- key,
|
|
|
- (page - 1) * pageSize,
|
|
|
- page * pageSize - 1,
|
|
|
-);
|
|
|
-// Result: ['video-001', 'video-002', ...] (20 items, newest first)
|
|
|
-```
|
|
|
-
|
|
|
-### Write Tags (JSON List)
|
|
|
-
|
|
|
-```typescript
|
|
|
-const key = tsCacheKeys.video.tagList('channel-1', 'category-1');
|
|
|
-// Note: This key doesn't exist in current schema
|
|
|
-// For global tags, use tag.all()
|
|
|
-
|
|
|
-const tagKey = tsCacheKeys.tag.all();
|
|
|
-const tags = [
|
|
|
- { id: 'tag-1', name: 'Sports', seq: 1, status: 1, ... },
|
|
|
- { id: 'tag-2', name: 'Action', seq: 2, status: 1, ... }
|
|
|
-];
|
|
|
-
|
|
|
-// ✅ CORRECT: Use setJson for JSON array
|
|
|
-await redis.setJson(tagKey, tags);
|
|
|
-```
|
|
|
-
|
|
|
-### Read Tags (JSON)
|
|
|
-
|
|
|
-```typescript
|
|
|
-const tagKey = tsCacheKeys.tag.all();
|
|
|
-
|
|
|
-// ✅ CORRECT: GET returns full JSON, parse it
|
|
|
-const tagsJson = await redis.get(tagKey);
|
|
|
-const tags = JSON.parse(tagsJson);
|
|
|
-// Result: [{ id: 'tag-1', name: 'Sports', ... }, ...]
|
|
|
-```
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## ✅ Mistake Prevention
|
|
|
-
|
|
|
-### ❌ Mistake 1: Storing JSON in Video Lists
|
|
|
-
|
|
|
-```typescript
|
|
|
-// WRONG!
|
|
|
-const videos = [
|
|
|
- { id: 'vid-1', title: 'Video 1', description: 'Desc' },
|
|
|
- { id: 'vid-2', title: 'Video 2', description: 'Desc' },
|
|
|
-];
|
|
|
-await redis.rpush(key, ...videos.map((v) => JSON.stringify(v)));
|
|
|
-
|
|
|
-// CORRECT!
|
|
|
-const videoIds = videos.map((v) => v.id); // Extract IDs only
|
|
|
-await redis.rpushList(key, videoIds); // ['vid-1', 'vid-2']
|
|
|
-```
|
|
|
-
|
|
|
-### ❌ Mistake 2: Parsing Video ID Lists as JSON
|
|
|
-
|
|
|
-```typescript
|
|
|
-// WRONG!
|
|
|
-const items = await redis.lrange(key, 0, -1); // ['vid-1', 'vid-2', ...]
|
|
|
-const parsed = items.map((item) => JSON.parse(item)); // ERROR! They're not JSON
|
|
|
-
|
|
|
-// CORRECT!
|
|
|
-const videoIds = await redis.lrange(key, 0, -1); // Use as-is
|
|
|
-// Result: ['vid-1', 'vid-2', ...]
|
|
|
-```
|
|
|
-
|
|
|
-### ❌ Mistake 3: Confusing Tag Keys
|
|
|
-
|
|
|
-```typescript
|
|
|
-// WRONG!
|
|
|
-const tagVideoKey = tsCacheKeys.video.tagList('channel-1', 'category-1');
|
|
|
-const allTagsKey = tsCacheKeys.tag.all();
|
|
|
-
|
|
|
-// These are DIFFERENT keys with DIFFERENT data!
|
|
|
-const videoIds = await redis.lrange(tagVideoKey, 0, -1); // Returns video IDs
|
|
|
-const tagsJson = await redis.get(allTagsKey); // Returns JSON array of tags
|
|
|
-const tags = JSON.parse(tagsJson); // Now we have tag objects
|
|
|
-
|
|
|
-// CORRECT USAGE!
|
|
|
-// video.tagList() → video IDs (strings only)
|
|
|
-// tag.all() → Tag JSON objects
|
|
|
-```
|
|
|
-
|
|
|
-### ❌ Mistake 4: Non-Atomic Writes
|
|
|
-
|
|
|
-```typescript
|
|
|
-// WRONG! Not atomic - readers might see partial state
|
|
|
-await redis.del(key);
|
|
|
-await redis.rpush(key, ...items);
|
|
|
-
|
|
|
-// CORRECT! Atomic via pipeline
|
|
|
-await redis.rpushList(key, items); // Internally does DEL then RPUSH
|
|
|
-```
|
|
|
-
|
|
|
-### ❌ Mistake 5: Using Wrong Redis Operation
|
|
|
-
|
|
|
-```typescript
|
|
|
-// WRONG! Using SET for LIST key
|
|
|
-await redis.set(videoListKey, JSON.stringify(videoIds));
|
|
|
-
|
|
|
-// CORRECT! Use appropriate operation for type
|
|
|
-await redis.rpushList(videoListKey, videoIds); // For LIST type
|
|
|
-await redis.zadd(videoPoolKey, membersWithScores); // For ZSET type
|
|
|
-await redis.setJson(tagKey, tagObjects); // For STRING/JSON type
|
|
|
-```
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## ✅ Testing Checklist
|
|
|
-
|
|
|
-### Before Submitting Code
|
|
|
-
|
|
|
-- [ ] I've used `tsCacheKeys` for key generation (type-safe)
|
|
|
-- [ ] I understand the Redis type for my key
|
|
|
-- [ ] I'm using correct operations (LRANGE, ZREVRANGE, GET, etc.)
|
|
|
-- [ ] All writes use atomic pipelines (DEL + operation)
|
|
|
-- [ ] Video ID keys contain strings only, not JSON
|
|
|
-- [ ] Tag JSON keys are parsed correctly
|
|
|
-- [ ] No manual JSON stringification for video lists
|
|
|
-- [ ] Pagination offset calculations are correct
|
|
|
-
|
|
|
-### Code Review Questions
|
|
|
-
|
|
|
-1. **Is this a video ID key or JSON key?** → Check key pattern
|
|
|
-2. **What Redis type is this?** → Check cache-keys.ts JSDoc
|
|
|
-3. **Are we writing atomically?** → Check for rpushList, zadd, setJson
|
|
|
-4. **Are we parsing JSON when needed?** → Check for JSON.parse on tag keys
|
|
|
-5. **Are we using tsCacheKeys?** → Check for type safety
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## ✅ Quick Reference Links
|
|
|
-
|
|
|
-- **Complete Semantics**: `libs/common/src/cache/CACHE_SEMANTICS.md`
|
|
|
-- **Quick Lookup**: `libs/common/src/cache/CACHE_KEYS_QUICK_REF.md`
|
|
|
-- **Key Definitions**: `libs/common/src/cache/cache-keys.ts`
|
|
|
-- **Type-Safe API**: `libs/common/src/cache/ts-cache-key.provider.ts`
|
|
|
-- **Type Checking**: `libs/common/src/cache/cache-semantics.constants.ts`
|
|
|
-- **Documentation Index**: `libs/common/src/cache/README.md`
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## ✅ When Stuck
|
|
|
-
|
|
|
-1. **"What's the structure of this key?"** → Read CACHE_SEMANTICS.md
|
|
|
-2. **"How do I generate a key?"** → Use `tsCacheKeys` with IDE autocomplete
|
|
|
-3. **"What Redis command should I use?"** → Check CACHE_KEYS_QUICK_REF.md table
|
|
|
-4. **"Is this a common mistake?"** → Check CACHE_KEYS_QUICK_REF.md → "Common Mistakes"
|
|
|
-5. **"How do I paginate?"** → Copy code snippet from CACHE_KEYS_QUICK_REF.md
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-**Last Updated**: December 6, 2025
|
|
|
-**Version**: 1.0 (Documentation Phase)
|