Best Practices for Requests and Storage#
I. On Request Methods#
1. POST vs PUT Feature Comparison#
| Feature | POST | PUT |
|---|
| Semantics | Create child resource, or submit data for processing | Replace entire target resource (idempotent) |
| Idempotency | ❌ Not guaranteed (multiple requests may create multiple resources) | ✅ Idempotent (multiple requests yield same result) |
| URI | URI typically determined by server (e.g., /posts → /posts/123) | Client explicitly specifies full URI (e.g., /posts/123) |
| Common use cases | Create new resource, form submission, partial updates (commonly used before PATCH) | Full update of known resource, create resource (if client specifies ID) |
2. Example Comparison#
| Operation | Use POST | Use PUT |
|---|
| Create new article | ✅ Common practice | Also possible (client needs to generate ID, e.g., /articles/uuid-123) |
| Full update of article 123 | ❌ Possible but not recommended (non-idempotent) | ✅ Standard practice |
| Submit form (cart, contact form) | ✅ | ❌ |
| Upload large file and overwrite existing | ❌ (use PUT) | ✅ |
3. Misconceptions and Supplements#
- POST can also update: REST does not prohibit POST for updates, but PUT/PATCH are more semantically explicit
- Partial updates: Standard approach is PATCH, because PUT requires full replacement (missing fields will be deleted)
- PUT for resource creation: Allowed, but only if client can provide a unique and final URI
- Some API practices: Only expose POST + PATCH, ignoring PUT
- Idempotency is important for network retries (PUT is safe, POST is not)
Quick Decision Guide#
Client specifies full URI + idempotent (safe retry) + full replacement → PUT
URI determined by server + may change state / create resource → POST
Partial update + idempotent → PATCH
II. Backend Storage#
1. Core Principles#
| Principle | Description |
|---|
| Data safety first | Data loss is more critical than slowness |
| Choose on demand | No universal storage, only suitable scenarios |
| Tiered storage | Separate hot/warm/cold data |
| Immutability principle | Append over modify, soft delete over hard delete |
| Observability | Slow queries, connection pools, capacity must be monitorable |
2. Database Selection Decision Tree#
Complex data relationships? Many joins?
├─ Yes → Relational database (PostgreSQL / MySQL)
│ ├─ Strong consistency + complex transactions → PostgreSQL
│ └─ Read-heavy + mature ecosystem → MySQL
Large data volume (TB-level)? Heavy write pressure?
├─ Yes → Distributed database
│ ├─ Strong transactions + horizontal scaling → TiDB / CockroachDB
│ └─ Eventual consistency + ultra-high throughput → Cassandra / HBase
Flexible data structure? No schema?
├─ Yes → Document database (MongoDB)
│ └─ Suitable for: Logs, user profiles, CMS
Need full-text search?
├─ Yes → Elasticsearch (dual-write with primary storage)
Need caching + high performance?
├─ Yes → Redis
│ └─ Suitable for: Sessions, counters, leaderboards, distributed locks
Need graph relationships (social, recommendation)?
├─ Yes → Graph database (Neo4j / Amazon Neptune)
Time-series data (IoT, monitoring)?
├─ Yes → Time-series database (InfluxDB / TimescaleDB / Prometheus)
3. Tiered Storage Architecture#
Request Path
│
▼
┌──────────────────────────────────────────────────────┐
│ L1: Cache Layer │
│ Redis / Memcached │
│ - TTL 5-15 minutes │
│ - Cache penetration/breakdown/avalanche protection │
└──────────────────────────────────────────────────────┘
│ Miss
▼
┌──────────────────────────────────────────────────────┐
│ L2: Hot Storage │
│ PostgreSQL / MySQL (SSD, master-slave cluster) │
│ - Last 7-30 days data │
│ - Directly accessed by online services │
└──────────────────────────────────────────────────────┘
│ Archival policy
▼
┌──────────────────────────────────────────────────────┐
│ L3: Warm Storage │
│ Archive tables / Partitioned tables / Object storage│
│ - 30 days ~ 1 year data │
│ - Available for offline analysis │
└──────────────────────────────────────────────────────┘
│ Long-term retention
▼
┌──────────────────────────────────────────────────────┐
│ L4: Cold Storage │
│ Object storage (Glacier Deep Archive) │
│ - Data older than 1 year │
│ - Retrieval time 12-48 hours │
└──────────────────────────────────────────────────────┘
4. Relational Database Best Practices#
Table Design#
-- ✅ Recommended: Soft delete + audit fields
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
status SMALLINT DEFAULT 1, -- 1:active 2:disabled 3:deleted
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
deleted_at TIMESTAMPTZ, -- NULL = not deleted
version INT DEFAULT 1 -- Optimistic lock
);
-- ✅ Use UUID as business ID (prevents enumeration / sharding-compatible)
-- ❌ Avoid auto-increment ID exposing business volume
Index Strategy#
| Scenario | Recommendation |
|---|
Equality query (WHERE email = ?) | B-Tree index |
Range query (WHERE created_at > ?) | B-Tree index with consistent sort order |
Fuzzy query (LIKE 'abc%') | Regular index (leading wildcard %abc won’t use index) |
| Full-text search | Inverted index (PostgreSQL tsvector / Elasticsearch) |
| Multi-column sorting | Composite index (covering sort fields) |
| JSON field query | GIN index (PostgreSQL) |
Index Golden Rules:
- ≤ 5 indexes per table
- Remove duplicate/redundant indexes decisively
- Use
EXPLAIN to verify execution plans - Monitor index hit rate (unused for > 30 days → drop)
Preventing Slow Queries#
-- Set statement timeout
SET statement_timeout = '5s';
-- ❌ Avoid
SELECT * FROM orders; -- Full table scan
SELECT COUNT(*) FROM huge_table; -- Large table count
WHERE name LIKE '%keyword%'; -- Leading wildcard
WHERE YEAR(created_at) = 2025; -- Function breaks index
-- ✅ Recommended
SELECT id, email, status FROM orders LIMIT 100; -- Limit fields + pagination
WHERE created_at BETWEEN '2025-01-01' AND '2025-12-31';
5. Redis Caching Best Practices#
Data Structure Selection#
| Business Scenario | Recommended Structure | Example |
|---|
| Cache single value | String | user:123 |
| Object cache | Hash | user:123 with multiple fields |
| Leaderboard | Sorted Set | Score ranking |
| Deduplication set | Set | Today’s visit IPs |
| Message queue | Stream / List | Lightweight queue |
| Counter | String + INCR | PV/UV |
| Distributed lock | String + SET NX EX | Redlock |
The Three Cache Problems (Penetration, Breakdown, Avalanche)#
# 1. Cache penetration (querying non-existent data)
# Solution: Bloom filter or cache null values (short TTL)
if not bloom_filter.might_contain(key):
return None
value = cache.get(key)
if value == "NULL":
return None
# 2. Cache breakdown (hot key expires)
# Solution: Mutex lock / logical expiration
value = cache.get(key)
if value is None:
with redis_lock(f"lock:{key}"):
value = db.query()
cache.set(key, value, ttl=300)
# 3. Cache avalanche (many keys expire simultaneously)
# Solution: Add random value to TTL
ttl = 3600 + random.randint(0, 300)
Memory Control#
# Set max memory
maxmemory 4gb
# Eviction policy (choose one)
maxmemory-policy allkeys-lru # Most common
# volatile-lru # Only for keys with expiry
# allkeys-random # Not recommended
# noeviction # Reject writes when memory full
6. Writes and Transactions#
Transaction Boundaries#
# ✅ Recommended: Short transactions
@transaction.atomic
def create_order(user_id, items):
# 1. Create order (1 INSERT)
order = Order.objects.create(user_id=user_id)
# 2. Batch insert order items (N INSERTs)
OrderItem.objects.bulk_create(items)
# 3. Deduct inventory (1 UPDATE)
# Total transaction time < 100ms
# ❌ Avoid: Long transactions
@transaction.atomic
def bad_operation():
# 1. Call external API (may take 5s)
# 2. Send email (may take 2s)
# 3. Update database (1 UPDATE)
# → Holds locks for 7s, blocking other requests
Write Optimization#
| Scenario | Solution |
|---|
| Bulk writes | bulk_insert in batches (500-1000 per batch) |
| High-concurrency deduction | Redis counter + async DB sync |
| Logs/telemetry | Write to local queue first, async batch write |
| Avoid lock contention | Optimistic lock (version field) over pessimistic lock |
| Large field updates | Separate into dedicated table (e.g., JSON config) |
7. High Availability and Backup#
┌─────────────┐
│ Load Balancer │
└──────┬──────┘
│
┌─────────────┼─────────────┐
│ │ │
┌───▼───┐ ┌───▼───┐ ┌───▼───┐
│ Master│ ◄─► │ Slave │ │Slave │ ← Read-write separation
└───┬───┘ └───────┘ └───────┘
│
│ Async replication / Semi-sync
▼
┌───────────┐
│ Backup │ ← Daily full backup + WAL archive
└───────────┘
Backup Strategy#
| Backup Type | Frequency | Retention | RTO |
|---|
| Full backup | Daily | 30 days | 2-4 hours |
| Incremental/WAL | Every 15-30 min | 7 days | 30 minutes |
| Logical export | Weekly | 90 days | Depends on size |
⚠️ Verify backups: Conduct at least one recovery drill per month
Monitoring Metrics#
| Dimension | Key Metrics | Alert Threshold |
|---|
| Connections | Active / Max connections | > 80% |
| Slow queries | Slow query count / QPS | > 10 per minute |
| Disk | Usage | > 80% |
| Replication lag | Master-slave delay in seconds | > 10 seconds |
| Cache hit rate | Hits / Total requests | < 90% (business-dependent) |
| Transactions | Average duration | > 500ms |
8. Common Anti-Patterns#
| Anti-Pattern | Correct Approach |
|---|
| Store large JSON blobs and update frequently | Split table or use document database |
| Put all storage in one database | Read-write separation + sharding |
| No connection pool (new connection each time) | Reuse connection pool |
| Index overuse (10+ indexes per table) | Periodically clean unused indexes |
SELECT * | Only fetch needed fields |
| Query database in loops (N+1 problem) | Batch queries or preloading |
| Rely on auto-increment ID for business logic | Use UUID / Snowflake ID |
| Directly modify table structure in production | Online DDL tools (gh-ost / pt-osc) |
9. Decision Quick Reference#
| Requirement | Solution |
|---|
| Need strong ACID transactions? | → PostgreSQL / MySQL |
| Need extremely high read/write (100k+ QPS)? | → Redis (cache) + sharding |
| Data volume > 1TB? | → Distributed (TiDB / Cassandra) |
| Unpredictable query patterns? | → MongoDB |
| Need full-text search? | → Elasticsearch + primary storage |
| Only a few simple key-value pairs? | → Redis is enough |
| Logs / monitoring metrics? | → Time-series DB + object storage archive |