Hành trình 1 TiB film.mp4 qua Ceph RADOS — Full Deep Dive
Tổng quan hạ tầng
Infrastructure Summary:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
8 OSD Nodes
Mỗi node: 24 × HDD 14TB (data OSD) = 336 TB raw/node
2 × SSD 2TB (index OSD) = 4 TB raw/node
Tổng HDD raw: 8 × 24 × 14TB = 2,688 PB
Tổng SSD raw: 8 × 2 × 2TB = 32 TB
EC Pool (data): k=6, m=2 trên HDD, failure domain = node
Index Pool: replication 3× trên SSD
Sơ đồ ASCII tổng thể: Data Flow
┌─────────────────────────────────────────────────────────────────────┐
│ CLIENT (aws s3 cp) │
│ film.mp4 (1 TiB) → multipart upload → 128 parts × 8 MiB mặc định│
│ (thực tế: xem phân tích bên dưới) │
└───────────────────────────┬─────────────────────────────────────────┘
│ HTTPS PUT (per part)
▼
┌─────────────────────────────────────────────────────────────────────┐
│ CEPH RGW (RADOS Gateway) │
│ Beast frontend → thread pool → rgw::sal::Object::write() │
│ │
│ Mỗi part 8 MiB → stripe thành 2 RADOS objects × 4 MiB │
│ (rgw_obj_stripe_size = 4 MiB default) │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ DATA PATH │ │ INDEX PATH │ │
│ │ (song song) │ │ (song song) │ │
│ └──────┬───────┘ └──────┬───────┘ │
└─────────┼─────────────────────────┼─────────────────────────────────┘
│ librados │ librados
│ aio_write │ aio_write
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ EC POOL (HDD) │ │ INDEX POOL (SSD) │
│ k=6, m=2 │ │ repl-3 │
│ │ │ │
│ CRUSH: 1 OSD/node │ │ CRUSH: 3 SSD OSDs │
│ × 8 nodes │ │ từ 3 nodes khác nhau│
└────────┬────────────┘ └────────┬────────────┘
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ PRIMARY OSD (HDD) │ │ PRIMARY OSD (SSD) │
│ EC encode: │ │ Ghi index entry │
│ 4MB → 8 shards │ │ Replicate → 2 SSD │
│ (6 data + 2 parity)│ │ secondaries │
│ Gửi shards → │ │ │
│ 7 secondary OSDs │ │ │
└────────┬────────────┘ └────────┬────────────┘
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ BlueStore (HDD) │ │ BlueStore (SSD) │
│ WAL → RocksDB → │ │ WAL + Data trên │
│ Data extent │ │ cùng SSD device │
└────────┬────────────┘ └────────┬────────────┘
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ Linux Block Layer │ │ Linux Block Layer │
│ mq-deadline sched │ │ none scheduler │
│ NCQ 32 depth │ │ NVMe queue │
└────────┬────────────┘ └────────┬────────────┘
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ HDD Physical │ │ SSD / NVMe NAND │
│ Actuator → Platter │ │ FTL → NAND pages │
│ Magnetic write │ │ │
└─────────────────────┘ └─────────────────────┘
TẦNG 1 — Client & AWS CLI
1.1 aws s3 cp film.mp4 s3://mybucket/ thực sự làm gì?
AWS CLI phát hiện file 1 TiB (1,099,511,627,776 bytes) vượt ngưỡng multipart_threshold (mặc định 8 MiB). CLI tự động chuyển sang Multipart Upload thay vì single PUT.
Quy trình cụ thể:
- Initiate Multipart Upload: CLI gửi
POST /mybucket/film.mp4?uploadstới RGW endpoint. RGW trả vềUploadId— một chuỗi unique identifier cho toàn bộ upload session. - Tính toán parts: Với
multipart_chunksizemặc định = 8 MiB, file 1 TiB cần: - 1 TiB / 8 MiB = 1,048,576 / 8 = 131,072 parts
- Tuy nhiên S3 API giới hạn tối đa 10,000 parts.
- AWS CLI tự động tăng chunksize để đảm bảo ≤ 10,000 parts.
- Chunksize mới: ceil(1 TiB / 10,000) = ceil(1,099,511,627,776 / 10,000) ≈ 109,951,163 bytes ≈ ~104.86 MiB
- Thực tế CLI round lên: thường chọn ~128 MiB hoặc tính ra khoảng 107 MiB tùy version.
- Giả sử chunksize = 107 MiB: số parts ≈ ceil(1,048,576 MiB / 107 MiB) ≈ 9,799 parts (part cuối nhỏ hơn).
- Concurrency: AWS CLI dùng
max_concurrent_requestsmặc định = 10 threads. Tức là tối đa 10 parts upload song song tại cùng thời điểm. - Upload từng part: Mỗi part là một
PUT /mybucket/film.mp4?partNumber=N&uploadId=XXX. - Complete Multipart Upload: Sau khi tất cả parts hoàn thành, CLI gửi
POST /mybucket/film.mp4?uploadId=XXXkèm danh sách tất cả (partNumber, ETag) → RGW ghép logical object.
1.2 HTTP Headers quan trọng
Mỗi PUT part request có:
PUT /mybucket/film.mp4?partNumber=1&uploadId=abc123 HTTP/1.1
Host: rgw-endpoint.example.com
Content-Length: 112197632 ← kích thước part (~107 MiB)
Content-MD5: dGhpcyBpcyBhIGJhc2U2NA== ← Base64(MD5(part body)) để RGW verify integrity
x-amz-content-sha256: <hex-sha256> ← SHA-256 của body (bắt buộc với SigV4)
Expect: 100-continue ← CLI gửi header trước, chờ RGW "100 Continue"
rồi mới stream body → tránh gửi ~107 MiB
rồi mới bị reject
Authorization: AWS4-HMAC-SHA256 ... ← SigV4 signature
Content-Type: application/octet-stream
Content-MD5: RGW tính MD5 body nhận được, so sánh với header. Nếu mismatch → 400 BadDigest. Đây là tầng integrity đầu tiên.x-amz-content-sha256: Với Signature V4, toàn bộ payload phải được hash SHA-256. Với file lớn, có thể dùngUNSIGNED-PAYLOADnhưng CLI mặc định tính full SHA-256 (tốn CPU client).Expect: 100-continue: Client gửi headers trước, RGW kiểm tra auth/quota/bucket exist, nếu OK trảHTTP/1.1 100 Continue, client mới bắt đầu stream body. Tránh lãng phí bandwidth.
1.3 Retry Logic & Timeout
- Retry: AWS CLI dùng exponential backoff + jitter: lần 1 đợi ~1s, lần 2 ~2s, lần 3 ~4s… tối đa 5 retries (configurable
max_attempts). - Connect timeout: mặc định 60s.
- Read timeout: mặc định 60s. Với HDD chậm, nếu RGW mất >60s xử lý 1 part → client timeout → retry.
- Adaptive retry mode (nếu bật
retry_mode=adaptive): CLI tự throttle khi nhận 503/throttling responses. - Tại sao HDD chậm ảnh hưởng: Nếu cluster đang bận (scrubbing, recovery), write latency HDD tăng từ ~10ms lên hàng trăm ms → RGW response chậm → client có thể timeout ở những parts cuối.
TẦNG 2 — Ceph RGW (RADOS Gateway)
2.1 RGW tiếp nhận multipart request
Frontend: Ceph RGW hiện đại (Quincy, Reef, Squid) mặc định dùng Beast frontend (dựa trên Boost.Beast/Boost.Asio). Civetweb đã deprecated.
Thread model Beast:
- Beast chạy trên Boost.Asio event loop.
rgw_thread_pool_size(mặc định 512) — số worker threads xử lý requests.- Mỗi incoming HTTP connection được gán cho 1 thread từ pool.
- Với 10 concurrent parts từ CLI → 10 threads đồng thời xử lý.
Luồng xử lý 1 part upload:
HTTP PUT part arrives
→ Beast parses HTTP headers
→ Auth: verify SigV4 signature (keystone/internal user DB)
→ Check bucket exists, quota not exceeded
→ 100 Continue response (nếu Expect header có)
→ Stream body vào RGW buffer
→ Stripe body thành RADOS objects
→ Ghi song song: data → EC pool, index → SSD pool
→ Khi cả hai hoàn thành → 200 OK + ETag (MD5 của part)
2.2 Stripe: từ 1 Part thành N RADOS Objects
rgw_obj_stripe_size mặc định = 4 MiB (4,194,304 bytes).
Mỗi multipart part (~107 MiB) được stripe thành:
- ceil(107 MiB / 4 MiB) = ceil(26.75) = 27 RADOS objects (26 × 4 MiB + 1 × phần dư ~3 MiB)
Tổng RADOS objects cho toàn bộ 1 TiB file:
- 1 TiB = 1,048,576 MiB
- 1,048,576 / 4 = 262,144 RADOS objects
2.3 Naming Convention
RADOS object names cho multipart upload tuân theo format:
<bucket_marker>_<object_key>.<upload_id>.N_M
Trong đó:
bucket_marker: unique ID của bucket (ví dụ:default.4128.1)object_key: tên file =film.mp4upload_id: multipart upload IDN: part number (1-based, 1 → 9799)M: stripe index bên trong part (0-based, 0 → 26)
Ví dụ cụ thể:
default.4128.1__shadow_film.mp4.2~abc123.1_0 ← part 1, stripe 0 (bytes 0 - 4MB)
default.4128.1__shadow_film.mp4.2~abc123.1_1 ← part 1, stripe 1 (bytes 4MB - 8MB)
...
default.4128.1__shadow_film.mp4.2~abc123.1_26 ← part 1, stripe 26 (bytes 104MB - 107MB)
default.4128.1__shadow_film.mp4.2~abc123.5000_0 ← part 5000, stripe 0
Object đầu tiên (head object) chứa thêm metadata (content-type, ACL, custom headers) trong xattrs.
2.4 Hai luồng ghi song song
RGW Thread (xử lý 1 part)
│
├──── librados::aio_write(data_obj, EC_pool) ──→ EC Pool (HDD)
│ async, non-blocking
│
├──── librados::aio_write(index_obj, index_pool) ──→ Index Pool (SSD)
│ async, non-blocking
│
└──── wait for BOTH completions
→ return 200 OK to client
Luồng 1 — Data: Mỗi RADOS object 4 MiB được gửi tới EC pool. librados hash object name → PG → CRUSH tìm primary OSD → gửi OSD op.
Luồng 2 — Index: Bucket index ghi lại metadata entry cho object/part: tên, size, ETag, timestamp. Index entry nhỏ (vài trăm bytes) nhưng phải atomic và consistent.
2.5 Tại sao tách Index Pool ra SSD riêng?
Vấn đề nếu index chung HDD:
- Bucket listing (
GET /mybucket/) yêu cầu scan index. Trên HDD, random read latency ~10ms/IO → listing bucket có hàng triệu object trở nên cực chậm. - Mỗi object write cũng kèm index update. Nếu index trên HDD, write latency bị cộng thêm ~10-15ms cho index IO. Trên SSD, index update chỉ ~100-200µs.
- Index contention: Nhiều RGW threads đồng thời ghi index vào cùng bucket index object → nếu trên HDD, lock contention + HDD seek = bottleneck nghiêm trọng.
- Latency mismatch: Data write đã chịu HDD latency rồi. Nếu index cũng trên HDD → cả hai đều chậm → total latency tăng gấp đôi. Tách SSD cho index = index write hoàn thành gần như tức thì, tổng latency chỉ bị bottleneck bởi data write (chấp nhận được).
2.6 librados Async I/O
// Pseudocode cho RGW ghi 1 RADOS object
librados::AioCompletion *data_comp =
librados::Rados::aio_create_completion(callback_data, callback_complete);
librados::AioCompletion *idx_comp =
librados::Rados::aio_create_completion(callback_idx, callback_complete);
// Ghi data object vào EC pool
ec_io_ctx.aio_write("default.4128.1__shadow_film.mp4...1_0",
data_comp,
data_bufferlist, // 4 MiB
4194304, // len
0); // offset
// Ghi index entry vào index pool
idx_io_ctx.aio_operate("mybucket.bucket.index.0",
idx_comp,
&index_write_op); // cls_rgw_bucket_link_olh()
// Đợi cả hai hoàn thành
data_comp->wait_for_complete();
idx_comp->wait_for_complete();
// Kiểm tra return value
if (data_comp->get_return_value() < 0 || idx_comp->get_return_value() < 0) {
// Error handling, retry
}
RGW dùng completion callbacks — khi OSD trả ACK, callback được gọi trong context của librados messenger thread, signal condition variable → RGW thread tiếp tục xử lý.
TẦNG 3 — RADOS, CRUSH & Placement Groups
3.1 CRUSH Map với 2 loại Pool
CRUSH Hierarchy:
━━━━━━━━━━━━━━━━
root default
├── node-01 (host)
│ ├── osd.0 (HDD 14TB)
│ ├── osd.1 (HDD 14TB)
│ ├── ...
│ ├── osd.23 (HDD 14TB)
│ ├── osd.192 (SSD 2TB) ← index OSD
│ └── osd.193 (SSD 2TB) ← index OSD
├── node-02 (host)
│ ├── osd.24 (HDD 14TB)
│ ├── ...
│ ├── osd.47 (HDD 14TB)
│ ├── osd.194 (SSD 2TB)
│ └── osd.195 (SSD 2TB)
├── ...
└── node-08 (host)
├── osd.168 (HDD 14TB)
├── ...
├── osd.191 (HDD 14TB)
├── osd.206 (SSD 2TB)
└── osd.207 (SSD 2TB)
Tổng: 192 HDD OSDs + 16 SSD OSDs = 208 OSDs
CRUSH Rule cho EC Pool (HDD):
rule ec_hdd_rule {
id 1
type erasure
step take default class hdd ← chỉ chọn HDD OSDs
step chooseleaf indep 8 type host ← 8 OSDs, mỗi OSD từ 1 host khác nhau
step emit
}
chooseleaf indep: thuật toán lựa chọn cho EC — “indep” nghĩa là mỗi shard được chọn độc lập, nếu 1 OSD fail thì chỉ shard đó được remap, không ảnh hưởng shard khác.type host: failure domain = host → đảm bảo 8 shards nằm trên 8 nodes khác nhau.- Với 8 nodes và k+m=8 → mỗi PG sẽ có đúng 1 shard trên mỗi node.
CRUSH Rule cho Index Pool (SSD):
rule ssd_repl_rule {
id 2
type replicated
step take default class ssd ← chỉ chọn SSD OSDs
step chooseleaf firstn 3 type host ← 3 replicas, mỗi replica từ 1 host khác
step emit
}
firstn 3: chọn 3 SSD OSDs từ 3 nodes khác nhau.- Tại sao
firstnchứ khôngindep? Replicated pool dùngfirstn— khi 1 OSD fail, các replica shift xuống, đơn giản hơn indep.
3.2 Object → PG Mapping
Công thức:
PG_id = hash(object_name) % pg_num
- Hash function: CRUSH hash (dựa trên
rjenkins1— Robert Jenkins’ hash) hoặcxxhash(Ceph versions mới). pg_numcho EC pool: thường tính theo công thức:
pg_num ≈ (total_OSDs × 100) / k+m
Với 192 HDD OSDs, k+m=8:
pg_num ≈ 192 × 100 / 8 = 2400 → round lên power-of-2 gần nhất (nếu chưa bật pg_autoscale) = 2048 hoặc 4096
Giả sử pg_num = 2048 cho EC pool. - Với 262,144 RADOS objects, chúng phân bố đều vào 2,048 PGs → mỗi PG nhận trung bình ~128 objects từ file này. Nhưng vì hash đều, thực tế mỗi PG có thể nhận 100-160 objects (distribution gần uniform). - Sau khi có PG, CRUSH map PG → 8 OSDs (1 per node). OSD đầu tiên = primary.
3.3 EC 6+2: Toán học Reed-Solomon
Galois Field GF(2⁸):
- Mọi phép tính EC diễn ra trong GF(2⁸) = GF(256), trường hữu hạn với 256 phần tử (0-255).
- Phép cộng = XOR, phép nhân = polynomial multiplication modulo irreducible polynomial (thường dùng
x⁸ + x⁴ + x³ + x² + 1= 0x11D). - Mỗi byte data là 1 phần tử trong GF(256).
Encoding Matrix:
Encoding matrix (8 × 6) cho k=6, m=2:
D0 D1 D2 D3 D4 D5 (6 data shards)
┌ ┐
S0 │ 1 0 0 0 0 0 │ → Data shard 0 (= D0 nguyên vẹn)
S1 │ 0 1 0 0 0 0 │ → Data shard 1 (= D1 nguyên vẹn)
S2 │ 0 0 1 0 0 0 │ → Data shard 2 (= D2 nguyên vẹn)
S3 │ 0 0 0 1 0 0 │ → Data shard 3 (= D3 nguyên vẹn)
S4 │ 0 0 0 0 1 0 │ → Data shard 4 (= D4 nguyên vẹn)
S5 │ 0 0 0 0 0 1 │ → Data shard 5 (= D5 nguyên vẹn)
P0 │ α⁰ α¹ α² α³ α⁴ α⁵│ → Parity shard 0 (linear combination)
P1 │ α⁰ α² α⁴ α⁶ α⁸ α¹⁰│ → Parity shard 1 (khác combination)
└ ┘
(α = generator của GF(2⁸), thường α = 2)
Phần trên (6×6 identity matrix) đảm bảo 6 data shards = dữ liệu gốc (systematic code). Phần dưới (2×6) tạo 2 parity shards.
Tính parity:
P0[byte_i] = α⁰·D0[byte_i] ⊕ α¹·D1[byte_i] ⊕ α²·D2[byte_i] ⊕ ... ⊕ α⁵·D5[byte_i]
P1[byte_i] = α⁰·D0[byte_i] ⊕ α²·D1[byte_i] ⊕ α⁴·D2[byte_i] ⊕ ... ⊕ α¹⁰·D5[byte_i]
Tất cả phép nhân và cộng trong GF(2⁸): nhân dùng log/antilog table, cộng = XOR.
3.4 Ví dụ cụ thể: 1 RADOS Object 4 MiB
RADOS object: 4 MiB = 4,194,304 bytes
Chia thành k=6 data shards:
Mỗi data shard = 4,194,304 / 6 = 699,050.67 bytes
→ Padding: 4,194,304 không chia hết cho 6
→ Padding lên bội số gần nhất: 699,052 × 6 = 4,194,312 (thêm 8 bytes padding)
→ Mỗi data shard: 699,052 bytes ≈ 682.67 KiB
Tính 2 parity shards:
Mỗi parity shard = 699,052 bytes (cùng kích thước data shard)
Tổng shards: 8
Tổng bytes ghi: 8 × 699,052 = 5,592,416 bytes ≈ 5.33 MiB
Overhead ratio: 5.33 / 4.0 = 1.333× (= 8/6 = 1.333...)
3.5 Encoding diễn ra ở đâu?
Encoding diễn ra tại Primary OSD, không phải client.
Luồng:
Client (RGW via librados)
│
│ Gửi FULL 4 MiB object tới Primary OSD
│ (qua OSD messenger, TCP)
▼
Primary OSD
│
│ 1. Nhận toàn bộ 4 MiB vào buffer
│ 2. Chia thành 6 data chunks × ~699 KB
│ 3. Tính 2 parity chunks (Reed-Solomon GF(2⁸))
│ → Dùng ISA-L (Intel Storage Acceleration Library) hoặc Jerasure
│ → ISA-L dùng SIMD (AVX2/AVX512) để tăng tốc GF multiply
│ 4. Giữ lại shard mình (shard 0)
│ 5. Gửi 7 shards còn lại tới 7 secondary OSDs
│
▼
7 Secondary OSDs nhận mỗi OSD 1 shard (~699 KB)
3.6 Tổng bytes ghi vật lý cho 1 TiB file
Data path (EC pool, HDD):
Raw data: 1 TiB = 1,099,511,627,776 bytes
RADOS objects: 262,144 objects × 4 MiB
EC overhead (8/6): 1 TiB × (8/6) = 1.333 TiB ≈ 1,466 GiB
Mỗi node ghi: 1.333 TiB / 8 nodes ≈ 170.67 GiB/node
Mỗi HDD OSD: ~170.67 GiB (chỉ 1 OSD/node tham gia mỗi PG,
nhưng objects phân tán nhiều PGs → nhiều HDD
OSDs trên cùng node cùng nhận data)
Thực tế: phân bố đều 24 HDD → ~7.1 GiB/HDD/node
Chính xác hơn: mỗi shard ~699 KB, tổng 262,144 objects × 8 shards = 2,097,152 shards. Mỗi node nhận 2,097,152/8 = 262,144 shards. Phân bố đều 24 HDD/node → mỗi HDD nhận ~10,922 shards ≈ 7.1 GiB.
Index path (repl-3, SSD):
Số index entries ≈ số multipart parts = ~9,799 entries (1 entry per part trong bucket index)
+ 262,144 internal shadow object entries (index cho mỗi RADOS stripe)
(thực tế: RGW chỉ index per-part, không per-stripe trong bucket index)
→ Chính xác: ~9,800 entries trong bucket index
Mỗi entry ≈ 200-500 bytes (key + metadata)
Tổng index data: ~9,800 × 300 bytes ≈ 2.8 MiB
Replicated 3×: 2.8 MiB × 3 = 8.4 MiB trên SSD
Write amplification tổng thể:
Application data: 1 TiB
Actual HDD writes: 1.333 TiB (EC overhead)
BlueStore WAL writes: +1.333 TiB (data written to WAL first, then to data area = 2×)
→ Deferred write: nếu object ≥ min_alloc_size, ghi trực tiếp
→ HDD min_alloc_size = 64 KB; shard ~699 KB > 64 KB
→ Có thể dùng direct write, bypass WAL cho data portion
→ Nhưng RocksDB metadata vẫn qua WAL
Total HDD write amp: ~1.4-1.5× tổng (bao gồm RocksDB compaction)
Total SSD write amp: ~8.4 MiB × ~2-3 (RocksDB compaction) ≈ 25 MiB (negligible)
Overall storage efficiency: 1 TiB data → ~1.45 TiB physical HDD writes
TẦNG 4 — OSD Process & EC Write Pipeline
4.1 Sơ đồ EC Shard Distribution trên 8 Nodes
EC 6+2 Shard Distribution cho 1 RADOS Object (4 MiB):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Node-1 Node-2 Node-3 Node-4
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ OSD.x │ │ OSD.y │ │ OSD.z │ │ OSD.w │
│ (PRIMARY)│ │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ Shard 0 │ │ Shard 1 │ │ Shard 2 │ │ Shard 3 │
│ (Data D0)│ │ (Data D1)│ │ (Data D2)│ │ (Data D3)│
│ 699 KB │ │ 699 KB │ │ 699 KB │ │ 699 KB │
│ HDD #3 │ │ HDD #17 │ │ HDD #8 │ │ HDD #22 │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
Node-5 Node-6 Node-7 Node-8
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ OSD.a │ │ OSD.b │ │ OSD.c │ │ OSD.d │
│ │ │ │ │ │ │ │
│ Shard 4 │ │ Shard 5 │ │ Shard 6 │ │ Shard 7 │
│ (Data D4)│ │ (Data D5)│ │(Parity P0)│ │(Parity P1)│
│ 699 KB │ │ 699 KB │ │ 699 KB │ │ 699 KB │
│ HDD #11 │ │ HDD #5 │ │ HDD #19 │ │ HDD #2 │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
(HDD # = HDD cụ thể trên node đó, do CRUSH chọn)
4.2 Primary OSD — Chi tiết xử lý
Primary OSD (giả sử Node-1, OSD.x) nhận write op từ RGW:
1. Nhận OSD op WRITE(object_name, data=4MiB) từ librados messenger
2. Map → PG → kiểm tra PG primary = chính mình
3. Lấy PG lock (per-PG write queue, serialized)
4. EC Encode:
a. Split 4 MiB thành 6 data chunks
b. ISA-L ec_encode_data():
- Input: 6 × 699KB data buffers
- Output: 2 × 699KB parity buffers
- Encode table: precomputed từ Vandermonde/Cauchy matrix
- ISA-L dùng AVX2: throughput ~10-20 GB/s trên modern Xeon
- Thời gian encode 4 MiB: ~0.2-0.5ms (negligible so với HDD latency)
5. Tạo 8 sub-write ops, mỗi cái chứa 1 shard
6. Gửi 7 shard ops tới 7 secondary OSDs qua OSD Messenger (async TCP)
7. Tự ghi shard 0 vào local BlueStore
8. Đợi tất cả 8 shards (kể cả local) hoàn thành
9. Khi ALL 8 ACKs nhận → gửi ACK về RGW (librados)
4.3 Secondary OSD — Chi tiết xử lý
Mỗi secondary OSD (7 cái):
1. Nhận sub-write op từ primary qua OSD Messenger
- Payload: 1 shard (~699 KB) + metadata
2. Xác nhận PG mapping đúng, epoch đúng
3. Ghi shard vào BlueStore (chi tiết ở Tầng 5)
4. Khi BlueStore commit → gửi ACK về primary OSD
4.4 Write Pipeline — Sequence Diagram
Thời gian →
RGW Primary OSD Secondary OSD 1-7 BlueStore(Primary) BlueStore(Sec 1-7)
│ │ │ │ │
│──WRITE 4MB──→│ │ │ │
│ │ │ │ │
│ │ EC encode │ │ │
│ │ (0.3ms) │ │ │
│ │ │ │ │
│ │──shard 1-7───→│ │ │
│ │ (async TCP) │ │ │
│ │ │ │ │
│ │──shard 0─────────────────────────→│ │
│ │ │ │ │
│ │ │──shard to──────────────────────────────→│
│ │ │ BlueStore │ │
│ │ │ │ │
│ │ │ │ WAL write │ WAL write
│ │ │ │ (sequential) │ (sequential)
│ │ │ │ ~5-10ms HDD │ ~5-10ms HDD
│ │ │ │ │
│ │ │ │ RocksDB meta │ RocksDB meta
│ │ │ │ ~2-5ms │ ~2-5ms
│ │ │ │ │
│ │ │←────────ACK────────────────────────────│
│ │←───────────────│ ACKs (all 7) │ │
│ │←──────────────local ACK────────────│ │
│ │ │ │ │
│←────ACK──────│ │ │ │
│ │ │ │ │
Total latency per RADOS object: ~15-25ms (dominated by slowest HDD among 8)
4.5 Khi nào “committed”?
BlueStore era (không có journal truyền thống):
- Ceph OSD (BlueStore) báo “applied” (có thể đọc lại) khi data nằm trong WAL/deferred hoặc data area.
- Báo “committed” (durable, survive power loss) khi WAL entry được fsync xuống block device.
- Với HDD:
fdatasync()hoặc dùng FUA/O_DSYNC để đảm bảo data xuống platter (không chỉ trong HDD write cache). - Primary ACK về RGW chỉ sau khi TẤT CẢ 8 shards committed (không phải chỉ applied).
4.6 PG State Machine & Slow OSD
Bình thường: PG ở trạng thái active+clean.
Khi ghi:
active+clean → (write arrives) → active+clean (write completes)
Nếu 1 HDD OSD chậm (slow OSD):
- Primary đợi tất cả 8 ACKs. Nếu 1 secondary OSD chậm (HDD degraded, ~100ms+ thay vì ~15ms), toàn bộ write bị chậm theo “tail latency” problem.
osd_heartbeat_grace(mặc định 20s): nếu OSD không heartbeat trong 20s → MON đánh dấu OSDdown.- PG chuyển sang
active+degraded— writes vẫn tiếp tục nhưng thiếu 1 shard. osd_op_complaint_time(mặc định 30s): log warning nếu 1 op mất >30s.- min_write cho EC pool (tùy config, mặc định cần tất cả k+m): nếu 1 shard fail, write có thể phải retry sau khi PG re-peer.
4.7 Scrubbing & Deep Scrub trên HDD
Scrub (shallow): Kiểm tra metadata consistency — so sánh object metadata giữa các replicas/shards. Không đọc full data.
- Interval:
osd_scrub_min_interval= 1 ngày (mặc định). - Impact nhẹ.
Deep scrub: Đọc TOÀN BỘ data, tính checksum, so sánh.
- Interval:
osd_deep_scrub_interval= 1 tuần (mặc định). - Với 1 HDD 14TB:
Deep scrub throughput: ~100-150 MB/s sequential read (giới hạn bởi HDD)
Thời gian: 14 TB / 130 MB/s ≈ 107,692 seconds ≈ 30 giờ
Thực tế: deep scrub chạy per-PG, throttled. osd_scrub_sleep (mặc định 0) thêm delay giữa scrubs.
- Impact tới write throughput: Deep scrub đọc HDD → tranh giành bandwidth với write I/O → write latency tăng 20-50% khi deep scrub chạy song song.
TẦNG 5 — BlueStore trên HDD
5.1 BlueStore Layout trên 1 HDD 14TB
HDD 14TB Physical Layout:
━━━━━━━━━━━━━━━━━━━━━━━━━
┌─────────────────────────────────────────┐
│ Raw Block Device │
│ /dev/sdX (14 TB) │
│ │
│ ┌────────────┐ ┌────────────────────┐ │
│ │ BlueFS │ │ Data Space │ │
│ │ partition │ │ (phần lớn │ │
│ │ │ │ 14TB) │ │
│ │ ┌────────┐ │ │ │ │
│ │ │RocksDB │ │ │ Raw extents │ │
│ │ │ WAL │ │ │ chứa object │ │
│ │ │ DB │ │ │ data (shards) │ │
│ │ │ SST │ │ │ │ │
│ │ └────────┘ │ │ Managed by │ │
│ │ │ │ BitmapAllocator │ │
│ └────────────┘ └────────────────────┘ │
│ │
└─────────────────────────────────────────┘
RocksDB WAL/DB location cho HDD OSD:
Câu hỏi quan trọng: 2 SSD per node dùng cho index pool → RocksDB WAL/DB của HDD OSD đặt ở đâu?
Trả lời — Có 2 kịch bản:
Kịch bản A (Tối ưu, phổ biến): Dùng chung SSD cho cả index OSD và BlueStore DB/WAL của HDD OSDs.
- Mỗi SSD 2TB được phân vùng: phần lớn cho index OSD data, một phần nhỏ cho BlueStore DB/WAL partitions của các HDD OSDs trên cùng node.
- Ví dụ: mỗi SSD chia ra: 1.5TB cho index OSD + 500GB cho BlueStore DB partitions của 12 HDD OSDs (mỗi HDD OSD được ~40GB DB trên SSD).
- WAL + DB trên SSD giúp RocksDB compaction nhanh, metadata lookup cải thiện đáng kể.
Kịch bản B (Đề bài nói SSD “dành riêng cho index pool”): RocksDB WAL và DB đặt ngay trên HDD chính.
- BlueFS quản lý vùng nhỏ trên cùng HDD cho RocksDB.
- Performance hệ quả: RocksDB operations (WAL sync, compaction, metadata lookup) phải share HDD seek với data I/O → latency tăng 30-50%.
- Đây là worst-case nhưng vẫn hoạt động. BlueStore được thiết kế cho trường hợp này.
Với đề bài “2 SSD dành riêng cho index pool”: Giả sử Kịch bản B — RocksDB WAL/DB trên HDD.
5.2 Write Path BlueStore (cho 1 shard ~699 KB)
Shard data (~699 KB) arrives at BlueStore
│
├─→ Kiểm tra size vs min_alloc_size
│ HDD min_alloc_size = 64 KB (mặc định cho HDD)
│ 699 KB > 64 KB → đủ lớn cho DIRECT WRITE (bypass WAL cho data)
│
├─→ Deferred Write path (cho large writes trên HDD):
│
│ STEP 1: Allocate extents
│ ├── BitmapAllocator tìm contiguous extent ≥ 699 KB
│ ├── Trả về (offset, length) trên raw block device
│ └── Bitmap updated in-memory
│
│ STEP 2: Write data TRỰC TIẾP vào allocated extent
│ ├── pwrite64(/dev/sdX, shard_data, 699KB, allocated_offset)
│ ├── O_DIRECT: bypass page cache
│ ├── Sequential write → HDD throughput tốt (~150-200 MB/s)
│ └── KHÔNG fsync ngay (deferred)
│
│ STEP 3: Write metadata + WAL entry cho transaction
│ ├── RocksDB WriteBatch:
│ │ ├── onode: object metadata (size, checksum, extent map)
│ │ ├── extent map: (object offset → block offset, length)
│ │ └── WAL/deferred op entry: "data at offset X, length Y"
│ ├── WAL fsync (sequential append → HDD ~5-8ms)
│ └── Sau WAL sync → transaction "committed" (durable)
│
│ STEP 4: ACK gửi về primary OSD ← TẠI THỜI ĐIỂM NÀY
│
│ STEP 5 (async, sau ACK):
│ ├── fsync data extent (đảm bảo data trên platter)
│ ├── Xóa deferred op entry trong RocksDB
│ └── Nếu crash trước step 5: replay deferred ops khi restart
│
Tại sao large write bypass WAL?
- WAL trên HDD = ghi data 2 lần vào cùng HDD (write amplification 2×).
- Với shard 699 KB, ghi trực tiếp vào data area + chỉ ghi metadata vào WAL = chỉ 1× data write.
- Trade-off: cần deferred mechanism để đảm bảo atomicity (nếu crash giữa data write và metadata commit → deferred op replay khi recovery).
5.3 Allocator
BitmapAllocator (mặc định từ Nautilus):
- Chia toàn bộ 14TB thành blocks (min_alloc_size = 64KB cho HDD).
- Bitmap: 14TB / 64KB = ~224 million bits ≈ 28 MB bitmap in memory.
- Cấp phát contiguous extent khi có thể (best-fit hoặc next-fit).
- Block size 64KB: HDD hoạt động tốt nhất với writes ≥ 64KB (tránh partial-track writes, maximize sequential throughput).
5.4 Fragmentation trên HDD
- SSD: fragmentation gần như không ảnh hưởng (no seek time, random read ≈ sequential read).
- HDD: mỗi fragment = 1 seek (~8-12ms) + rotational latency (~4ms) = ~12-16ms penalty.
- Nếu 1 shard 699 KB bị fragment thành 10 extents → 10 seeks × 12ms = 120ms thay vì 5ms sequential.
- BlueStore cố gắng cấp phát contiguous, nhưng sau nhiều write/delete cycles → fragmentation tăng.
- Mitigation:
bluestore_extent_map_shard_max_sizegiới hạn extent map, BlueStore prefer large contiguous allocations.
5.5 Checksum
- BlueStore tính CRC32C per block (mỗi 64KB block trên HDD).
- Shard 699 KB → ceil(699/64) = 11 blocks → 11 CRC32C values (mỗi 4 bytes) = 44 bytes checksum.
- Checksums lưu trong RocksDB onode metadata.
- Khi đọc: tính CRC32C data đọc được, so sánh với stored checksum. Mismatch → bit rot detected → trigger repair từ parity shards.
TẦNG 6 — Linux Kernel, Block Layer & HDD Driver
6.1 I/O Interface
BlueStore mở raw block device:
fd = open("/dev/sdX", O_RDWR | O_DIRECT); // bypass page cache
I/O submission:
- BlueStore sử dụng libaio (Linux native AIO) hoặc io_uring (nếu kernel ≥ 5.1 và config cho phép).
io_uring(preferred trên Ceph Reef+): zero-copy, submission queue / completion queue → ít syscall overhead.libaio:io_submit()batch nhiều IOs → 1 syscall.
// libaio path:
struct iocb cb;
io_prep_pwrite(&cb, fd, shard_buf, 699*1024, allocated_offset);
io_submit(ctx, 1, &cb);
// ... later:
io_getevents(ctx, 1, 1, &events, &timeout);
// io_uring path:
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_write(sqe, fd, shard_buf, 699*1024, allocated_offset);
io_uring_submit(&ring);
// ... later:
io_uring_wait_cqe(&ring, &cqe);
6.2 Kernel Block Layer Path
userspace: io_submit() / io_uring_submit()
│
▼
kernel: VFS layer (bypassed vì O_DIRECT trên raw device)
│
▼
Block Layer (block/blk-mq.c)
│
├── I/O Scheduler
│ ├── mq-deadline (thường dùng cho HDD)
│ │ ├── Chia requests thành read/write queues
│ │ ├── Deadline: mỗi request có deadline (read: 500ms, write: 5s)
│ │ ├── Merge adjacent requests
│ │ └── Dispatch theo sector order (minimize seeks)
│ │ nhưng respect deadlines
│ │
│ └── Tại sao KHÔNG dùng "none" cho HDD?
│ "none" = FIFO, không reorder
│ → HDD phải seek theo thứ tự random
│ → Throughput giảm 3-5×
│ SSD dùng "none" vì random = sequential performance
│
▼
Request Merge:
- Requests gần nhau trên disk → merge thành 1 large request
- bio merge: (LBA 1000, 64KB) + (LBA 1008, 64KB) → (LBA 1000, 128KB)
▼
SCSI/SATA subsystem
│
▼
Host Bus Adapter (HBA) driver
- AHCI driver (SATA) hoặc mpt3sas/hpsa (SAS)
- Tạo SCSI/ATA command
│
▼
Hardware: HBA → SATA/SAS cable → HDD controller
6.3 NCQ và SCSI Commands
SATA HDD:
WRITE FPDMA QUEUED (NCQ command)
- LBA: sector address (ví dụ: LBA 0x1A3F0000)
- Sector count: 699KB / 512B = 1398 sectors (hoặc /4096 = 171 sectors nếu 4Kn)
- Tag: 0-31 (NCQ depth = 32)
SAS HDD:
WRITE(16)
- LBA: 8-byte address
- Transfer length: sector count
- Command queue depth: 128-256 (cao hơn SATA NCQ)
NCQ reordering: HDD firmware nhận tối đa 32 commands (SATA NCQ), sắp xếp lại theo vị trí sector trên platter → giảm seek. Ví dụ: 5 writes ở LBA 100, 50000, 200, 49999, 300 → firmware reorder: 100, 200, 300, 49999, 50000 → arm chỉ cần sweep 1 lần.
TẦNG 7 — HDD Physical (Cơ học + Từ tính)
7.1 Cấu tạo vật lý HDD 14TB
Seagate Exos X16 14TB / WD Ultrastar DC HC530 14TB (đại diện):
┌─────────────────────────────────────┐
│ HDD 14TB Enterprise │
│ │
│ Platters: 8 platters │
│ (16 surfaces) │
│ Platter size: ~875 GB/platter │
│ RPM: 7200 │
│ Interface: SATA 6Gb/s hoặc │
│ SAS 12Gb/s │
│ │
│ Tracks/surface: ~500,000+ │
│ Sectors/track: varies (ZBR) │
│ Outer zone: ~2000 sectors │
│ Inner zone: ~1000 sectors │
│ Sector size: 512B (512e) hoặc │
│ 4096B (4Kn) │
│ │
│ Cache: 256 MB DRAM │
│ Recording: CMR (PMR) │
│ Areal density: ~1.08 Tbit/in² │
│ │
│ Sustained rate: ~250-270 MB/s │
│ (outer zone) │
│ ~150-170 MB/s │
│ (inner zone) │
│ Average seek: 8-9 ms (read) │
│ 9-10 ms (write) │
│ Rotational lat: 4.17 ms average │
│ (= 60/7200/2 sec) │
└─────────────────────────────────────┘
7.2 Quá trình ghi vật lý chi tiết
Khi HDD controller nhận lệnh WRITE cho 1 shard (~699 KB):
STEP 1: Actuator Seek
━━━━━━━━━━━━━━━━━━━━
- Voice coil motor (VCM) nhận dòng điện → tạo từ trường
- Từ trường tương tác với nam châm vĩnh cửu → đẩy actuator arm
- Arm pivot quanh trục, đưa head assembly tới track đích
- Seek time phụ thuộc khoảng cách:
- Short seek (vài track): ~1-2 ms
- Average seek (1/3 stroke): ~8-9 ms
- Full stroke (inner→outer): ~15-20 ms
- Servo tracking: head đọc servo marks (embedded trên mỗi track)
để fine-tune vị trí → track following
STEP 2: Rotational Latency
━━━━━━━━━━━━━━━━━━━━━━━━━━
- Platter quay 7200 RPM = 120 vòng/giây = 1 vòng/8.33 ms
- Average rotational latency = 8.33/2 = 4.17 ms
- Chờ sector bắt đầu quay tới dưới write head
STEP 3: Data Encoding
━━━━━━━━━━━━━━━━━━━━
- HDD controller chip (System-on-Chip) xử lý:
a) CRC/ECC calculation: mỗi sector 4096 bytes → thêm ~100 bytes ECC
(LDPC — Low-Density Parity-Check code trên HDD enterprise hiện đại)
b) Scrambling: XOR data với pseudo-random sequence để tránh
long runs of same bit → tốt cho magnetic recording
c) RLL encoding (Run-Length Limited):
- Đảm bảo tỉ lệ "0" liên tiếp không quá dài
- Giúp clock recovery circuit duy trì đồng bộ
- Tăng bit density trên track
STEP 4: Magnetic Write
━━━━━━━━━━━━━━━━━━━━━━
- Write head: thin-film inductive head (Perpendicular Magnetic Recording - PMR)
- PMR: từ trường ghi VUÔNG GÓC với bề mặt platter
→ bit density cao hơn longitudinal recording
- Write current: ~40-60 mA tạo từ trường ~1 Tesla tại gap
- Từ trường flip hướng từ hóa của magnetic grains trên platter
- Mỗi bit = 1 grain cluster (~20-50 grains)
- Bit size: ~20-30 nm (perpendicular direction)
- Track pitch: ~50-70 nm
┌──────────────────────────────────────────────────────────┐
│ Write Head Detail │
│ │
│ ┌─── Write pole (narrow, focused field) │
│ │ │
│ ~~~~│~~~~ gap ← từ trường tập trung tại gap │
│ │ │
│ └─── Return pole (wide, diffuse field) │
│ │
│ ═══════════════════════════════════════════════════ │
│ ↑↑↓↓↑↑↑↓↓↑↓ ← magnetic domains trên platter │
│ (mỗi ↑↓ = 1 bit transition) │
│ │
│ Platter coating: CoCrPt alloy thin film (~15-20nm) │
│ Substrate: aluminum or glass │
└──────────────────────────────────────────────────────────┘
STEP 5: Write Verify (optional, thường OFF cho performance)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- Enterprise HDD có thể verify write bằng read-after-write
- Thường disabled vì tốn thêm 1 revolution (~8ms)
- Thay vào đó, dựa vào ECC khi read
Với 699 KB data (giả sử sector 4KB):
699 KB / 4 KB = ~175 sectors
Nếu contiguous trên cùng tracks:
- Sectors/track (outer zone) ≈ 2000 × 4KB = 8 MB/track
- 699 KB < 1 track → ghi xong trong < 1 revolution
- Write time: ~0.5 ms (699KB / 150 MB/s inner hoặc 699KB / 250 MB/s outer)
Total: seek (~9ms) + rotational (~4ms) + write (~0.5ms) ≈ 13.5 ms
7.3 Zone Bit Recording (ZBR)
┌─────────────────────────────────────────────────┐
│ Platter (top view) │
│ │
│ Inner zones │
│ (fewer sectors/track) ┌───┐ │
│ ~1000 sectors/track │ │ │
│ ~150 MB/s │ │ │
│ ┌──────────────┐ │ │ │
│ │ │ │ │ Spindle │
│ │ Zone 0 │ │ │ │
│ │ (inner) │ │ │ │
│ │ │ └───┘ │
│ └──────────────┘ │
│ ┌──────────────────┐ │
│ │ Zone 5 │ │
│ │ (middle) │ │
│ │ ~1500 sect/trk │ │
│ │ ~200 MB/s │ │
│ └──────────────────┘ │
│ ┌──────────────────────┐ │
│ │ Zone 10 │ │
│ │ (outer) │ │
│ │ ~2000 sectors/track │ │
│ │ ~250 MB/s │ │
│ └──────────────────────┘ │
│ │
└─────────────────────────────────────────────────┘
Tại sao outer tracks nhanh hơn?
- RPM constant → linear velocity ở outer track > inner track
- Circumference outer > inner (2πr, r lớn hơn)
- Cùng RPM, head "quét" qua nhiều bits hơn mỗi giây ở outer track
- → ZBR cho phép nhiều sectors hơn ở outer tracks
- → Data transfer rate outer zone ~1.5-1.7× inner zone
7.4 Write Cache & FUA
Write cache HDD enterprise (256MB DRAM):
Write arrives → HDD controller
│
├─→ Write Cache ENABLED (default cho enterprise):
│ 1. Data copy vào DRAM cache → immediate ACK tới host
│ 2. Controller flush cache → platter (lazy, batched)
│ Nguy hiểm: power loss = data loss nếu không flush
│
├─→ FUA (Force Unit Access) bit set:
│ 1. Data phải hit platter trước khi ACK
│ 2. Bypass write cache (hoặc write-through)
│ 3. Chậm hơn: phải đợi seek + rotational + physical write
│
└─→ Cache FLUSH command (FLUSH CACHE EXT):
1. Force tất cả dirty data trong cache → platter
2. ACK sau khi tất cả flushed
Ceph BlueStore và FUA:
- BlueStore dùng O_DIRECT (bypass OS page cache) nhưng không nhất thiết dùng FUA trên mọi write.
- BlueStore dùng fdatasync() sau WAL writes → đảm bảo WAL durable.
fdatasync()triggers FLUSH CACHE command tới HDD → tất cả data pending trong HDD DRAM cache flush xuống platter.- BlueStore config
bluestore_sync_submit_transaction: nếu true → sync commit; nếu false → async commit rồi fdatasync. - Enterprise HDD với power-loss protection (capacitor): có thể enable write cache mà vẫn safe — capacitor cung cấp đủ năng lượng để flush cache khi mất điện (~5-10ms worth of power).
7.5 Enterprise HDD Power Loss Protection
┌─────────────────────────────────────────┐
│ Enterprise HDD (Exos X16) │
│ │
│ ┌──────────┐ ┌──────────────────┐ │
│ │ DRAM │ │ Capacitor Array │ │
│ │ Cache │←───│ (super capacitor)│ │
│ │ 256 MB │ │ ~1-5 F │ │
│ └────┬─────┘ │ ~3-5V │ │
│ │ │ │ │
│ │ │ Khi mất điện: │ │
│ │ │ cấp năng lượng │ │
│ │ │ để flush cache │ │
│ │ │ (~5ms đủ flush │ │
│ │ │ 256MB @ ~50GB/s │ │
│ │ │ internal rate) │ │
│ ▼ └──────────────────┘ │
│ ┌──────────┐ │
│ │ Platters │ │
│ └──────────┘ │
│ │
│ → Ceph CAN enable write cache vì │
│ data loss risk mitigated │
└─────────────────────────────────────────┘
TẦNG 8 — Index Pool Path (Song song với Data Path)
8.1 Sơ đồ Index Pool Repl-3 trên SSD
Index Pool Replication (3×) trên SSD:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
RGW librados aio_operate(bucket_index_obj)
│
▼
CRUSH → chọn 3 SSD OSDs từ 3 nodes khác nhau
│
▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Node-2 │ │ Node-5 │ │ Node-7 │
│ SSD OSD.194│ │ SSD OSD.200│ │ SSD OSD.204│
│ (PRIMARY) │ │ (SECONDARY) │ │ (SECONDARY) │
│ │ │ │ │ │
│ ┌────────┐ │ │ ┌────────┐ │ │ ┌────────┐ │
│ │Bucket │ │ │ │Bucket │ │ │ │Bucket │ │
│ │Index │ │ │ │Index │ │ │ │Index │ │
│ │Object │ │ │ │Object │ │ │ │Object │ │
│ │(replica│ │ │ │(replica│ │ │ │(replica│ │
│ │ #1) │ │ │ │ #2) │ │ │ │ #3) │ │
│ └────────┘ │ │ └────────┘ │ │ └────────┘ │
│ │ │ │ │ │
│ SSD 2TB │ │ SSD 2TB │ │ SSD 2TB │
│ NVMe/SATA │ │ NVMe/SATA │ │ NVMe/SATA │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ Write latency: │ │
│ ~100-200 µs │ │
│ (NVMe: ~50 µs) │ │
▼ ▼ ▼
Primary ghi local Primary gửi Primary gửi
+ repl tới 2 data tới Sec-1 data tới Sec-2
secondaries ghi local ghi local
ACK → Primary ACK → Primary
Khi cả 3 committed → ACK về RGW
8.2 BlueStore trên SSD
SSD OSD BlueStore khác HDD OSD:
- WAL + DB + Data đều trên cùng SSD: không cần tách vì SSD random I/O performance tốt.
- min_alloc_size = 4 KB (vs 64 KB trên HDD): SSD tốt với small writes.
- RocksDB performance: compaction nhanh vì random write trên SSD ~50µs vs HDD ~12ms.
- No seek penalty: mọi access pattern đều ~50-200µs.
8.3 Bucket Index Sharding
Với 1 TiB multipart upload → ~9,800 parts:
Vấn đề: Nếu bucket index là 1 single RADOS object → tất cả part completions ghi vào cùng 1 object → lock contention → serial writes → bottleneck.
Giải pháp — rgw_bucket_index_max_aio và bucket index sharding:
bucket_index_max_aiomặc định = 128: tối đa 128 concurrent index operations.- Index sharding:
rgw_override_bucket_index_max_shards(mặc định: thường 1 hoặc auto). - Mỗi bucket index có thể chia thành N shards (ví dụ 16 hoặc 32).
- Object name hash % N → xác định shard nào.
- Mỗi shard = 1 RADOS object riêng → phân tán lock contention.
- Với 9,800 parts và 16 shards → ~612 entries/shard → manageable.
Dynamic resharding (mặc định ON từ Luminous):
- Khi 1 shard vượt
rgw_max_objs_per_shard(mặc định 150,000) → tự động split. - Với ~9,800 entries: chưa cần reshard.
8.4 Latency Comparison
Latency cho 1 RADOS object write:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Index Write (SSD, repl-3):
Network client→primary: ~0.1 ms
Primary BlueStore SSD: ~0.2 ms
Replicate to 2 secondaries:
Network: ~0.1 ms
Secondary BlueStore SSD: ~0.2 ms each (parallel)
─────────────────────────────────
Total index write: ~0.5-0.8 ms
Data Write (HDD, EC 6+2):
Network client→primary: ~0.1 ms
EC encode (ISA-L): ~0.3 ms
Ship 7 shards (parallel): ~0.1 ms network
8× BlueStore HDD: ~10-15 ms each (parallel, bottleneck)
─────────────────────────────────
Total data write: ~12-18 ms
RGW đợi cái nào?
→ RGW đợi CẢ HAI hoàn thành
→ Data write (HDD) LUÔN chậm hơn index write (SSD) ~20-30×
→ Bottleneck = HDD data write
→ Index write "free" (hoàn thành trong khi đợi data write)
Bảng số liệu tổng hợp
| Metric | Giá trị |
|---|---|
| File size | 1 TiB = 1,099,511,627,776 bytes |
| Multipart part size (auto-adjusted) | ~107 MiB |
| Số parts | ~9,799 parts (part cuối nhỏ hơn) |
| Số RADOS objects (4 MiB stripe) | 262,144 objects |
| Số shards ghi xuống HDD | 262,144 × 8 = 2,097,152 shards |
| Kích thước mỗi shard | ~699 KB (682.67 KiB) |
| Tổng bytes ghi HDD (tất cả nodes) | 1.333 TiB ≈ 1,466 GiB |
| Bytes ghi mỗi node (8 nodes) | ~183.3 GiB |
| Bytes ghi mỗi HDD OSD (ước lượng) | ~7.1 GiB (phân bố đều 24 HDD/node) |
| Số index entries (bucket index) | ~9,800 entries (1 per part) |
| Tổng index data (raw) | ~2.8 MiB |
| Tổng index data (repl-3) | ~8.4 MiB trên SSD |
| Write latency per RADOS object | ~12-18 ms (HDD bottleneck) |
| Estimated end-to-end upload time | ~25-45 phút (xem phân tích dưới) |
| Throughput bottleneck | HDD write IOPS + tail latency (slowest shard among 8) |
Ước tính thời gian upload end-to-end
Per-RADOS-object write latency: ~15 ms (average)
Số RADOS objects: 262,144
Nếu sequential (1 object at a time): 262,144 × 15 ms = 3,932 s ≈ 65 min
Nhưng RGW pipeline CONCURRENT writes:
- 10 parts upload song song (CLI concurrency)
- Mỗi part = 27 stripes, pipeline liên tục
- Effective concurrency: ~10-20 RADOS writes đồng thời
- RADOS objects phân tán nhiều PGs → nhiều primary OSDs → parallel
Effective throughput = f(HDD aggregate bandwidth):
- Mỗi HDD: ~150-200 MB/s sequential write
- 8 nodes × ~5-10 HDD active (not all 24): ~8-16 GB/s aggregate
- Nhưng bị giới hạn bởi: network, RGW CPU, EC encoding
- Realistic cluster throughput cho 1 client: ~500 MB/s - 2 GB/s
1 TiB / 1 GB/s ≈ 1,024 seconds ≈ 17 phút
1 TiB / 500 MB/s ≈ 2,048 seconds ≈ 34 phút
Estimated: ~20-40 phút (phụ thuộc network, HDD load, cluster health)
Failure Scenario: 1 HDD OSD chết giữa chừng
Kịch bản
Node-4, OSD.w (chứa shard 3 — data D3) bị fail khi đã ghi được 60% file (khoảng 157,000/262,144 RADOS objects).
Diễn biến chi tiết
Timeline:
━━━━━━━━
T=0: OSD.w trên Node-4 ngừng respond (HDD fail, kernel I/O error)
T=~5s: OSD.w heartbeat miss → peers (OSDs lân cận) báo MON
(osd_heartbeat_interval = 6s, osd_heartbeat_grace = 20s)
T=~20s: MON đánh dấu OSD.w = DOWN sau khi vượt grace period
MON cập nhật OSDMap epoch++
Broadcast OSDMap mới tới tất cả OSDs và clients
T=~20s: Tất cả PGs có shard trên OSD.w chuyển trạng thái:
active+clean → active+degraded
(thiếu 1 shard nhưng vẫn có đủ k=6 data + m-1=1 parity
= vẫn đủ để đọc/ghi)
T=~20s: CRUSH remap: mỗi PG bị ảnh hưởng chọn 1 OSD mới từ Node-4
(hoặc node khác nếu Node-4 down hoàn toàn)
→ PG: active+degraded+remapped
T=~21s: Peering: primary OSD + OSDs mới trao đổi PG log
→ xác định objects nào thiếu shard
T=~30s: Recovery bắt đầu:
- Với EC 6+2: cần đọc k=6 shards bất kỳ (từ 7 OSD còn sống)
- Decode (Reed-Solomon): giải hệ 6 phương trình GF(2⁸)
để reconstruct shard bị mất
- Ghi shard mới tới OSD thay thế
- Recovery throttled: osd_recovery_max_active (mặc định 3)
→ tối đa 3 PGs recovery song song per OSD
Impact tới write đang diễn ra
Objects đã ghi TRƯỚC khi OSD.w die:
→ Tất cả 8 shards đã committed → an toàn (durable)
→ Recovery sẽ re-create shard bị mất lên OSD mới
Objects ĐANG ghi lúc OSD.w die:
→ Primary OSD đợi ACK từ OSD.w → timeout
→ Primary retry / fail op → RGW nhận error → retry
→ Sau OSDMap update: PG remapped → write op retry
tới OSD mới trong PG → thành công
Objects CHƯA ghi:
→ RGW tiếp tục ghi bình thường
→ PG active+degraded: write chỉ cần k+m-1=7 shards
(thiếu 1 shard, EC pool vẫn accept writes nhưng
reduced redundancy: chỉ chịu thêm 0 failures
thay vì 1 nếu m=2 → m_effective=1 khi degraded)
Recovery toán học
Mất shard 3 (D3). Còn: D0, D1, D2, D4, D5, P0, P1 (7 shards, cần 6)
Chọn 6 shards bất kỳ, ví dụ: D0, D1, D2, D4, D5, P0
Encoding matrix submatrix (6×6):
[ 1 0 0 0 0 ] [ D0 ] [ D0 ]
[ 0 1 0 0 0 ] [ D1 ] [ D1 ]
[ 0 0 1 0 0 ] × [ D2 ] = [ D2 ]
[ 0 0 0 1 0 ] [ D4 ] [ D4 ]
[ 0 0 0 0 1 ] [ D5 ] [ D5 ]
[ α⁰ α¹ α² α⁴ α⁵] [ P0' ] ← P0 without D3 contribution
→ Giải hệ: D3 = (P0 ⊕ α⁰·D0 ⊕ α¹·D1 ⊕ α²·D2 ⊕ α⁴·D4 ⊕ α⁵·D5) / α³
Thực tế: invert submatrix × received_shards → recover lost shard
ISA-L ec_encode_data() thực hiện nhanh (~10-20 GB/s throughput)
Recovery time ước tính
Data cần recovery trên OSD.w: ~7.1 GiB shards (portion of 262,144 shards)
Recovery = đọc 6 shards + decode + ghi 1 shard mới
Recovery throughput (per PG):
- Đọc 6 × 699KB từ 6 OSDs (parallel): ~10-15 ms (HDD read)
- Decode: ~0.05 ms
- Ghi 699KB tới new OSD: ~10-15 ms
- Per-object: ~20-30 ms
Nhưng throttled: osd_recovery_max_active = 3 PGs
- Mỗi PG có ~128 objects
- Recovery 1 PG: 128 × 25 ms = 3.2 seconds
- 3 PGs parallel → effective PG throughput: ~1 PG/second
- Total PGs affected: depends on pg_num mapping to OSD.w
(~2048 PGs, mỗi PG trải 8 OSDs, OSD.w host 1 shard per PG)
→ OSD.w involved in: 2048 × (1/8) ≈ 256 PGs (ước tính)
Thực tế: mỗi OSD host nhiều PGs.
Với 24 HDD OSDs/node, ~2048 PGs / 192 OSDs × 8 shards ≈
mỗi HDD OSD host ~85 PGs
→ Recovery 85 PGs × 3.2s/PG / 3 parallel ≈ 91 seconds ≈ 1.5 phút
(Nhưng trong thực tế recovery background bị throttle để không
ảnh hưởng client I/O → có thể 5-15 phút)
Trade-off Analysis: Tại sao mỗi quyết định thiết kế?
EC k=6, m=2 vs Replication 3×
| Aspect | EC 6+2 | Repl 3× |
|---|---|---|
| Storage overhead | 1.33× | 3.0× |
| Raw capacity 1 TiB | 1.33 TiB | 3.0 TiB |
| Failure tolerance | 2 OSD failures | 2 OSD failures |
| Write latency | Higher (encode + scatter 8 shards) | Lower (just replicate) |
| Read latency | Higher (need k shards) | Lower (any 1 replica) |
| CPU overhead | EC encoding (ISA-L mitigates) | Negligible |
| Phù hợp | Large objects, cold/warm storage | Hot data, small random I/O |
Tại sao EC cho data pool? File 1 TiB = cold/warm data, sequential access. EC tiết kiệm 2.25× storage cost. Latency overhead không quan trọng cho large sequential writes.
Index trên SSD vs HDD
| Aspect | Index trên SSD | Index trên HDD |
|---|---|---|
| Latency mỗi index op | ~0.2 ms | ~12 ms |
| Bucket listing 10K objects | ~50 ms | ~2,000 ms |
| Concurrent write contention | Manageable (SSD handles parallel) | Severe (HDD seek storm) |
| Cost | +2 SSD × 8 nodes = 16 SSDs | Saved |
| Lý do chọn | Critical for RGW performance | Only for extreme budget constraints |
Replication 3× cho Index vs EC
Index objects nhỏ (KB-range), random access, read-heavy (bucket listing). EC penalty cho small random reads quá lớn (cần đọc k shards). Replication 3× cho phép đọc từ bất kỳ 1 replica → low latency.
rgw_obj_stripe_size = 4 MiB
- Quá nhỏ (ví dụ 64KB): hàng triệu RADOS objects → metadata overhead explosion, PG overload.
- Quá lớn (ví dụ 64MB): partial overwrites tốn kém (phải rewrite toàn bộ stripe), memory buffer lớn.
- 4 MiB: cân bằng giữa metadata overhead và granularity. EC encode 4 MiB nhanh (~0.3ms). Shard size ~699KB phù hợp HDD block I/O.
Failure domain = node (không phải rack)
Với chỉ 8 nodes (1 rack?), dùng failure domain = node đảm bảo mất 1 node = mất tối đa 1 shard per PG → vẫn trong khả năng chịu lỗi m=2. Nếu dùng failure domain = rack mà chỉ có 1 rack → không tận dụng được. Node-level là hợp lý nhất.
HDD min_alloc_size = 64KB
- SSD: 4KB OK (flash page aligned).
- HDD: 64KB tối ưu — nhỏ hơn gây fragmentation, HDD cần sequential I/O ≥ track sector size để đạt bandwidth tốt. 64KB = ~16 sectors (4K) = đủ lớn để HDD sequential throughput tốt mà không waste space quá nhiều cho small objects.