Elasticsearch 6 — Cluster, Shard & mở rộng
Elasticsearch 6 — Cluster, Shard & mở rộng
Bốn bài trước nói về mô hình dữ liệu, Query DSL, Aggregations và nạp dữ liệu — tức là góc nhìn của người dùng ES như một dịch vụ. Bài này lật xuống dưới: ES thực chất là một hệ phân tán, và mọi tính chất tốt (HA, mở rộng ngang, near-real-time) lẫn mọi sự cố kinh điển (split-brain, cluster vàng/đỏ, đĩa đầy vì shard, search chậm vì over-sharding) đều bắt nguồn từ cách nó tổ chức node và shard.
Với một team dữ liệu ngân hàng, đây là phần quyết định chi phí và độ tin cậy: cụm log giao dịch phải giữ 13 tháng (yêu cầu tra soát/đối chiếu và một số quy định lưu trữ), nghĩa là hàng chục TB dữ liệu chỉ-đọc-nhiều-ghi-một-lần. Không thể nhét tất cả vào ổ SSD đắt tiền, cũng không được để một node chết là mất log. Bài này đi từ node & vai trò, đến cluster state & bầu master, xuống shard & routing, cách chọn số shard, rebalancing, rồi ILM (hot-warm-cold-frozen) và snapshot — công cụ để vận hành cụm 13 tháng vừa rẻ vừa an toàn.
Node và vai trò
Một cluster là tập các node (mỗi node thường là một tiến trình ES trên một máy/container) cùng chung cluster.name, tự tìm nhau qua discovery. Mỗi node có thể mang một hoặc nhiều vai trò (cấu hình bằng node.roles):
| Vai trò | Nhiệm vụ | Ghi chú |
|---|---|---|
| master (master-eligible) | Quản lý cluster state: tạo/xoá index, phân bổ shard, theo dõi node vào/ra. Chỉ một node là master đang hoạt động tại một thời điểm. | Không giữ dữ liệu (nếu tách vai trò). Cần ít, ổn định. |
| data | Lưu shard, thực thi index/search/aggregation. Đây là node "nặng" nhất. | Có thể chia theo tier: data_hot, data_warm, data_cold, data_frozen. |
| ingest | Chạy ingest pipeline (grok/date/geoip...) trước khi ghi. | Có thể trùng với data node ở cụm nhỏ. |
| coordinating | Nhận request client, phát tán tới shard liên quan, gộp kết quả trả về. | Mọi node đều làm được; node "coordinating-only" (bỏ hết vai trò khác) dùng như load balancer nội bộ. |
| ml | Chạy machine learning job (anomaly detection...). | Tính năng thương mại; cụm cơ bản có thể bỏ. |
Ở cụm nhỏ (1–3 node), một node thường kiêm hết vai trò. Khi lớn lên, tách master ra khỏi data là bước quan trọng đầu tiên: master phải luôn phản hồi để quản trị cluster state, nếu nó vừa gánh query nặng vừa làm master thì một truy vấn aggregation "khủng" có thể làm nghẽn cả cluster.
Cluster state & bầu master (quorum, split-brain)
Cluster state là "bản đồ" toàn cục: danh sách index, mapping, setting, và shard nào nằm ở node nào. Chỉ master node đang hoạt động được sửa cluster state; mọi node khác giữ một bản sao. Khi thay đổi (thêm index, di chuyển shard), master phát bản state mới tới toàn cluster.
Vì chỉ được có một master tại một thời điểm, ES phải bầu master khi cluster khởi động hoặc khi master hiện tại biến mất. Cơ chế bầu dựa trên quorum (đa số): một master mới chỉ được công nhận khi có đa số node master-eligible đồng thuận.
Split-brain là kịch bản kinh điển phải tránh: mạng bị chia đôi, mỗi nửa tự bầu một master riêng, cả hai cùng nhận ghi → cluster state phân kỳ, dữ liệu hỏng khi mạng lành lại. Quorum ngăn điều này: nếu cần đa số để bầu, thì tối đa một nửa mạng có thể đạt đa số, nửa còn lại không bầu được master và tự đứng đọc-only.
Đây là lý do số node master-eligible nên là số lẻ (thường 3, cụm lớn dùng 5):
- 3 node → quorum = 2, chịu mất 1 node vẫn hoạt động.
- 5 node → quorum = 3, chịu mất 2 node.
- Số chẵn không tốt hơn số lẻ liền dưới nó: 4 node → quorum = 3, cũng chỉ chịu mất 1 (như 3 node) nhưng tốn thêm một máy và tăng xác suất lỗi. 2 node → quorum = 2, mất 1 là mất luôn cluster.
Từ ES 7 trở đi, quorum được quản lý tự động qua khái niệm voting configuration (ES tự điều chỉnh tập node có quyền bầu), không còn tham số minimum_master_nodes phải chỉnh tay như thời ES 5/6 — nhưng nguyên tắc "số master-eligible lẻ, tối thiểu 3 cho production" vẫn nguyên giá trị. Lần đầu khởi động cụm mới cần khai báo cluster.initial_master_nodes để ES biết tập bầu ban đầu (chỉ dùng đúng lần bootstrap đầu tiên).
Shard: primary và replica
Một index không lưu nguyên khối. Nó được chia thành các shard, mỗi shard là một Lucene index độc lập — đơn vị nhỏ nhất mà ES phân tán và mở rộng. Có hai loại:
- Primary shard: bản gốc. Số primary (
number_of_shards) cố định khi tạo index, không đổi được sau đó (muốn đổi phải reindex hoặc dùng split/shrink API). - Replica shard: bản sao của một primary (
number_of_replicas, đổi được lúc chạy). Replica phục vụ hai mục đích:- HA (cao khả dụng): primary chết thì replica được promote thành primary. ES không bao giờ đặt primary và replica của cùng một shard trên cùng một node — mất một node vẫn còn bản sao.
- Tăng throughput đọc: search có thể chạy trên primary hoặc replica, nên thêm replica = thêm bản sao phục vụ đọc song song. (Nhưng replica không giúp ghi nhanh hơn — ngược lại, mỗi ghi phải nhân bản sang tất cả replica.)
Ví dụ index logs-txn-2026.06 với number_of_shards: 3, number_of_replicas: 1 → 3 primary + 3 replica = 6 shard vật lý, trải trên tối thiểu 2 node.
PUT /logs-txn-2026.06
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
}
}
Sơ đồ phân bổ (minh hoạ) trên cụm 3 data node, mỗi ô là một shard, P=primary, R=replica:
Mất data-2 (chứa P1 + R2): ES promote R1→primary (trên data-1) và R2→ đợi tái tạo; cluster vẫn phục vụ đọc/ghi vì không shard nào mất cả primary lẫn replica.
Routing: document nào vào shard nào
Khi index một document, ES phải quyết định nó thuộc primary shard nào. Mặc định dùng hash routing:
shard = hash(_routing) % number_of_primary_shards
trong đó _routing mặc định là _id. Vì number_of_primary_shards nằm trong công thức, không thể đổi số primary sau khi tạo index (đổi sẽ khiến routing tính ra shard khác → không tìm lại được document cũ). Đó chính là lý do primary cố định còn replica thì đổi thoải mái.
Hai hệ quả thực tế:
- Phân bố đều: hash trên
_idrải document tương đối đều các shard → tránh "hot shard". Với log giao dịch dùngtxn_idlàm_id, dữ liệu tự cân bằng. - Custom routing: có thể ép các document liên quan vào cùng shard bằng
?routing=<key>(ví dụ routing theoaccount_nođể mọi giao dịch của một tài khoản nằm cùng shard → search theo tài khoản chỉ chạm 1 shard, rất nhanh). Đánh đổi: nếu một key có quá nhiều document sẽ tạo shard lệch. Với ngân hàng, routing theobranch_codehoặcaccount_nolà kỹ thuật tối ưu đáng cân nhắc khi truy vấn luôn kèm khoá đó.
Truy vấn ngược lại: coordinating node phát query tới tất cả shard liên quan (scatter), mỗi shard trả top-N cục bộ, coordinating gộp và xếp hạng lại (gather) — nên số shard càng nhiều thì mỗi query càng tốn công điều phối.
Chọn số shard: bẫy over-sharding
Đây là quyết định thiết kế quan trọng và dễ sai nhất. Hai thái cực đều xấu:
- Quá nhiều shard nhỏ (over-sharding): mỗi shard là một Lucene index, tốn RAM heap (field data, segment metadata), tốn file handle, và mỗi query phải điều phối qua nhiều shard hơn. Cụm log hay mắc lỗi này: tạo index theo ngày với 5 shard, giữ 13 tháng → ~2.000 shard chỉ cho một luồng log, phần lớn rỗng hoặc bé tí. Master ngập trong cluster state, GC dài, cluster ì ạch.
- Quá ít shard, mỗi shard quá to (>50–60 GB): shard lớn khó di chuyển khi rebalancing (chuyển 200 GB qua mạng rất lâu), recovery sau lỗi chậm, và một shard không thể chia đôi để chạy song song trên 2 node → hạn chế mở rộng.
Kích thước khuyến nghị của Elastic: mỗi shard khoảng 10–50 GB cho log/time-series (nhiều nơi nhắm ~30–50 GB). Quy tắc ngón tay cái thứ hai: giữ tổng số shard trên mỗi GB heap ≤ ~20 (ví dụ node heap 30 GB → cố gắng dưới ~600 shard/node).
Cách tính thực dụng cho log time-series: ước lượng tổng dung lượng một chu kỳ rollover, chia cho kích thước shard mục tiêu.
Ví dụ minh hoạ (KHÔNG phải số thật):
- 1,8 triệu doc/ngày, mỗi doc ~1,2 KB sau index → ~2,2 GB/ngày (primary).
- Rollover mỗi 30 GB → khoảng 14 ngày/index.
- Muốn ~30 GB/shard → number_of_shards = 1 (một index ≈ một shard) là đủ.
- 13 tháng ≈ 28 index rollover × (1 primary + 1 replica) = 56 shard. Rất gọn.
So sánh với thiết kế "1 index/ngày × 5 shard" = ~2.000 shard cho cùng khối lượng: chênh nhau ~35 lần số shard cho cùng dữ liệu. Đây là lý do data stream + rollover theo kích thước (nói ở dưới) thắng tuyệt đối so với "index theo ngày, shard cố định".
Rebalancing & allocation
ES tự động cân bằng shard giữa các data node để phân tán tải và dung lượng. Vài cơ chế bạn cần biết để không bị "cluster vàng" khó hiểu:
- Allocation awareness: khai báo
node.attr.zone(rack/AZ) rồi bậtcluster.routing.allocation.awareness.attributes: zone→ ES tránh đặt primary và replica cùng một zone. Với ngân hàng chạy 2 trung tâm dữ liệu, đây là cách đảm bảo mất một DC vẫn còn bản sao. - Disk watermark: ES ngừng phân shard mới lên node vượt
cluster.routing.allocation.disk.watermark.low(mặc định 85%), di chuyển shard đi khi vượthigh(90%), và chuyển index sang read-only khi vượtflood_stage(95%). Đây là nguyên nhân phổ biến nhất khiến "đột nhiên không ghi được log": đĩa gần đầy. - Shard allocation filtering: dùng
index.routing.allocation.require.data: hot|warm|coldđể buộc index nằm trên tier tương ứng — nền tảng của ILM di chuyển dữ liệu qua các tier.
Index Lifecycle Management (ILM)
Với dữ liệu time-series giữ lâu (log 13 tháng), không hợp lý khi để mọi dữ liệu trên SSD đắt tiền với replica đầy đủ. ILM tự động chuyển index qua các phase theo tuổi/kích thước, mỗi phase gắn với một data tier phần cứng khác nhau:
| Phase | Đặc điểm | Phần cứng (tier) | Hành động điển hình |
|---|---|---|---|
| Hot | Đang ghi + đọc nhiều | SSD nhanh (data_hot) | rollover khi đủ size/tuổi |
| Warm | Chỉ đọc, đọc còn thường | HDD/SSD rẻ hơn (data_warm) | giảm replica, forcemerge gộp segment, shrink giảm shard |
| Cold | Đọc hiếm | HDD rẻ (data_cold) | có thể chuyển sang searchable snapshot |
| Frozen | Gần như không đọc | Object store + cache (data_frozen) | searchable snapshot — dữ liệu nằm trên S3/blob, chỉ mount khi cần |
| Delete | Hết hạn lưu | — | xoá index |
Rollover là hạt nhân: thay vì tự cắt index theo ngày, ta ghi vào một alias/data stream (ví dụ logs-txn), và ILM tự tạo index mới (logs-txn-000001, -000002...) khi index hiện tại đạt ngưỡng — theo kích thước (max_primary_shard_size: 30gb), số doc (max_docs), hoặc tuổi (max_age: 7d), điều kiện nào đến trước. Nhờ vậy mỗi shard luôn trong khoảng mục tiêu bất kể lưu lượng biến động — tránh cả over-sharding lẫn shard khổng lồ.
PUT /_ilm/policy/logs-txn-policy
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": { "max_primary_shard_size": "30gb", "max_age": "30d" }
}
},
"warm": {
"min_age": "30d",
"actions": {
"forcemerge": { "max_num_segments": 1 },
"set_priority": { "priority": 50 },
"allocate": { "number_of_replicas": 1, "require": { "data": "warm" } }
}
},
"cold": {
"min_age": "90d",
"actions": { "allocate": { "require": { "data": "cold" } } }
},
"frozen": {
"min_age": "180d",
"actions": { "searchable_snapshot": { "snapshot_repository": "s3-backup" } }
},
"delete": {
"min_age": "395d",
"actions": { "delete": {} }
}
}
}
}
Vòng đời ILM cho cụm log ngân hàng 13 tháng:
Data tiers ánh xạ trực tiếp phần cứng: node data_hot dùng NVMe đắt (ít, dung lượng nhỏ), data_warm/data_cold dùng HDD dung lượng lớn giá rẻ, data_frozen gần như không cần đĩa cục bộ (dữ liệu ở object store, chỉ cache). Với 13 tháng log, đại đa số dữ liệu (cold/frozen) nằm trên tầng rẻ — tiết kiệm chi phí lớn mà vẫn tra cứu được khi thanh tra yêu cầu.
Snapshot & restore (backup)
ILM delete là xoá theo vòng đời, không phải backup. Để phục hồi sau thảm hoạ (xoá nhầm index, hỏng cluster), ES dùng snapshot: sao lưu incremental (chỉ chép segment mới so với snapshot trước) vào một repository — thường là S3/GCS/Azure blob hoặc shared filesystem.
PUT /_snapshot/s3-backup
{
"type": "s3",
"settings": { "bucket": "ncb-es-backup", "base_path": "prod" }
}
PUT /_snapshot/s3-backup/snap-2026.06.30?wait_for_completion=false
{ "indices": "logs-txn-*", "include_global_state": false }
Điểm cần nhớ: snapshot là incremental và không chặn ghi; nên lập lịch snapshot tự động bằng SLM (Snapshot Lifecycle Management, họ hàng của ILM); và searchable snapshot (dùng ở frozen) thực chất là dữ liệu nằm luôn trong repository — vừa là backup vừa là dữ liệu đọc được, giảm mạnh chi phí lưu trữ dài hạn. Chi tiết vận hành backup/restore nằm trong phần Performance & Ops.
Health: xanh / vàng / đỏ
GET /_cluster/health trả về trạng thái tổng hợp — thứ đầu tiên phải nhìn khi có sự cố:
| Màu | Ý nghĩa | Ví dụ nguyên nhân |
|---|---|---|
| Green | Mọi primary và mọi replica đều được phân bổ. | Bình thường. |
| Yellow | Mọi primary OK, nhưng có replica chưa phân bổ. Dữ liệu không mất, đọc/ghi vẫn chạy, chỉ giảm HA/throughput đọc. | Cụm 1 node mà đặt replicas: 1 (không đủ node để đặt replica); một data node vừa restart; disk watermark chặn allocate. |
| Red | Có primary chưa phân bổ → thiếu dữ liệu, một số truy vấn lỗi/thiếu kết quả. | Mất node giữ primary mà không có replica; corrupt shard; đĩa đầy nghiêm trọng. |
Mẹo chẩn đoán: yellow là bình thường trên cụm 1 node (không có chỗ đặt replica — set number_of_replicas: 0 cho dev). Red mới là báo động; dùng GET /_cluster/allocation/explain để ES nói rõ vì sao một shard chưa được phân bổ (thường: watermark, thiếu tier phù hợp, hoặc node chứa primary đã biến mất).
GET /_cluster/health
{
"status": "yellow",
"number_of_nodes": 3,
"active_primary_shards": 56,
"active_shards": 105,
"unassigned_shards": 7
}
Lưu ý: các ví dụ trên là JSON API của Elasticsearch qua HTTP, không phải SQL — không có block nào ở đây chạy được trong sandbox PostgreSQL của Knowledge Base.
Use case thực tế
Bối cảnh. Team dữ liệu NCB vận hành cụm ES lưu log giao dịch 13 tháng phục vụ tra soát nghiệp vụ, giám sát giao dịch AML và điều tra sự cố. Khối lượng: ~1,8 triệu doc/ngày, ~2,2 GB/ngày sau index (số minh hoạ) → khoảng 0,8 TB/năm primary, gấp đôi khi tính replica.
Thiết kế cụm.
- 3 node master-eligible (tách riêng, máy nhỏ) → quorum 2, chịu mất 1 master. Trải trên 2 trung tâm dữ liệu, node thứ 3 làm tie-breaker → tránh split-brain khi đứt link giữa 2 DC.
- Data tiers: 3 node
data_hot(NVMe) giữ ~30 ngày nóng; 2 nodedata_warm(HDD) giữ 30–90 ngày; dữ liệu >180 ngày đẩy xuống frozen (searchable snapshot trên object store nội bộ) → chi phí đĩa cục bộ chỉ gánh ~3 tháng gần nhất thay vì 13 tháng. - Allocation awareness theo
zone(2 DC) → primary và replica luôn ở 2 DC khác nhau.
Vòng đời & sizing.
- Ghi qua data stream
logs-txn(từ Logstash_bulk, xem bài Indexing & nạp dữ liệu). - ILM rollover
max_primary_shard_size: 30gbhoặcmax_age: 30d→ mỗi backing index ~1 primary ~30 GB, đúng khoảng khuyến nghị. Cả năm ~28 index rollover → ~56 shard (thay vì ~2.000 nếu chia index theo ngày × 5 shard). - Warm:
forcemergevề 1 segment (đọc nhanh hơn, ít metadata), giữ replica 1.
Sự cố thực tế.
- Một data_hot node hỏng ổ NVMe → cluster chuyển yellow (replica chưa đủ), không mất log vì replica ở DC kia được promote; ES tự tái tạo replica lên node còn lại, ~40 phút sau về green.
- Có lần đĩa hot chạm watermark 90% do một đợt backfill → ES tự chuyển vài shard sang node khác và cảnh báo; team giãn TTL hot xuống và tăng ngưỡng rollover, tránh chạm
flood_stageread-only.
Kết quả. Tra cứu 3 tháng gần nhất phản hồi ~giây (hot/warm SSD-HDD); truy vấn dữ liệu 10–13 tháng trước (frozen) chậm hơn nhưng vẫn phục vụ được yêu cầu thanh tra; chi phí lưu trữ giảm rõ nhờ chỉ giữ dữ liệu nóng trên NVMe; xoá dữ liệu quá 13 tháng = ILM delete tự động, không thao tác tay. Cách tiếp cận phân tầng nóng/lạnh này cũng là nền tảng khi đối chiếu với kho dữ liệu lịch sử trong Lakehouse.
Ghi nhớ
- Node & vai trò: master (quản lý cluster state), data (giữ shard, chia theo tier hot/warm/cold/frozen), ingest (pipeline), coordinating (điều phối query — mọi node đều làm được), ml. Cụm lớn phải tách master khỏi data.
- Bầu master theo quorum (đa số) để tránh split-brain. Số node master-eligible phải lẻ, tối thiểu 3 cho production (3 → chịu mất 1; 5 → chịu mất 2). Số chẵn không lợi hơn số lẻ liền dưới.
- Shard = đơn vị phân tán. Primary cố định lúc tạo index (routing =
hash(_routing) % số_primary), replica đổi được lúc chạy — replica cho HA (promote khi primary chết) và tăng throughput đọc (không giúp ghi nhanh). Primary và replica cùng shard không bao giờ ở cùng node. - Chọn số shard: nhắm ~10–50 GB/shard. Tránh over-sharding (nhiều shard bé → tốn heap, master ì); tránh shard quá to (>50–60 GB → khó rebalance/recovery). Quy tắc ~≤20 shard/GB heap.
- Rebalancing/allocation: chú ý disk watermark (85/90/95% → dừng phân, di chuyển, read-only) là nguyên nhân hàng đầu "đột nhiên không ghi được"; allocation awareness để tách primary/replica theo zone/DC.
- ILM đưa index qua hot → warm → cold → frozen → delete theo tuổi/kích thước, gắn với data tiers phần cứng rẻ dần. Rollover theo
max_primary_shard_size/max_agegiữ shard đúng kích thước — cực hợp log/time-series 13 tháng. - Snapshot & restore (incremental, lên S3/blob) mới là backup thật; lập lịch bằng SLM; searchable snapshot dùng ở frozen vừa backup vừa đọc được.
- Health: green (đủ primary + replica) / yellow (thiếu replica, không mất dữ liệu — bình thường trên cụm 1 node) / red (thiếu primary → mất dữ liệu, báo động). Dùng
allocation/explainđể tìm nguyên nhân shard chưa phân bổ.
Bài viết liên quan
CSV/JSON/Parquet/Avro, lưu trữ theo hàng vs cột, nén, và OLTP vs OLAP.
Data Engineering là gì, vai trò trong vòng đời dữ liệu, và bức tranh hệ sinh thái công cụ.
Vì sao xử lý phân tán, mô hình Spark (RDD/DataFrame), lazy evaluation, shuffle và tối ưu.
Đảm bảo chất lượng & minh bạch dữ liệu bằng dbt: data tests dựng sẵn (unique, not_null, relationships, accepted_values), singular test, unit test, và tài liệu tự sinh kèm lineage graph.