413 lines
10 KiB
Markdown
413 lines
10 KiB
Markdown
|
|
# CDC Event Flow: Customer-Specific vs Global Cache
|
||
|
|
|
||
|
|
## 🎯 The Key Misunderstanding
|
||
|
|
|
||
|
|
### What CDC Events Actually Contain
|
||
|
|
|
||
|
|
```json
|
||
|
|
// CDC Event from Salesforce
|
||
|
|
{
|
||
|
|
"payload": {
|
||
|
|
"Id": "01t5g000002AbcdEAC", // Product ID
|
||
|
|
"Name": "Internet Home 1G", // Product Name
|
||
|
|
"changeType": "UPDATE",
|
||
|
|
"changedFields": ["Name", "UnitPrice"],
|
||
|
|
"entityName": "Product2"
|
||
|
|
},
|
||
|
|
"replayId": 12345
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Notice:**
|
||
|
|
- ✅ Contains: Product ID, what changed
|
||
|
|
- ❌ Does NOT contain: Customer ID, User ID, Account ID
|
||
|
|
- ❌ Does NOT specify: "For Customer A" or "For Customer B"
|
||
|
|
|
||
|
|
**CDC events are GLOBAL notifications, not customer-specific!**
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔄 Complete Flow: What Happens With CDC
|
||
|
|
|
||
|
|
### Scenario: Price Change for "Internet Home 1G"
|
||
|
|
|
||
|
|
```
|
||
|
|
TIME: 10:00 AM
|
||
|
|
SALESFORCE: Admin changes price $50 → $60
|
||
|
|
↓
|
||
|
|
↓ (1 CDC Event sent)
|
||
|
|
↓
|
||
|
|
PORTAL CDC SUBSCRIBER receives event:
|
||
|
|
{
|
||
|
|
"Id": "01t123...",
|
||
|
|
"changeType": "UPDATE",
|
||
|
|
"changedFields": ["UnitPrice"]
|
||
|
|
}
|
||
|
|
↓
|
||
|
|
CACHE INVALIDATION (Global):
|
||
|
|
Redis: DELETE "catalog:internet:plans"
|
||
|
|
↓
|
||
|
|
Cache key deleted from Redis
|
||
|
|
No cache exists anymore for ANYONE
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 👥 What Happens to Different Customer Types?
|
||
|
|
|
||
|
|
### Customer A: Online & Active (viewing website)
|
||
|
|
|
||
|
|
```
|
||
|
|
10:00:00 AM - Viewing catalog page
|
||
|
|
↓ Cache hit (old price $50)
|
||
|
|
|
||
|
|
10:00:05 AM - CDC event received
|
||
|
|
↓ Cache deleted
|
||
|
|
|
||
|
|
10:00:10 AM - Refreshes page
|
||
|
|
↓ Cache miss (key deleted)
|
||
|
|
↓ API call to Salesforce
|
||
|
|
↓ Fetches new data (price $60)
|
||
|
|
↓ Stores in cache with 24h TTL
|
||
|
|
↓ Shows new price $60 ✅
|
||
|
|
```
|
||
|
|
|
||
|
|
**Action taken:** Cache miss → API call → Fresh data
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Customer B: Online & Idle (logged in but not viewing catalog)
|
||
|
|
|
||
|
|
```
|
||
|
|
10:00:00 AM - Logged in, viewing dashboard
|
||
|
|
(Not looking at catalog)
|
||
|
|
|
||
|
|
10:00:05 AM - CDC event received
|
||
|
|
↓ Cache deleted (global)
|
||
|
|
|
||
|
|
10:30:00 AM - Clicks "View Plans" for first time
|
||
|
|
↓ Cache miss (was deleted at 10:00)
|
||
|
|
↓ API call to Salesforce
|
||
|
|
↓ Fetches new data (price $60)
|
||
|
|
↓ Stores in cache
|
||
|
|
↓ Shows new price $60 ✅
|
||
|
|
```
|
||
|
|
|
||
|
|
**Action taken:** Cache miss → API call → Fresh data
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Customer C: Offline (not logged in for 7 days)
|
||
|
|
|
||
|
|
```
|
||
|
|
Day 1 - 9:00 AM - Customer C logs in
|
||
|
|
↓ Cache miss
|
||
|
|
↓ API call (fetches old price $50)
|
||
|
|
↓ Cache populated
|
||
|
|
|
||
|
|
Day 1 - 10:00 AM - CDC event (price changed to $60)
|
||
|
|
↓ Cache deleted
|
||
|
|
↓ Customer C logs out
|
||
|
|
|
||
|
|
Day 2-7: - Customer C offline
|
||
|
|
- Cache doesn't exist (deleted on Day 1)
|
||
|
|
- No action needed ✅
|
||
|
|
|
||
|
|
Day 8 - 8:00 AM - Customer C logs back in
|
||
|
|
↓ Clicks "View Plans"
|
||
|
|
↓ Cache miss (doesn't exist)
|
||
|
|
↓ API call to Salesforce
|
||
|
|
↓ Fetches new data (price $60)
|
||
|
|
↓ Shows new price $60 ✅
|
||
|
|
```
|
||
|
|
|
||
|
|
**Action taken:** Nothing during offline period. Fresh fetch on login.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎯 Key Point: ONE Cache Key for ALL Customers
|
||
|
|
|
||
|
|
Your catalog cache structure:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// GLOBAL cache keys (shared by ALL customers)
|
||
|
|
"catalog:internet:plans" // ← All customers use this
|
||
|
|
"catalog:sim:plans" // ← All customers use this
|
||
|
|
"catalog:vpn:plans" // ← All customers use this
|
||
|
|
|
||
|
|
// USER-SPECIFIC cache keys (per customer)
|
||
|
|
"catalog:eligibility:801xxx" // ← Customer A's eligibility
|
||
|
|
"catalog:eligibility:802xxx" // ← Customer B's eligibility
|
||
|
|
```
|
||
|
|
|
||
|
|
When Product2 CDC event arrives:
|
||
|
|
```typescript
|
||
|
|
// Invalidates GLOBAL keys (affects everyone)
|
||
|
|
await cache.delPattern("catalog:internet:*");
|
||
|
|
await cache.delPattern("catalog:sim:*");
|
||
|
|
await cache.delPattern("catalog:vpn:*");
|
||
|
|
|
||
|
|
// Does NOT invalidate user-specific keys
|
||
|
|
// "catalog:eligibility:801xxx" stays intact
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 💡 Why This Works Perfectly
|
||
|
|
|
||
|
|
### 1. Offline Customers Don't Waste Resources ✅
|
||
|
|
|
||
|
|
```
|
||
|
|
CDC event arrives → Cache deleted
|
||
|
|
↓
|
||
|
|
Offline customers:
|
||
|
|
- Not requesting data (they're offline)
|
||
|
|
- Not using API calls (they're offline)
|
||
|
|
- Not consuming memory (cache deleted)
|
||
|
|
↓
|
||
|
|
Result: ZERO resources wasted ✅
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Online Customers Get Fresh Data ✅
|
||
|
|
|
||
|
|
```
|
||
|
|
CDC event arrives → Cache deleted
|
||
|
|
↓
|
||
|
|
Next request (from ANY online customer):
|
||
|
|
- Cache miss
|
||
|
|
- 1 API call to Salesforce
|
||
|
|
- Fresh data stored in cache
|
||
|
|
↓
|
||
|
|
Subsequent requests (from ALL online customers):
|
||
|
|
- Cache hit
|
||
|
|
- 0 API calls
|
||
|
|
↓
|
||
|
|
Result: Fresh data shared by everyone ✅
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. Memory Stays Lean ✅
|
||
|
|
|
||
|
|
```
|
||
|
|
Before CDC:
|
||
|
|
Redis: "catalog:internet:plans" = [old data]
|
||
|
|
Memory: ~500KB
|
||
|
|
|
||
|
|
CDC event arrives:
|
||
|
|
Redis: DELETE "catalog:internet:plans"
|
||
|
|
Memory: 0 KB ✅
|
||
|
|
|
||
|
|
Next customer request:
|
||
|
|
Redis: "catalog:internet:plans" = [fresh data]
|
||
|
|
Memory: ~500KB (with 24h TTL)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔄 Complete Example: 100 Customers
|
||
|
|
|
||
|
|
```
|
||
|
|
SETUP:
|
||
|
|
- 100 total customers
|
||
|
|
- 50 online & active (viewing website)
|
||
|
|
- 30 online & idle (logged in, not viewing catalog)
|
||
|
|
- 20 offline (not logged in)
|
||
|
|
|
||
|
|
TIME: 10:00 AM - Product price changes in Salesforce
|
||
|
|
↓
|
||
|
|
ONE CDC event sent (not 100 events!)
|
||
|
|
↓
|
||
|
|
Portal receives event
|
||
|
|
↓
|
||
|
|
DELETE "catalog:internet:plans" (one global key)
|
||
|
|
↓
|
||
|
|
Cache no longer exists for ANYONE
|
||
|
|
|
||
|
|
TIME: 10:01 AM - Customer #37 (online, active) refreshes page
|
||
|
|
↓
|
||
|
|
Cache miss (key deleted)
|
||
|
|
↓
|
||
|
|
1 API call to Salesforce
|
||
|
|
↓
|
||
|
|
Fetches fresh data (new price)
|
||
|
|
↓
|
||
|
|
Stores in Redis with 24h TTL
|
||
|
|
↓
|
||
|
|
Key: "catalog:internet:plans" = [fresh data]
|
||
|
|
|
||
|
|
TIME: 10:02 AM - Customer #42 (online, active) refreshes page
|
||
|
|
↓
|
||
|
|
Cache HIT (Customer #37 populated it)
|
||
|
|
↓
|
||
|
|
0 API calls ✅
|
||
|
|
↓
|
||
|
|
Shows fresh data
|
||
|
|
|
||
|
|
TIME: 10:03 AM - Customers #1-20 (online, active) view catalog
|
||
|
|
↓
|
||
|
|
All cache HITs
|
||
|
|
↓
|
||
|
|
0 API calls ✅
|
||
|
|
|
||
|
|
TIME: 10:30 AM - Customer #55 (was idle, now viewing catalog)
|
||
|
|
↓
|
||
|
|
Cache HIT (still fresh from 10:01 AM)
|
||
|
|
↓
|
||
|
|
0 API calls ✅
|
||
|
|
|
||
|
|
OFFLINE CUSTOMERS (#81-100):
|
||
|
|
↓
|
||
|
|
Not requesting anything (offline)
|
||
|
|
↓
|
||
|
|
0 API calls ✅
|
||
|
|
↓
|
||
|
|
When they log in later:
|
||
|
|
- Cache might exist (if populated by others)
|
||
|
|
- OR Cache might be expired (24h TTL)
|
||
|
|
- Either way: Fresh data
|
||
|
|
|
||
|
|
RESULT:
|
||
|
|
CDC event: 1 event for 100 customers
|
||
|
|
API calls: 1 call (Customer #37)
|
||
|
|
Cache hits: 99 other customers shared the result
|
||
|
|
Offline customers: 0 impact, 0 waste
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎯 Direct Answers to Your Questions
|
||
|
|
|
||
|
|
### Q1: "We received CDC for a customer that's offline, what do we do?"
|
||
|
|
|
||
|
|
**Answer:** CDC is NOT "for a customer" - it's a GLOBAL notification!
|
||
|
|
|
||
|
|
```
|
||
|
|
CDC Event: "Product X changed"
|
||
|
|
↓
|
||
|
|
Action: Delete global cache key
|
||
|
|
↓
|
||
|
|
Offline customer: Does nothing (not requesting data)
|
||
|
|
↓
|
||
|
|
When they login later: Fetches fresh data
|
||
|
|
```
|
||
|
|
|
||
|
|
### Q2: "What do we do for existing customer?"
|
||
|
|
|
||
|
|
**Answer:** Same action - delete global cache!
|
||
|
|
|
||
|
|
```
|
||
|
|
CDC Event: "Product X changed"
|
||
|
|
↓
|
||
|
|
Action: Delete global cache key
|
||
|
|
↓
|
||
|
|
Online customer: Next request is cache miss
|
||
|
|
↓
|
||
|
|
Fetches fresh data from Salesforce
|
||
|
|
↓
|
||
|
|
Stores in cache for everyone
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔍 Only USER-SPECIFIC Data Has Per-Customer Logic
|
||
|
|
|
||
|
|
### Global Cache (CDC invalidates for everyone):
|
||
|
|
```typescript
|
||
|
|
// Products - same for all customers
|
||
|
|
"catalog:internet:plans"
|
||
|
|
|
||
|
|
// Prices - same for all customers
|
||
|
|
"catalog:sim:plans"
|
||
|
|
|
||
|
|
// Addons - same for all customers
|
||
|
|
"catalog:vpn:plans"
|
||
|
|
```
|
||
|
|
|
||
|
|
### User-Specific Cache (CDC invalidates per customer):
|
||
|
|
```typescript
|
||
|
|
// Eligibility - different per customer
|
||
|
|
"catalog:eligibility:801xxx" ← Customer A
|
||
|
|
"catalog:eligibility:802xxx" ← Customer B
|
||
|
|
|
||
|
|
// Orders - different per customer
|
||
|
|
"orders:account:801xxx" ← Customer A's orders
|
||
|
|
"orders:account:802xxx" ← Customer B's orders
|
||
|
|
```
|
||
|
|
|
||
|
|
**Account eligibility CDC:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"payload": {
|
||
|
|
"AccountId": "801xxx", // ← Specific customer!
|
||
|
|
"Internet_Eligibility__c": "Home 10G"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Action:**
|
||
|
|
```typescript
|
||
|
|
// Only invalidate THAT customer's eligibility
|
||
|
|
await cache.del("catalog:eligibility:801xxx");
|
||
|
|
|
||
|
|
// Other customers' eligibility stays cached ✅
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📊 Summary Table
|
||
|
|
|
||
|
|
| Cache Type | CDC Event | Offline Customer | Online Customer |
|
||
|
|
|------------|-----------|------------------|-----------------|
|
||
|
|
| **Global Catalog** | Product2 changed | Delete global cache. Customer offline, no impact. When logs in: fresh fetch | Delete global cache. Next request: cache miss, fetch fresh |
|
||
|
|
| **User Eligibility** | Account X changed | Delete cache for Customer X only. Other customers unaffected | Delete cache for Customer X only. Next request: fresh fetch |
|
||
|
|
| **Orders** | Order X changed | Delete cache for Order X & Account. Customer offline, no impact | Delete cache for Order X & Account. Next request: fresh fetch |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎓 The Elegance of This Design
|
||
|
|
|
||
|
|
**Why it works:**
|
||
|
|
|
||
|
|
1. **CDC is a notification system**, not a data distribution system
|
||
|
|
2. **Cache is deleted, not updated** → Zero stale data
|
||
|
|
3. **Global cache shared by all** → Maximum efficiency
|
||
|
|
4. **Lazy loading** → Only fetch when actually requested
|
||
|
|
5. **Offline users invisible** → No special handling needed
|
||
|
|
|
||
|
|
**Result:**
|
||
|
|
- ✅ Simple logic (no tracking of online/offline)
|
||
|
|
- ✅ Correct behavior (always fresh data)
|
||
|
|
- ✅ Efficient (minimal API calls)
|
||
|
|
- ✅ Memory efficient (deleted cache = 0 bytes)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🚀 Conclusion
|
||
|
|
|
||
|
|
**When CDC arrives:**
|
||
|
|
1. Delete the global cache key
|
||
|
|
2. Done. That's it.
|
||
|
|
|
||
|
|
**Offline customers:**
|
||
|
|
- Not requesting data → No impact
|
||
|
|
- No API calls → No cost
|
||
|
|
- No memory used → Efficient
|
||
|
|
|
||
|
|
**Online customers:**
|
||
|
|
- Next request → Cache miss
|
||
|
|
- 1 API call → Fresh data
|
||
|
|
- Other customers → Cache hit
|
||
|
|
|
||
|
|
**You don't need to:**
|
||
|
|
- ❌ Track who's online/offline
|
||
|
|
- ❌ Check customer status
|
||
|
|
- ❌ Store data per customer (for global catalog)
|
||
|
|
- ❌ Do anything special
|
||
|
|
|
||
|
|
**Just:**
|
||
|
|
- ✅ Delete cache when CDC arrives
|
||
|
|
- ✅ Let customers fetch on-demand
|
||
|
|
- ✅ Share cached results globally
|
||
|
|
|
||
|
|
Simple, correct, efficient! 🎉
|
||
|
|
|