Post

Tại sao tốc độ đọc ghi trên RAM nhanh hơn trên đĩa

Tại sao tốc độ đọc ghi trên RAM nhanh hơn trên đĩa

Tại sao RAM nhanh hơn ổ cứng? Từ lý thuyết đến thực hành

Phần 1: Lý thuyết cơ bản

1.1. RAM là gì?

RAM (Random Access Memory) là bộ nhớ tạm thời của máy tính, nơi lưu trữ dữ liệu mà CPU đang xử lý.

Đặc điểm:

  • Truy cập dữ liệu cực nhanh (~100 nanosecond)
  • Cần điện liên tục để duy trì dữ liệu
  • Giá thành cao (~$5-10/GB)
  • Dung lượng hạn chế (8-64GB cho người dùng thông thường)

Ví dụ thực tế: Giống như bàn làm việc của bạn - bạn đặt những thứ cần dùng ngay lên bàn để lấy nhanh, nhưng bàn có diện tích hạn chế và khi bạn đứng dậy (tắt máy), mọi thứ sẽ bị dọn sạch.

1.2. Ổ cứng (HDD/SSD) là gì?

Ổ cứng là nơi lưu trữ dữ liệu lâu dài của máy tính.

HDD (Hard Disk Drive) - Ổ đĩa cứng truyền thống

Đặc điểm:

  • Chậm (~10 millisecond)
  • Lưu trữ lâu dài
  • Rất rẻ (~$0.02/GB)
  • Dung lượng lớn (1-20TB)
  • Có bộ phận cơ học (đĩa quay + đầu đọc)

SSD (Solid State Drive) - Ổ cứng thể rắn

Đặc điểm:

  • Nhanh hơn HDD (~100 microsecond)
  • Lưu trữ lâu dài
  • Trung bình (~$0.1/GB)
  • Dung lượng trung bình (256GB-4TB)
  • Không có cơ học, dùng chip điện tử

Ví dụ thực tế: Giống như tủ đồ và kho chứa - bạn cất những thứ ít dùng vào đó. Có thể là tủ hiện đại (SSD) hoặc kho cũ xa xôi (HDD), nhưng đều chậm hơn nhiều so với lấy đồ trên bàn (RAM).

1.3. Tại sao RAM nhanh hơn?

Yếu tố 1: Công nghệ phần cứng

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
RAM:
┌─────────────┐
│ Transistor  │ → Tín hiệu điện trực tiếp
│   + Tụ điện │ → Không có chuyển động cơ học
└─────────────┘ → Tốc độ ánh sáng (~300,000 km/s)

HDD:
┌─────────────┐
│  Đĩa quay   │ → Phải đợi đĩa quay đúng vị trí
│  + Đầu đọc  │ → Đầu đọc di chuyển cơ học
└─────────────┘ → Giới hạn vật lý (~100 m/s)

SSD:
┌─────────────┐
│ NAND Flash  │ → Không có cơ học
│ + Controller│ → Nhưng phải qua nhiều lớp xử lý
└─────────────┘ → Chậm hơn RAM nhưng nhanh hơn HDD

Yếu tố 2: Khoảng cách vật lý

Tín hiệu điện có tốc độ hữu hạn, nên khoảng cách càng ngắn, tốc độ càng nhanh:

1
2
3
4
5
CPU ↔ L1 Cache:   ~5 mm    (trong CPU)
CPU ↔ L2 Cache:   ~10 mm   (trong CPU)
CPU ↔ RAM:        ~15 cm   (trên mainboard)
CPU ↔ SSD:        ~30 cm   (qua cáp và controller)
CPU ↔ HDD:        ~50 cm   (qua cáp SATA dài)

Yếu tố 3: Đường truyền dữ liệu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
RAM → CPU:
  ├─ Kênh: DDR4 64-bit bus
  ├─ Tốc độ: 3200 MHz
  └─ Băng thông: ~50 GB/s

SSD NVMe → CPU:
  ├─ Kênh: PCIe 4.0 x4
  ├─ Tốc độ: 8 GT/s
  └─ Băng thông: ~7 GB/s

SSD SATA → CPU:
  ├─ Kênh: SATA III
  ├─ Tốc độ: 6 Gbps
  └─ Băng thông: ~0.5 GB/s

HDD → CPU:
  ├─ Kênh: SATA III
  ├─ Tốc độ: 6 Gbps (lý thuyết)
  └─ Băng thông: ~0.15 GB/s (thực tế)

1.4. So sánh tốc độ cụ thể

Bộ nhớLatencyBăng thôngNhanh hơn RAM
CPU L1 Cache1 ns1,000 GB/s100x nhanh hơn
CPU L2 Cache4 ns200 GB/s25x nhanh hơn
RAM (DDR4)100 ns50 GB/sBaseline (1x)
NVMe SSD25 μs7 GB/s250x chậm hơn
SATA SSD100 μs0.5 GB/s1,000x chậm hơn
HDD 7200 RPM10 ms0.15 GB/s100,000x chậm hơn

Đơn vị thời gian:

  • 1 giây = 1,000 millisecond (ms)
  • 1 millisecond = 1,000 microsecond (μs)
  • 1 microsecond = 1,000 nanosecond (ns)

1.5. Minh họa bằng thực tế

Giả sử 1 nanosecond = 1 giây, thì:

Thao tácThời gian thựcThời gian tương đương
Đọc từ L1 Cache1 ns1 giây
Đọc từ RAM100 ns1 phút 40 giây
Đọc từ SSD25 μs7 giờ
Đọc từ HDD10 ms4 tháng

Bạn thấy đấy, chênh lệch là khổng lồ!


Phần 2: Quy trình truy cập dữ liệu

2.1. Đọc dữ liệu từ RAM

1
2
3
4
5
6
7
8
9
Bước 1: CPU gửi địa chỉ bộ nhớ
  ↓ (~10 ns)
Bước 2: RAM decoder định vị ô nhớ
  ↓ (~40 ns)
Bước 3: Đọc dữ liệu qua bus
  ↓ (~50 ns)
Bước 4: Dữ liệu về CPU
  
Tổng thời gian: ~100 nanoseconds

2.2. Đọc dữ liệu từ HDD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Bước 1: CPU gửi lệnh qua SATA controller
  ↓ (~100 μs)
Bước 2: Controller xử lý lệnh
  ↓ (~200 μs)
Bước 3: Đầu đọc di chuyển đến vị trí (Seek Time)
  ↓ (~4-8 ms)
Bước 4: Đợi đĩa quay đến vị trí đúng (Rotational Latency)
  ↓ (~2-4 ms)
Bước 5: Đọc dữ liệu từ đĩa
  ↓ (~1-3 ms)
Bước 6: Truyền dữ liệu qua SATA về CPU
  ↓ (~100 μs)
Bước 7: Dữ liệu về CPU

Tổng thời gian: ~7-15 milliseconds

Kết luận: HDD chậm hơn RAM khoảng 70,000 - 150,000 lần!

2.3. Đọc dữ liệu từ SSD

1
2
3
4
5
6
7
8
9
10
11
Bước 1: CPU gửi lệnh qua PCIe/SATA
  ↓ (~5 μs)
Bước 2: SSD controller xử lý
  ↓ (~10 μs)
Bước 3: Đọc từ NAND flash
  ↓ (~50 μs)
Bước 4: Truyền về CPU
  ↓ (~10 μs)
Bước 5: Dữ liệu về CPU

Tổng thời gian: ~75 microseconds

Kết luận: SSD chậm hơn RAM khoảng 750 lần, nhưng nhanh hơn HDD 100-200 lần!


Phần 3: Ứng dụng thực tế

Hiểu được lý thuyết, giờ chúng ta sẽ áp dụng vào lập trình thực tế.

3.1. Khi nào dùng RAM?

Cache với Redis

Redis lưu trữ dữ liệu trên RAM, phù hợp cho:

1. Session Management

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Lưu session user
import { redisService } from './services/redis.service'

async function createSession(userId: string, data: any) {
  const sessionId = generateSessionId()
  
  await redisService.set(
    `session:${sessionId}`,
    JSON.stringify({ userId, ...data }),
    3600 // TTL 1 giờ
  )
  
  return sessionId
}

async function getSession(sessionId: string) {
  const data = await redisService.get(`session:${sessionId}`)
  return data ? JSON.parse(data) : null
}

// Lợi ích:
// - Truy cập session trong ~0.1ms
// - Tự động xóa sau 1 giờ (TTL)
// - Giảm tải database

2. Caching dữ liệu thường xuyên truy cập

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Cache-aside pattern
async function getUser(userId: string) {
  const cacheKey = `user:${userId}`
  
  // Bước 1: Kiểm tra cache (RAM - nhanh)
  let user = await redisService.get(cacheKey)
  
  if (user) {
    console.log('Cache hit - Trả về trong ~0.1ms')
    return JSON.parse(user)
  }
  
  // Bước 2: Không có cache, đọc từ database (Disk - chậm)
  console.log('Cache miss - Đọc từ DB trong ~10ms')
  user = await db.users.findById(userId)
  
  // Bước 3: Lưu vào cache cho lần sau
  await redisService.set(cacheKey, JSON.stringify(user), 300)
  
  return user
}

// Kết quả:
// - Lần 1: 10ms (đọc từ database)
// - Lần 2+: 0.1ms (đọc từ cache) → Nhanh hơn 100 lần!

3. Rate Limiting

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import { Request, Response, NextFunction } from 'express'

async function rateLimiter(req: Request, res: Response, next: NextFunction) {
  const ip = req.ip
  const key = `rate_limit:${ip}`
  
  // Đếm số request từ IP này
  const count = await redisService.get(key)
  
  if (count && parseInt(count) > 100) {
    return res.status(429).json({ 
      error: 'Too many requests' 
    })
  }
  
  // Tăng counter
  if (count) {
    await redisService.set(key, (parseInt(count) + 1).toString(), 60)
  } else {
    await redisService.set(key, '1', 60) // TTL 60s
  }
  
  next()
}

// Lợi ích:
// - Kiểm tra rate limit trong ~0.1ms
// - Không cần database phức tạp
// - Tự động reset sau 60 giây

4. Leaderboard / Ranking

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Sử dụng Sorted Set của Redis
async function updateScore(userId: string, score: number) {
  await redisService.getClient().zadd('leaderboard', score, userId)
}

async function getTopPlayers(limit: number = 10) {
  const players = await redisService.getClient().zrevrange(
    'leaderboard',
    0,
    limit - 1,
    'WITHSCORES'
  )
  
  return players
}

async function getUserRank(userId: string) {
  const rank = await redisService.getClient().zrevrank('leaderboard', userId)
  return rank !== null ? rank + 1 : null
}

// Lợi ích:
// - Truy vấn ranking trong ~0.1ms
// - Database sẽ mất ~100ms để sort và filter
// - Lý tưởng cho game, voting, trending

5. Real-time Analytics

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Đếm page views theo thời gian thực
async function trackPageView(pageUrl: string) {
  const today = new Date().toISOString().split('T')[0]
  const key = `pageviews:${pageUrl}:${today}`
  
  await redisService.getClient().incr(key)
  await redisService.expire(key, 86400 * 30) // Giữ 30 ngày
}

async function getPageViews(pageUrl: string, date: string) {
  const key = `pageviews:${pageUrl}:${date}`
  const views = await redisService.get(key)
  return views ? parseInt(views) : 0
}

// Lợi ích:
// - Cập nhật counter trong ~0.1ms
// - Không làm quá tải database
// - Có thể handle hàng triệu request/giây

In-Memory Storage trong Application

1
2
3
4
5
6
7
8
// Map để lưu tạm
const activeConnections = new Map<string, WebSocket>()
const requestCache = new Map<string, { data: any; timestamp: number }>()

// Phù hợp cho:
// - WebSocket connections
// - Temporary cache (không cần persist)
// - Application state

3.2. Khi nào dùng SSD/HDD?

Database với PostgreSQL/MySQL

Database lưu trữ trên disk (SSD/HDD) vì:

  • Dữ liệu quan trọng, cần lưu trữ lâu dài
  • Dữ liệu lớn (GB → TB)
  • ACID compliance (Atomicity, Consistency, Isolation, Durability)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Pool } from 'pg'

const pool = new Pool({
  host: 'localhost',
  database: 'myapp',
  user: 'postgres',
  password: 'password'
})

// Lưu user vào database
async function createUser(email: string, name: string) {
  const result = await pool.query(
    'INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *',
    [email, name]
  )
  
  return result.rows[0]
}

// Thời gian: ~5-10ms với SSD, ~20-50ms với HDD
// Nhưng dữ liệu được bảo toàn vĩnh viễn

File Storage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import fs from 'fs/promises'
import path from 'path'

// Upload file
async function saveUploadedFile(file: Buffer, filename: string) {
  const uploadDir = '/uploads'
  const filepath = path.join(uploadDir, filename)
  
  await fs.writeFile(filepath, file)
  
  return filepath
}

// Download file
async function getFile(filepath: string) {
  return await fs.readFile(filepath)
}

// Sử dụng cho:
// - Hình ảnh, video, documents
// - Backups
// - Logs

Logging

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import winston from 'winston'

const logger = winston.createLogger({
  transports: [
    new winston.transports.File({ 
      filename: 'error.log', 
      level: 'error' 
    }),
    new winston.transports.File({ 
      filename: 'combined.log' 
    })
  ]
})

// Logs phải lưu trên disk
// - Dùng để debug, audit
// - Không cần tốc độ cao
// - Cần lưu trữ lâu dài

3.3. Chiến lược Hybrid (Kết hợp RAM + Disk)

Cách tốt nhất là kết hợp cả hai: dùng RAM làm cache, disk làm persistent storage.

Pattern 1: Cache-Aside (Lazy Loading)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
async function getProduct(productId: string) {
  const cacheKey = `product:${productId}`
  
  // 1. Thử cache trước (RAM)
  let product = await redisService.get(cacheKey)
  if (product) {
    return JSON.parse(product) // ~0.1ms
  }
  
  // 2. Cache miss - đọc từ database (Disk)
  product = await db.products.findById(productId) // ~10ms
  
  // 3. Lưu vào cache
  await redisService.set(cacheKey, JSON.stringify(product), 3600)
  
  return product
}

// Hiệu quả:
// - 90% requests hit cache → 0.1ms
// - 10% requests miss cache → 10ms
// - Trung bình: ~1ms (thay vì 10ms)

Pattern 2: Write-Through Cache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
async function updateProduct(productId: string, data: any) {
  // 1. Cập nhật database (Disk) - source of truth
  const updated = await db.products.update(productId, data)
  
  // 2. Cập nhật cache (RAM) luôn
  const cacheKey = `product:${productId}`
  await redisService.set(cacheKey, JSON.stringify(updated), 3600)
  
  return updated
}

// Lợi ích:
// - Cache luôn đồng bộ với database
// - Read operations cực nhanh
// - Write operations hơi chậm (phải write 2 nơi)

Pattern 3: Write-Behind Cache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import Queue from 'bull'

const writeQueue = new Queue('database-writes', {
  redis: { host: 'localhost', port: 6379 }
})

async function updateProductAsync(productId: string, data: any) {
  // 1. Cập nhật cache ngay (RAM) - nhanh
  const cacheKey = `product:${productId}`
  await redisService.set(cacheKey, JSON.stringify(data), 3600)
  
  // 2. Queue job để update database sau (Disk) - async
  await writeQueue.add({
    productId,
    data
  })
  
  return data
}

// Worker xử lý queue
writeQueue.process(async (job) => {
  const { productId, data } = job.data
  await db.products.update(productId, data)
})

// Lợi ích:
// - Write cực nhanh (~0.1ms)
// - Database được update bất đồng bộ
// - Nhược điểm: Có khả năng mất data nếu crash

Pattern 4: Multi-Level Caching

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// Level 1: Application Memory (fastest)
const l1Cache = new Map<string, any>()

// Level 2: Redis (fast)
// Level 3: Database (slow)

async function getUser(userId: string) {
  // L1: Application memory
  if (l1Cache.has(userId)) {
    console.log('L1 cache hit')
    return l1Cache.get(userId) // ~0.001ms
  }
  
  // L2: Redis
  const cacheKey = `user:${userId}`
  let user = await redisService.get(cacheKey)
  if (user) {
    console.log('L2 cache hit')
    user = JSON.parse(user)
    l1Cache.set(userId, user) // Promote to L1
    return user // ~0.1ms
  }
  
  // L3: Database
  console.log('L3 cache miss - database query')
  user = await db.users.findById(userId) // ~10ms
  
  // Backfill caches
  await redisService.set(cacheKey, JSON.stringify(user), 300)
  l1Cache.set(userId, user)
  
  return user
}

// Hiệu quả:
// - 80% requests: L1 → 0.001ms
// - 15% requests: L2 → 0.1ms
// - 5% requests: L3 → 10ms
// - Trung bình: ~0.5ms

Phần 4: Best Practices

4.1. Khi nào nên dùng Cache?

NÊN cache:

  • Dữ liệu được đọc nhiều, thay đổi ít (read-heavy)
  • Dữ liệu có thể tái tạo được nếu mất
  • Truy vấn database phức tạp, tốn thời gian
  • API response từ third-party services

KHÔNG NÊN cache:

  • Dữ liệu thay đổi liên tục (real-time data)
  • Dữ liệu nhạy cảm, bảo mật cao
  • Dữ liệu mà bạn không thể mất (critical data)

4.2. Cache Invalidation Strategies

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 1. Time-based (TTL)
await redisService.set('key', 'value', 3600) // Expire sau 1 giờ

// 2. Event-based (khi dữ liệu thay đổi)
async function updateUser(userId: string, data: any) {
  await db.users.update(userId, data)
  await redisService.del(`user:${userId}`) // Xóa cache
}

// 3. Versioning
const version = Date.now()
await redisService.set(`user:${userId}:${version}`, data)

// 4. Cache tags
await redisService.set('product:123', data)
await redisService.getClient().sadd('tag:electronics', 'product:123')

// Khi cần invalidate tag
const products = await redisService.getClient().smembers('tag:electronics')
for (const key of products) {
  await redisService.del(key)
}

4.3. Monitoring và Optimization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Track cache hit rate
let cacheHits = 0
let cacheMisses = 0

async function getCachedData(key: string) {
  const data = await redisService.get(key)
  
  if (data) {
    cacheHits++
  } else {
    cacheMisses++
  }
  
  const hitRate = cacheHits / (cacheHits + cacheMisses)
  console.log(`Cache hit rate: ${(hitRate * 100).toFixed(2)}%`)
  
  return data
}

// Mục tiêu: Hit rate > 80%
// Nếu < 80%: Cân nhắc tăng TTL hoặc pre-warm cache

4.4. Memory Management

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Giới hạn memory cho Redis
// redis.conf:
// maxmemory 2gb
// maxmemory-policy allkeys-lru

// LRU (Least Recently Used): Xóa key ít dùng nhất khi hết memory

// Trong code: Dọn dẹp cache định kỳ
setInterval(async () => {
  const keys = await redisService.getClient().keys('temp:*')
  if (keys.length > 0) {
    await redisService.getClient().del(...keys)
  }
}, 3600000) // Mỗi giờ

Phần 5: So sánh hiệu năng thực tế

5.1. Benchmark đơn giản

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { performance } from 'perf_hooks'

async function benchmark() {
  const iterations = 10000
  
  // Test 1: Redis (RAM)
  const redisStart = performance.now()
  for (let i = 0; i < iterations; i++) {
    await redisService.set(`test:${i}`, `value${i}`)
    await redisService.get(`test:${i}`)
  }
  const redisEnd = performance.now()
  const redisTime = redisEnd - redisStart
  
  // Test 2: PostgreSQL (Disk)
  const dbStart = performance.now()
  for (let i = 0; i < iterations; i++) {
    await db.query('INSERT INTO test (key, value) VALUES ($1, $2)', [`test:${i}`, `value${i}`])
    await db.query('SELECT value FROM test WHERE key = $1', [`test:${i}`])
  }
  const dbEnd = performance.now()
  const dbTime = dbEnd - dbStart
  
  console.log(`Redis: ${redisTime.toFixed(2)}ms (${(iterations / redisTime * 1000).toFixed(0)} ops/sec)`)
  console.log(`PostgreSQL: ${dbTime.toFixed(2)}ms (${(iterations / dbTime * 1000).toFixed(0)} ops/sec)`)
  console.log(`PostgreSQL chậm hơn: ${(dbTime / redisTime).toFixed(2)}x`)
}

// Kết quả thực tế (trên MacBook Pro M1):
// Redis: 850ms (11,765 ops/sec)
// PostgreSQL: 45,000ms (222 ops/sec)
// PostgreSQL chậm hơn: 52.94x

5.2. Real-world Example: E-commerce Product Page

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Không có cache
async function getProductPageWithoutCache(productId: string) {
  const start = performance.now()
  
  const product = await db.products.findById(productId) // ~10ms
  const reviews = await db.reviews.findByProductId(productId) // ~15ms
  const related = await db.products.findRelated(productId) // ~20ms
  const inventory = await inventoryService.check(productId) // ~30ms (external API)
  
  const end = performance.now()
  console.log(`Total time: ${(end - start).toFixed(2)}ms`) // ~75ms
  
  return { product, reviews, related, inventory }
}

// Có cache
async function getProductPageWithCache(productId: string) {
  const start = performance.now()
  
  const cacheKey = `product_page:${productId}`
  let data = await redisService.get(cacheKey)
  
  if (data) {
    const end = performance.now()
    console.log(`Total time: ${(end - start).toFixed(2)}ms`) // ~0.5ms
    return JSON.parse(data)
  }
  
  // Cache miss - fetch and cache
  const product = await db.products.findById(productId)
  const reviews = await db.reviews.findByProductId(productId)
  const related = await db.products.findRelated(productId)
This post is licensed under CC BY 4.0 by the author.