KV Storage
KV là gì?
Global Key-Value Store
┌─────────────────────────────────────────────────────────────┐
│ CLOUDFLARE KV │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ KV Namespace │ │
│ │ │ │
│ │ key1 ──▶ value1 │ │
│ │ key2 ──▶ value2 │ │
│ │ key3 ──▶ value3 │ │
│ │ ... │ │
│ │ │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ Characteristics: │
│ - Eventually consistent (reads may be stale) │
│ - Optimized for read-heavy workloads │
│ - Global replication │
│ - Up to 25MB per value │
│ - TTL support │
│ │
└─────────────────────────────────────────────────────────────┘
Create KV Namespace
Via Wrangler
# Create namespace
wrangler kv:namespace create MY_KV
# Output:
# Add the following to your wrangler.toml:
# [[kv_namespaces]]
# binding = "MY_KV"
# id = "xxxx"
# Create preview namespace (for dev)
wrangler kv:namespace create MY_KV --preview
# List namespaces
wrangler kv:namespace list
Configuration
# wrangler.toml
[[kv_namespaces]]
binding = "MY_KV"
id = "production-namespace-id"
preview_id = "preview-namespace-id" # For wrangler dev
# Multiple namespaces
[[kv_namespaces]]
binding = "CACHE"
id = "cache-namespace-id"
[[kv_namespaces]]
binding = "SESSIONS"
id = "sessions-namespace-id"
Basic Operations
Get & Put
interface Env {
MY_KV: KVNamespace;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// PUT - Store value
await env.MY_KV.put('user:123', JSON.stringify({
name: 'John',
email: 'john@example.com',
}));
// GET - Retrieve value
const value = await env.MY_KV.get('user:123');
console.log(value); // '{"name":"John","email":"john@example.com"}'
// GET as JSON
const user = await env.MY_KV.get('user:123', 'json');
console.log(user.name); // 'John'
// GET as ArrayBuffer (for binary)
const binary = await env.MY_KV.get('file:123', 'arrayBuffer');
// GET as stream
const stream = await env.MY_KV.get('large:123', 'stream');
return new Response('OK');
},
};
Delete
// Delete single key
await env.MY_KV.delete('user:123');
// No bulk delete via API - use wrangler
// wrangler kv:key delete --binding MY_KV key1
TTL (Time To Live)
Set Expiration
// Expires in 1 hour (seconds)
await env.MY_KV.put('session:abc', sessionData, {
expirationTtl: 3600,
});
// Expires at specific timestamp
await env.MY_KV.put('token:xyz', tokenData, {
expiration: Math.floor(Date.now() / 1000) + 86400, // 24 hours from now
});
// No expiration (default)
await env.MY_KV.put('config:app', configData);
Common TTL Values
const TTL = {
MINUTE: 60,
HOUR: 3600,
DAY: 86400,
WEEK: 604800,
MONTH: 2592000,
};
// Cache API response for 1 hour
await env.CACHE.put(`api:${endpoint}`, response, {
expirationTtl: TTL.HOUR,
});
// Session expires in 7 days
await env.SESSIONS.put(`session:${id}`, sessionData, {
expirationTtl: TTL.WEEK,
});
// Put with metadata
await env.MY_KV.put('user:123', userData, {
metadata: {
created: Date.now(),
version: 1,
type: 'user',
},
});
// Get with metadata
const { value, metadata } = await env.MY_KV.getWithMetadata('user:123', 'json');
console.log(value); // User data
console.log(metadata); // { created: ..., version: 1, type: 'user' }
List Keys
Enumerate Keys
// List all keys
const { keys } = await env.MY_KV.list();
// keys = [{ name: 'key1', ... }, { name: 'key2', ... }]
// List with prefix
const { keys: userKeys } = await env.MY_KV.list({ prefix: 'user:' });
// Pagination
const { keys, list_complete, cursor } = await env.MY_KV.list({
prefix: 'log:',
limit: 100,
});
if (!list_complete) {
// Get next page
const { keys: moreKeys } = await env.MY_KV.list({
prefix: 'log:',
cursor,
});
}
Common Patterns
Caching Pattern
async function getCachedData<T>(
kv: KVNamespace,
key: string,
fetcher: () => Promise<T>,
ttl: number
): Promise<T> {
// Try cache first
const cached = await kv.get(key, 'json');
if (cached) {
return cached as T;
}
// Fetch fresh data
const data = await fetcher();
// Store in cache
await kv.put(key, JSON.stringify(data), {
expirationTtl: ttl,
});
return data;
}
// Usage
const users = await getCachedData(
env.CACHE,
'users:all',
async () => {
const response = await fetch(`${SUPABASE_URL}/rest/v1/users`);
return response.json();
},
3600 // 1 hour
);
Session Storage
interface Session {
userId: string;
email: string;
createdAt: number;
}
async function createSession(env: Env, userId: string, email: string): Promise<string> {
const sessionId = crypto.randomUUID();
const session: Session = {
userId,
email,
createdAt: Date.now(),
};
await env.SESSIONS.put(`session:${sessionId}`, JSON.stringify(session), {
expirationTtl: 86400 * 7, // 7 days
});
return sessionId;
}
async function getSession(env: Env, sessionId: string): Promise<Session | null> {
const session = await env.SESSIONS.get(`session:${sessionId}`, 'json');
return session as Session | null;
}
async function deleteSession(env: Env, sessionId: string): Promise<void> {
await env.SESSIONS.delete(`session:${sessionId}`);
}
Rate Limiting
async function checkRateLimit(
kv: KVNamespace,
key: string,
limit: number,
windowSeconds: number
): Promise<{ allowed: boolean; remaining: number }> {
const current = parseInt(await kv.get(key) || '0');
if (current >= limit) {
return { allowed: false, remaining: 0 };
}
await kv.put(key, (current + 1).toString(), {
expirationTtl: windowSeconds,
});
return { allowed: true, remaining: limit - current - 1 };
}
// Usage in middleware
const ip = request.headers.get('CF-Connecting-IP') || 'unknown';
const { allowed, remaining } = await checkRateLimit(
env.RATE_LIMIT,
`rate:${ip}`,
100, // 100 requests
60 // per minute
);
if (!allowed) {
return new Response('Rate limit exceeded', { status: 429 });
}
Limits
KV Limits
┌─────────────────────────────────────────────────────────────┐
│ KV LIMITS │
├─────────────────────────────────────────────────────────────┤
│ │
│ Free: Paid: │
│ - 100,000 reads/day - 10M reads/month │
│ - 1,000 writes/day - 1M writes/month │
│ - 1 GB storage - Pay per GB │
│ │
│ All plans: │
│ - Max key size: 512 bytes │
│ - Max value size: 25 MB │
│ - Max metadata size: 1024 bytes │
│ - Eventually consistent (up to 60s) │
│ │
└─────────────────────────────────────────────────────────────┘
Tổng kết
Operations
| Operation |
Method |
| Read |
kv.get(key) |
| Write |
kv.put(key, value, options) |
| Delete |
kv.delete(key) |
| List |
kv.list({ prefix }) |
| Read + Meta |
kv.getWithMetadata(key) |
Use Cases
✅ Caching API responses
✅ Session storage
✅ Feature flags
✅ Rate limiting
✅ Configuration storage
Q&A
- Caching strategy hiện tại?
- Session storage ở đâu?
- Có cần rate limiting không?