xiaohongshu
xiaohongshu
braiins-cache-strategist
Designs Redis caching strategies for Braiins API data, optimizing for data freshness vs. API rate limits and response latency.
Full skill instructions
Design Redis caching strategies optimized for Braiins Pool API data. This skill helps determine appropriate TTL values, cache key patterns, and invalidation strategies based on data volatility and API rate limits.
Based on data volatility and API rate limits from API.md:
| Data Type | Resource | TTL | Rationale |
|---|---|---|---|
| User Stats | /user/overview | 30s | Hashrate changes frequently; users expect near real-time |
| User Rewards | /user/rewards | 60s | Historical data; less volatile |
| Worker List | /workers | 30s | Worker status can change rapidly |
| Worker Detail | /workers/{id} | 60s | Detailed metrics update less often |
| Worker Hashrate | /workers/{id}/hashrate | 120s | Historical timeseries; stable data |
| Pool Stats | /pool/stats | 60s | Pool-wide aggregations; moderate change rate |
| Network Stats | /network/stats | 300s | Bitcoin network changes slowly (10 min blocks) |
Rate Limit Consideration: API.md specifies 1 request/30s for user endpoints. Cache TTLs are set to match or exceed this to prevent hitting rate limits.
braiins:{resource_type}:{scope}:{identifier}:{filter_hash}
Components:
# User overview (no identifier needed, scoped by auth token)
braiins:user:overview:{accountHash}
# User rewards with time range
braiins:user:rewards:{accountHash}:{paramsHash}
# Worker list (paginated, filtered)
braiins:workers:list:{accountHash}:p{page}:{filtersHash}
# Worker detail
braiins:workers:detail:{accountHash}:{workerId}
# Worker hashrate timeseries
braiins:workers:hashrate:{accountHash}:{workerId}:{paramsHash}
# Pool stats (global, no scope)
braiins:pool:stats
# Network stats (global, no scope)
braiins:network:stats
Never use raw API tokens or account IDs in cache keys:
import crypto from 'crypto';
function generateAccountHash(apiToken: string): string {
// Use first 8 chars of SHA256 hash
return crypto
.createHash('sha256')
.update(apiToken)
.digest('hex')
.substring(0, 8);
}
// Usage:
// Token: "sk_live_abc123..." -> Hash: "a7f2b9c1"
// Key: "braiins:user:overview:a7f2b9c1"
For requests with variable query parameters:
function generateFilterHash(params: Record<string, unknown>): string {
const sortedJson = JSON.stringify(
Object.keys(params)
.sort()
.reduce((acc, key) => {
acc[key] = params[key];
return acc;
}, {} as Record<string, unknown>)
);
return crypto
.createHash('sha256')
.update(sortedJson)
.digest('hex')
.substring(0, 8);
}
// Usage:
// { status: "active", sortBy: "hashrate" } -> "b3c4d5e6"
// Key: "braiins:workers:list:a7f2b9c1:p1:b3c4d5e6"
Rule 1: Never include raw user input directly in keys
// BAD: Direct user input
const key = `braiins:worker:${userInput.workerId}`;
// GOOD: Sanitize first
const sanitizedId = workerId.replace(/[^a-zA-Z0-9\-_]/g, '');
const key = `braiins:worker:${sanitizedId}`;
Rule 2: Maximum key length
const MAX_KEY_LENGTH = 200;
function createCacheKey(parts: string[]): string {
const key = parts.join(':');
if (key.length > MAX_KEY_LENGTH) {
// Hash the key if too long
const hash = crypto.createHash('sha256').update(key).digest('hex');
return `braiins:hashed:${hash}`;
}
return key;
}
Rule 3: Character allowlist
const VALID_KEY_CHARS = /^[a-zA-Z0-9:\-_]+$/;
function validateCacheKey(key: string): boolean {
return VALID_KEY_CHARS.test(key) && key.length <= MAX_KEY_LENGTH;
}
| Category | Characteristics | Base TTL |
|---|---|---|
| Real-time | Changes every second | 15-30s |
| Near real-time | Changes every minute | 30-60s |
| Historical | Changes hourly/daily | 120-300s |
| Static | Rarely changes | 3600s+ |
From API.md Section 9:
Rule: Cache TTL >= API rate limit interval
interface CacheKeyConfig {
resource: string;
scope: 'global' | 'account' | 'worker';
identifiers: string[];
hasFilters: boolean;
}
function designCacheKey(config: CacheKeyConfig): string {
const parts = ['braiins', config.resource];
if (config.scope === 'account') {
parts.push('{accountHash}');
}
parts.push(...config.identifiers);
if (config.hasFilters) {
parts.push('{filtersHash}');
}
return parts.join(':');
}
Create entry in cache configuration:
// src/cache/cacheConfig.ts
export const CACHE_CONFIG = {
userOverview: {
keyPattern: 'braiins:user:overview:{accountHash}',
ttl: 30,
description: 'User hashrate, rewards, worker counts',
invalidateOn: ['manual_refresh', 'payout_received'],
},
workerDetail: {
keyPattern: 'braiins:workers:detail:{accountHash}:{workerId}',
ttl: 60,
description: 'Individual worker status and metrics',
invalidateOn: ['worker_status_change', 'manual_refresh'],
},
poolStats: {
keyPattern: 'braiins:pool:stats',
ttl: 60,
description: 'Global pool statistics',
invalidateOn: ['new_block_found'],
},
} as const;
async function getWithCache<T>(
cacheKey: string,
ttl: number,
fetchFn: () => Promise<T>,
): Promise<T> {
// Try cache first
try {
const cached = await redisManager.get<T>(cacheKey);
if (cached !== null) {
logger.debug('Cache hit', { key: cacheKey });
return cached;
}
} catch (cacheError) {
logger.warn('Cache read failed, falling through to API', { error: cacheError });
}
// Cache miss - fetch from API
logger.debug('Cache miss', { key: cacheKey });
const data = await fetchFn();
// Store in cache (don't fail on cache errors)
try {
await redisManager.set(cacheKey, data, ttl);
} catch (cacheError) {
logger.warn('Cache write failed', { error: cacheError });
}
return data;
}
For near-real-time data where some staleness is acceptable:
interface CachedData<T> {
data: T;
cachedAt: number;
ttl: number;
}
async function getWithStaleWhileRevalidate<T>(
cacheKey: string,
ttl: number,
staleThreshold: number, // Additional seconds to serve stale data
fetchFn: () => Promise<T>,
): Promise<T> {
const cached = await redisManager.get<CachedData<T>>(cacheKey);
if (cached) {
const age = Date.now() - cached.cachedAt;
const isStale = age > cached.ttl * 1000;
const isTooStale = age > (cached.ttl + staleThreshold) * 1000;
if (!isStale) {
// Fresh data
return cached.data;
}
if (!isTooStale) {
// Stale but acceptable - return and revalidate in background
setImmediate(async () => {
try {
const fresh = await fetchFn();
await redisManager.set(cacheKey, {
data: fresh,
cachedAt: Date.now(),
ttl,
});
} catch (error) {
logger.warn('Background revalidation failed', { error });
}
});
return cached.data;
}
}
// No cache or too stale - must fetch
const data = await fetchFn();
await redisManager.set(cacheKey, {
data,
cachedAt: Date.now(),
ttl,
});
return data;
}
async function invalidateCache(pattern: string): Promise<number> {
// Get all keys matching pattern
const keys = await redisManager.keys(pattern);
if (keys.length === 0) {
return 0;
}
// Delete all matching keys
await redisManager.del(...keys);
logger.info('Cache invalidated', { pattern, count: keys.length });
return keys.length;
}
// Usage:
// Invalidate all worker data for an account
await invalidateCache('braiins:workers:*:a7f2b9c1:*');
// Invalidate specific worker
await invalidateCache('braiins:workers:*:a7f2b9c1:worker-123');
// Invalidate all pool stats
await invalidateCache('braiins:pool:*');
When designing cache strategy for a new endpoint:
Data Characteristics:
Cache Design:
// Cache configuration
const config = {
keyPattern: 'braiins:user:overview:{accountHash}',
ttl: 30, // 30 seconds (matches rate limit)
scope: 'account',
};
// Implementation
async function getUserOverview(accountHash: string): Promise<UserOverview> {
const cacheKey = `braiins:user:overview:${accountHash}`;
return getWithCache(
cacheKey,
30,
() => braiinsClient.getUserOverview(),
);
}
Data Characteristics:
Cache Design:
// Cache configuration
const config = {
keyPattern: 'braiins:workers:list:{accountHash}:p{page}:{filtersHash}',
ttl: 30,
scope: 'account',
};
// Implementation
async function listWorkers(
accountHash: string,
params: ListWorkersInput,
): Promise<WorkerList> {
const { page, pageSize, ...filters } = params;
const filtersHash = generateFilterHash(filters);
const cacheKey = `braiins:workers:list:${accountHash}:p${page}:${filtersHash}`;
return getWithCache(
cacheKey,
30,
() => braiinsClient.listWorkers(params),
);
}
Data Characteristics:
Cache Design:
// Cache configuration
const config = {
keyPattern: 'braiins:network:stats',
ttl: 300, // 5 minutes
scope: 'global',
};
// Implementation
async function getNetworkStats(): Promise<NetworkStats> {
const cacheKey = 'braiins:network:stats';
// Use stale-while-revalidate for better UX
return getWithStaleWhileRevalidate(
cacheKey,
300, // TTL: 5 minutes
60, // Serve stale for 1 more minute
() => braiinsClient.getNetworkStats(),
);
}
Pitfall 1: TTL shorter than rate limit
// BAD: Will hit rate limits
ttl: 15 // API rate limit is 30s
// GOOD: Respects rate limits
ttl: 30 // Matches API rate limit
Pitfall 2: Forgetting filter parameters in key
// BAD: Different filters return same cached data
const key = `braiins:workers:list:${accountHash}`;
// GOOD: Include filter state
const key = `braiins:workers:list:${accountHash}:${filtersHash}`;
Pitfall 3: Raw tokens in cache keys
// BAD: Security risk - token in key
const key = `braiins:user:${apiToken}`;
// GOOD: Hash the token
const key = `braiins:user:${hashToken(apiToken)}`;
Pitfall 4: Failing request on cache errors
// BAD: Cache error breaks the request
const cached = await redis.get(key); // Throws if Redis down
// GOOD: Fallthrough on cache errors
try {
const cached = await redis.get(key);
if (cached) return cached;
} catch {
// Continue to API call
}
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience