Ceph Object Storage Deep Dive Series. Part 1
Kiến trúc Ceph RGW: Đi sâu vào những nền tảng cốt lõi
Giới thiệu: Cỗ máy stateless đầy sức mạnh
Ceph Object Gateway (RGW) không chỉ đơn thuần là một proxy; nó là một lớp abstraction bậc cao, cung cấp một cách liền mạch các RESTful API theo chuẩn Amazon S3 và OpenStack Swift nằm trên nền RADOS (Reliable Autonomic Distributed Object Store). Với các storage architect, RGW đóng vai trò then chốt vì nó dịch các HTTP request tiêu chuẩn cho những thao tác object thành các RADOS operation native, được thực thi trực tiếp trên cluster. Nhờ vậy, các ứng dụng vốn được viết cho những hệ sinh thái cloud object storage phổ biến có thể dùng một Ceph cluster làm storage backend mà không cần sửa đổi gì.
Một nguyên tắc nền tảng chi phối thiết kế của RGW là bản chất stateless của nó. Quyết định kiến trúc mang tính cốt lõi này chính là gốc rễ cho khả năng scale theo chiều ngang cực lớn và tính sẵn sàng cao. Vì các RGW daemon không lưu giữ bất kỳ state bền vững nào liên quan đến client session, bạn có thể đạt được mức tăng hiệu năng gần như tuyến tính chỉ bằng cách triển khai thêm các RGW instance phía sau một load balancer thông thường. Việc một RGW daemon bất kỳ chết đi không phải là sự cố nghiêm trọng, bởi load balancer sẽ điều hướng traffic của client sang các instance còn khỏe mạnh, khiến sự cố gần như trong suốt với end-user. Toàn bộ state quan trọng — bao gồm user metadata, bucket definition, ACL và cả object data — đều được lưu trữ bền vững bên trong RADOS cluster, ở các pool được chỉ định riêng.
Trong phần deep dive đầu tiên này, chúng ta sẽ bóc tách từng lớp để xem xét các thành phần frontend của RGW, những RADOS pool chuyên biệt chứa metadata nội bộ của nó, và cơ chế then chốt của bucket index cùng sharding — thứ giúp các thao tác object đạt hiệu năng cao.
RGW Frontends
Một request từ client đi vào RGW daemon sẽ đi qua nhiều lớp nội bộ, bắt đầu từ frontend web server chịu trách nhiệm xử lý HTTP connection ban đầu. Trong lịch sử, RGW hỗ trợ hai frontend nhúng chính: Civetweb — lựa chọn mặc định cũ, và Beast — frontend hiện đại, hiệu năng cao và là mặc định ngày nay.
Civetweb hoạt động theo mô hình đồng bộ, mỗi connection một thread. Ngược lại, Beast là một frontend hiện đại được xây dựng trên nền Boost. Thư viện Asio C++ cho phép mô hình I/O bất đồng bộ, hướng sự kiện (event-driven). Thay vì dành hẳn một thread cho mỗi connection, Beast dùng một pool nhỏ các worker thread để phục vụ đồng thời hàng nghìn connection. Mô hình này hiệu quả hơn hẳn về mặt sử dụng CPU và memory, vì các thread không bị block khi chờ I/O, đồng thời overhead bộ nhớ trên mỗi connection cũng giảm đi rất nhiều. Bước chuyển kiến trúc từ Civetweb sang Beast chính là câu trả lời trực tiếp cho nhu cầu của các ứng dụng cloud-native hiện đại, vốn thường tạo ra workload có độ đồng thời cao và IOPS lớn.
Cấu hình Frontend trong thực tế
Khi triển khai hoặc chỉnh sửa RGW service bằng cephadm, kiểu frontend và các thiết lập của nó có thể được khai báo trực tiếp trong file service specification. Beast là lựa chọn mặc định và được khuyến nghị cho RGW frontend:
service_type: rgw
service_id: myrealm.myzone
spec:
rgw_realm: myrealm
rgw_zone: myzone
ssl: true
rgw_frontend_port: 1234
rgw_frontend_type: beast
rgw_frontend_ssl_certificate: ...
Đoạn YAML này minh họa cách cephadm triển khai một RGW service: chỉ định realm và zone, bật SSL termination, và đặt tường minh rgw_frontend_type là beast trên TCP port 1234.
Tìm hiểu về các RGW RADOS Pool
Để RGW thực sự là một thành phần stateless, mọi thông tin quan trọng — user data, metadata và log — đều phải được lưu bền vững ở lớp RADOS. Điều này được hiện thực hóa thông qua một tập hợp các RADOS pool chuyên biệt, dành riêng cho từng mục đích.
Kiến trúc multi-pool của RGW là một lựa chọn thiết kế có chủ đích, cho phép người vận hành tách biệt về mặt vật lý các loại dữ liệu khác nhau lên những tầng phần cứng khác nhau, từ đó cân bằng tối ưu giữa hiệu năng và chi phí. Chẳng hạn, metadata và log vốn nhạy cảm với độ trễ có thể đặt trên các replicated pool nằm trên SSD. Trong khi đó, phần payload của object vốn ngốn dung lượng có thể nằm trên các erasure-coded pool dùng HDD chậm hơn nhưng rẻ hơn, hoặc ngày càng phổ biến hơn là SSD dòng QLC. NVMe SSD được ưu tiên hơn so với SSD SAS/SATA đời cũ vì chúng giúp đảm bảo tương lai lâu dài, mật độ tốt hơn và hiệu năng trên mỗi đồng chi phí cũng cao hơn. Thực tế, một server NVMe có thể còn rẻ hơn một server SATA.
Các RGW Pool chính và mục đích của chúng
| Hậu tố tên Pool | Mục đích | Data Protection thường dùng | Media khuyến nghị |
|---|---|---|---|
| .rgw.root | Lưu cấu hình RGW toàn cục (realm, zonegroup, zone) | Replicated | SSD |
| .rgw.control | Điều phối nội bộ giữa các RGW daemon | Replicated | SSD |
| .rgw.meta | Metadata của user và bucket | Replicated | SSD |
| .rgw.log | Log thao tác và log replication | Replicated | SSD |
| .rgw.buckets.index | Danh sách object của bucket (omap). Cực kỳ quan trọng với hiệu năng | Replicated | SSD |
| .rgw.buckets.data | Payload dữ liệu object chính | Erasure Coded | TLC/QLC SSD, HDD |
| .rgw.buckets.non-ec | Pool phụ trợ cho các thao tác không tương thích với EC | Replicated | SSD / HDD |
Khi RGW service lần đầu thao tác trên một RADOS pool chưa tồn tại, nó sẽ tự tạo pool đó với giá trị lấy từ các config option osd_pool_default_pg_num và osd_pool_default_pgp_num. Các giá trị mặc định này là đủ với một số pool, nhưng với những pool khác (đặc biệt là các pool được liệt kê trong placement_pools cho bucket index và data) thì cần được tinh chỉnh thêm. Lưu ý rằng khi bật PG autoscaler, nó sẽ tự động điều chỉnh số placement group cho các pool này, với BIAS cao hơn dành cho các pool .index để chúng được cấp nhiều PG hơn mức mà tổng lượng dữ liệu của chúng thông thường yêu cầu. Để autoscaler hoạt động tốt nhất với cả dàn pool của RGW, chúng tôi khuyến nghị nâng các giá trị sau lên so với mặc định:
# ceph config set global mon_target_pg_per_osd 300
# ceph config set global mon_max_pg_per_osd 600
Tên pool gắn với một RGW zone cụ thể tuân theo quy ước đặt tên zone-name.pool-name. Ví dụ, một zone tên us-east sẽ có các pool sau:
.rgw.root
us-east.rgw.control
us-east.rgw.meta
us-east.rgw.log
us-east.rgw.buckets.index
us-east.rgw.buckets.data
Cấu trúc của các pool này rất quan trọng để hiểu được cách RGW vận hành. Nhiều pool ở mức logic thực chất được gom chung lại bằng cách dùng RADOS namespace bên trong các RADOS pool chính (ví dụ default.rgw.log).
Chúng ta có thể liệt kê các RADOS namespace bằng câu lệnh dạng sau. Ở đây có thể thấy rgw.meta pool chứa ba RADOS namespace khác nhau:
# rados ls -p default.rgw.meta --all | awk '{ print $1 }' | sort -u
root
users.keys
users.uid
Các pool cùng namespace của chúng sẽ lộ ra khi ta truy vấn cấu hình RGW zone:
$ radosgw-admin zone get --rgw-zone default
{
"id": "d9c4f708-5598-4c44-9d36-849552a08c4d",
"name": "default",
"domain_root": "default.rgw.meta:root",
"control_pool": "default.rgw.control",
"gc_pool": "default.rgw.log:gc",
"lc_pool": "default.rgw.log:lc",
"log_pool": "default.rgw.log",
"intent_log_pool": "default.rgw.log:intent",
"usage_log_pool": "default.rgw.log:usage",
"roles_pool": "default.rgw.meta:roles",
"reshard_pool": "default.rgw.log:reshard",
"user_keys_pool": "default.rgw.meta:users.keys",
"user_email_pool": "default.rgw.meta:users.email",
"user_swift_pool": "default.rgw.meta:users.swift",
"user_uid_pool": "default.rgw.meta:users.uid",
"otp_pool": "default.rgw.otp",
...
"placement_pools": [
{
"key": "default-placement",
"val": {
"index_pool": "default.rgw.buckets.index",
"storage_classes": {
"STANDARD": {
"data_pool": "default.rgw.buckets.data"
}
},
"data_extra_pool": "default.rgw.buckets.non-ec",
"index_type": 0
}
}
],
"realm_id": "",
"notif_pool": "default.rgw.log:notif"
}
Output JSON này mô tả chi tiết cấu hình của zone default. Hãy để ý rằng rất nhiều chức năng logic khác nhau (GC, LC, usage log) đều được map vào cùng một RADOS pool là default.rgw.log` nhưng được tách biệt bằng RADOS Namespace (ví dụdefault.rgw.log:gc``).
Nhìn kỹ về Bucket Index và Sharding (#a-detailed-overview-of-the-bucket-index-and-sharding)
Khả năng liệt kê nội dung của một bucket là điều căn bản của object storage. RGW hiện thực điều này bằng một cấu trúc chuyên biệt gọi là Bucket Index, chịu trách nhiệm liệt kê nội dung bucket, duy trì journal cho các thao tác có versioning, lưu metadata về quota, và đóng vai trò log phục vụ đồng bộ giữa nhiều zone.
Bucket Index và OMAP (#the-bucket-index-and-omaps)
Bucket index dựa trên một tính năng đặc biệt của RADOS object gọi là Object Map (OMAP). OMAP là một kho key-value gắn với một RADOS object, về ý tưởng khá giống Extended Attribute trong hệ thống file POSIX. Với mỗi bucket, RGW tạo ra một hoặc nhiều index object riêng trong pool .rgw.buckets.index. Thông tin listing của các object nằm trong bucket đó được lưu bên trong OMAP của những index object này.
Điểm mấu chốt: hiệu năng của bucket index phụ thuộc hoàn toàn vào kho key-value bên dưới — OMAP được lưu trữ vật lý trong cơ sở dữ liệu RocksDB nằm trên phân vùng DB của OSD. Chính vì vậy, các index pool như `default.rgw.buckets.index hiện bắt buộc phải dùng cơ chế bảo vệ dữ liệu replicated, bởi các thao tác OMAP không tương thích với erasure-coded pool. Đầu tư vào thiết bị flash tốc độ cao (SSD, lý tưởng nhất là NVMe) cho phân vùng DB của OSD là yếu tố tối quan trọng đối với hiệu năng bucket listing. Các RGW index pool có thể chọn CRUSH rule để đặt chúng lên các OSD thuần SSD, hoặc lên các OSD hybrid với phần DB được offload sang SSD. Vì omap chỉ nằm ở phần DB của OSD, nên cách nào cũng đều đáp ứng được.
Tinh chỉnh Index Pool để tối ưu hiệu năng (#tuning-the-index-pool-for-performance)
Storage nhanh cho OSD DB là quan trọng, nhưng cách bucket index được phân bố trên toàn cluster cũng quan trọng không kém. Điều này được điều khiển bởi số lượng Placement Group (PG) của index pool. Tinh chỉnh PG kém là nguyên nhân phổ biến gây ra hiệu năng listing tệ, đặc biệt trong các cluster lớn.
Số lượng Placement Group (PG) và tính song song (#placement-group-(pg)-count-and-parallelism)
Mỗi PG được map tới một tập OSD, trong đó một OSD đóng vai trò primary. Khi RGW thực hiện bucket listing, nó gửi các read request song song tới OMAP của rất nhiều bucket index shard object khác nhau. Số PG của index pool càng cao thì các shard này càng được trải ra trên nhiều primary OSD hơn. Điều đó làm tăng tính song song của thao tác listing, vì có nhiều thiết bị vật lý cùng phục vụ I/O đồng thời hơn. Ngược lại, số PG thấp có thể tạo ra nút thắt cổ chai, khi rất nhiều request bị dồn vào chỉ vài OSD và khiến chúng bị bão hòa.
Chúng tôi khuyến nghị mỗi index pool nên có ít nhất một PG cho mỗi OSD mà nó được đặt lên. Khi dùng PG autoscaler, index pool sẽ tự động có giá trị BIAS bằng 4 để nhận được nhiều PG hơn. Xem lại phần trên để biết khuyến nghị về các thiết lập cấu hình trung tâm giúp autoscaler cấp đủ PG cho index pool.
Quan sát trực quan Bucket Index Log (#visualizing-the-bucket-index-log)
Trước tiên, ta xác nhận sự tồn tại và Pool ID của bucket index pool:
$ ceph osd lspools | grep default.rgw.buckets.index
6 default.rgw.buckets.index
Ở đây ta thấy RADOS pool có ID 6 chính là index pool dành riêng cho zone default.
Tiếp theo, hãy lấy một tên bucket để làm ví dụ: bucket1:
$ radosgw-admin bucket list | grep bucket1
"bucket1",
Sau đó, ta có thể xem các index entry của một bucket cụ thể thuộc zone default là bucket1, bằng công cụ radosgw-admin:
$ radosgw-admin bi list --bucket bucket1
[
{
"type": "plain",
"idx": "hosts5",
"entry": {
"name": "hosts5",
"instance": "",
"ver": {
"pool": 16,
"epoch": 3
},
"locator": "",
"exists": "true",
"meta": {
"category": 1,
"size": 4066,
"mtime": "2022-12-14T16:27:02.562603Z",
"etag": "71ad37de1d442f5ee2597a28fe07461e",
"storage_class": "",
"owner": "test",
"owner_display_name": "test",
"content_type": "",
"accounted_size": 4066,
"user_data": "",
"appendable": "false"
},
"tag": "_iDrB7rnO7jqyyQ2po8bwqE0vL_Al6ZH",
"flags": 0,
"pending_map": [],
"versioned_epoch": 0
}
}
]
Output của radosgw-admin bi list hiển thị metadata được lưu cho một S3 object (hosts5), bao gồm size, thời điểm chỉnh sửa (mtime) và ETag.
Chìa khóa của khả năng mở rộng: Bucket Sharding (#the-scalability-enabler%3A-bucket-sharding)
Một vấn đề hiệu năng nghiêm trọng xuất hiện khi bucket index phình to. Nếu index của một bucket chỉ nằm trong một RADOS object duy nhất thì tại một thời điểm chỉ có thể thực hiện một thao tác. Sự tuần tự hóa này giới hạn tính song song và có thể trở thành nút thắt cổ chai nghiêm trọng với các workload ghi thông lượng cao.
Để vượt qua giới hạn đó, RGW sử dụng Bucket Index Sharding. Cơ chế này chia bucket index thành nhiều phần, mỗi shard được lưu trên một RADOS object riêng trong index pool. Khi một object được ghi, bản cập nhật sẽ được đưa tới một shard cụ thể, xác định bằng hash của tên object. Nhờ vậy, nhiều thao tác có thể diễn ra đồng thời trên các Placement Group (PG) và OSD khác nhau, giúp cải thiện khả năng mở rộng tổng thể. Số lượng shard nên là một số nguyên tố, và có thể cấu hình qua option bucket_index_max_shards, mặc định là 11). Ta có thể lấy các thông tin metadata liên quan đến bucket và object bằng lệnh radosgw-admin bucket stats — ví dụ số shard, mức sử dụng của bucket, quota, versioning, object lock, owner, v.v.
$ radosgw-admin bucket stats --bucket bucket1 | grep shards
"num_shards": 11,
Bucket Index pool của zone default:
$ ceph osd lspools | grep default.rgw.buckets.index
6 default.rgw.buckets.index
Ta có thể nhìn thấy tận mắt các shard này dưới dạng những RADOS object OMAP riêng biệt:
$ rados -p default.rgw.buckets.index ls
.dir.7fb0a3df-9553-4a76-938d-d23711e67677.34162.1.9
.dir.7fb0a3df-9553-4a76-938d-d23711e67677.34162.1.0
.dir.7fb0a3df-9553-4a76-938d-d23711e67677.34162.1.10
.dir.7fb0a3df-9553-4a76-938d-d23711e67677.34162.1.1
.dir.7fb0a3df-9553-4a76-938d-d23711e67677.34162.1.7
.dir.7fb0a3df-9553-4a76-938d-d23711e67677.34162.1.8
.dir.7fb0a3df-9553-4a76-938d-d23711e67677.34162.1.2
.dir.7fb0a3df-9553-4a76-938d-d23711e67677.34162.1.6
.dir.7fb0a3df-9553-4a76-938d-d23711e67677.34162.1.5
.dir.7fb0a3df-9553-4a76-938d-d23711e67677.34162.1.4
.dir.7fb0a3df-9553-4a76-938d-d23711e67677.34162.1.3
Mỗi RADOS object .dir liệt kê ở đây là một bucket index shard riêng. Trong ví dụ này ta thấy 11 shard, khớp với số shard mặc định của mỗi bucket.
Tại thời điểm tạo bucket, số shard ban đầu được quyết định bởi option bucket_index_max_shards ở mức zonegroup, và được áp dụng cho toàn bộ bucket. Nếu một bucket cụ thể cần số shard khác, ta hoàn toàn có thể thay đổi.
Lưu ý: chúng tôi khuyến nghị tối đa 102.400 S3 object trên mỗi bucket index shard.
Ta có thể lấy marker của một bucket bằng lệnh stats:
$ radosgw-admin bucket stats --bucket bucket1 | grep marker
"marker": "7fb0a3df-9553-4a76-938d-d23711e67677.34162.1",
Vậy là ta đã biết marker của bucket1 là 7fb0a3df-9553-4a76-938d-d23711e67677.34162.1. Giờ hãy upload một object tên file1 vào bucket1:
$ aws --endpoint=http://ceph-node02:8080 s3 cp /etc/hosts s3://bucket1/file1 --region default
upload: ../etc/hosts to s3://bucket1/file1
Hãy cùng soi bucket index của bucket này ở mức RADOS. Bằng cách liệt kê omapkey trên bucket index object, ta thấy một key tên file1, đúng bằng tên object vừa upload. Ở đây ta chạy listomapkeys trên một trong 11 shard object hiện có, cụ thể là shard 2. Như đã đề cập, các object sẽ được rải đều lên nhiều shard khác nhau khi được tạo.
$ rados -p default.rgw.buckets.index listomapkeys .dir.7fb0a3df-9553-4a76-938d-d23711e67677.34162.1.2
file1
Khi kiểm tra phần value, ta thấy entry key/value trong omap object của bucket index shard 2 thuộc bucket1 có kích thước 217 byte. Trong phần hex dump, ta thấy các thông tin trong đó có tên object.
$ rados -p default.rgw.buckets.index listomapvals .dir.7fb0a3df-9553-4a76-938d-d23711e67677.34162.1.2
file1
value (217 bytes) :
00000000 08 03 d3 00 00 00 05 00 00 00 66 69 6c 65 31 01 |..........file1.|
00000010 00 00 00 00 00 00 00 01 07 03 5a 00 00 00 01 32 |..........Z....2|
00000020 05 00 00 00 00 00 00 4b ab a1 63 95 74 ba 04 20 |.......K..c.t.. |
Khi thêm nhiều S3 object hơn vào bucket, ta sẽ thấy các entry key/value mới được thêm vào những shard hiện có của bucket. Trong ví dụ này, file1, file2, file4, file10 đều rơi vào shard 2:
$ rados -p default.rgw.buckets.index listomapkeys .dir.7fb0a3df-9553-4a76-938d-d23711e67677.34162.1.2
file1
file2
file4
file10
Ta có thể xác nhận vị trí đặt của một shard cụ thể, ở đây là shard 2:
$ ceph osd map default.rgw.buckets.index .dir.7fb0a3df-9553-4a76-938d-d23711e67677.34162.1.2
osdmap e90 pool 'default.rgw.buckets.index' (9) object '.dir.7fb0a3df-9553-4a76-938d-d23711e67677.34162.1.2' -> pg 9.6fa75bc9 (9.9) -> up ([1,2], p5) acting ([1,2], p5)
Output này cho thấy index shard được replicate trong cluster và nằm trên các OSD cụ thể. Việc trải index ra nhiều PG (và do đó nhiều OSD) chính là thứ tạo ra khả năng xử lý song song.
Bí ẩn 0 byte: Vì sao Index Pool trông như rỗng (#the-zero-byte-mystery%3A-why-the-index-pool-appears-empty)
Khi bạn truy vấn dung lượng sử dụng của bucket index pool, kết quả thường khiến các kỹ sư chưa quen với kiến trúc OMAP của Ceph phải ngạc nhiên:
$ rados df -p default.rgw.buckets.index
POOL_NAME USED OBJECTS CLONES COPIES MISSING_ON_PRIMARY UNFOUND DEGRADED RD_OPS RD WR_OPS WR USED COMPR UNDER COMPR
default.rgw.buckets.index 0 B 11 0 33 0 0 0 208 207 KiB 41 20 KiB 0 B 0 B
Ngay cả khi kiểm tra riêng một shard object (shard 2), kích thước vẫn là 0:
$ rados -p default.rgw.buckets.index stat .dir.7fb0a3df-9553-4a76-938d-d23711e67677.34162.1.2
default.rgw.buckets.index/.dir.7fb0a3df-9553-4a76-938d-d23711e67677.34162.1.2 mtime 2022-12-20T07:32:11.000000-0500, size 0
Dù chứa tới 11 RADOS object (các shard), pool vẫn báo dung lượng sử dụng là 0 byte. Lý do là toàn bộ dữ liệu listing của bucket index được lưu dưới dạng OMAP entry bên trong RocksDB của từng OSD, chứ không phải dưới dạng payload data của chính RADOS object. Điều này một lần nữa khẳng định vì sao dùng flash media nhanh (SSD) cho ít nhất phân vùng DB của OSD là điều thiết yếu để tối đa hóa hiệu năng bucket index.
Quản lý sự phình to của Index bằng Dynamic Bucket Resharding
Khi một bucket lớn dần tới hàng trăm nghìn hoặc hàng triệu S3 object, index của nó có thể trở thành nút thắt cổ chai về hiệu năng. Mặc định, một shard đơn lẻ có thể trở nên "nóng" khi tích tụ quá nhiều entry. Ngưỡng số S3 object trên mỗi shard là có thể cấu hình, mặc định là 100.000. Số lượng S3 object quá lớn trong một bucket sẽ làm tái diễn đúng vấn đề tuần tự hóa mà sharding sinh ra để giải quyết. Để đối phó, RGW có một cơ chế tự động nâng cao gọi là Dynamic Bucket Resharding (DBR).
DBR là một tiến trình chạy nền, liên tục theo dõi số entry trong từng bucket index shard. Khi một shard vượt ngưỡng cấu hình, DBR sẽ tự động kích hoạt thao tác resharding ngay khi hệ thống đang online. Quá trình này tạo ra một tập index object mới với số shard nhiều hơn, rồi di chuyển an toàn các index entry hiện có từ layout cũ (nhỏ hơn) sang layout mới (lớn hơn).
Hành trình tiến hóa của Online Resharding: Giảm thiểu tác động (#the-evolution-of-online-resharding%3A-minimizing-impact)
Trước đây, thao tác resharding đòi hỏi phải tạm dừng I/O ghi vào bucket. Dù các thao tác đọc không bị ảnh hưởng, việc "đóng băng" ghi này vẫn có thể gây khó chịu và đau đầu với những workload có mức hoạt động cao.
Tuy nhiên, một cải tiến đáng kể sẽ có trong bản Tentacle sắp tới đã giảm thiểu tối đa khoảng đóng băng ghi này. Cách hiện thực mới khiến quá trình resharding gần như trong suốt, cho phép các thao tác ghi tiếp tục diễn ra với mức gián đoạn tối thiểu. Đây là bước tiến quan trọng, biến dynamic resharding thành một tính năng mượt mà và an toàn cho production, kể cả trong những môi trường khắt khe nhất.
Không chỉ phình to, mà còn co lại: Sức mạnh của Shard Merging (#not-just-growing%2C-but-shrinking%3A-the-power-of-shard-merging)
Dynamic resharding không chỉ dùng để scale lên. Hãy hình dung kịch bản một bucket từng chứa hàng triệu object nay bị xóa đi phần lớn. Bucket giờ còn lại rất nhiều index shard thưa thớt hoặc thậm chí rỗng. Điều này gây lãng phí, vì mỗi thao tác listing vẫn phải kiểm tra tất cả các shard, tạo ra overhead không cần thiết.
Để giải quyết, cơ chế DBR đã được bổ sung khả năng shard merging. Như được mô tả trong tài liệu Ceph và các development tracker (ví dụ BZ#2135354), nếu số lượng object trong bucket giảm mạnh, DBR có thể kích hoạt thao tác resharding theo hướng "downsizing". Nó sẽ gom các entry từ nhiều shard thưa thớt vào một tập index object mới nhỏ hơn và có mật độ dày đặc hơn.
DBR là một tính năng tự động mạnh mẽ, nhưng với những trường hợp bạn đã biết chắc bucket sẽ cực lớn ngay từ đầu, best practice tiêu chuẩn vẫn là pre-shard bucket ngay tại thời điểm tạo. Bằng cách đặt sẵn số shard ban đầu phù hợp, bạn có thể tránh hoàn toàn lần dynamic resharding đầu tiên, đảm bảo hiệu năng tối ưu ngay từ object đầu tiên được ghi.
Tương lai sẽ có thứ tự: Thoáng nhìn về In-Order Sharding (#the-future-is-ordered%3A-a-glimpse-into-in-order-sharding)
Hiện tại, cơ chế hashed sharding của RGW được tối ưu cho việc phân tán ghi, nhưng lại là một thách thức khi cần liệt kê object theo thứ tự alphabet. Để phục vụ một request list có phân trang, RGW phải thực hiện thao tác "scatter-gather": truy vấn từng shard một rồi sắp xếp kết quả tổng hợp lại. Điều này có thể trở thành nút thắt cổ chai với những bucket có rất nhiều shard.
Để giải quyết, một tính năng mới quan trọng gọi là in-order sharding (hay ordered bucket listing) đang được phát triển. Bước tiến sắp tới này sẽ thay đổi logic sharding: object được đưa vào shard dựa trên thứ tự từ điển (lexicographical) của tên, thay vì dựa trên hash.
Tác động của thay đổi này sẽ rất lớn. Thay vì phải truy vấn toàn bộ shard, một request liệt kê object sẽ chỉ được gửi tới đúng shard chứa dải alphabet được yêu cầu. Điều đó khiến các thao tác listing có phân trang nhanh và hiệu quả hơn rất nhiều, đặc biệt với những workload thường xuyên duyệt hoặc lặp qua các object key.
Bằng cách kết hợp khả năng tự động scale của Dynamic Bucket Resharding với hiệu quả listing của in-order sharding, Ceph RGW đang đi theo một lộ trình rõ ràng: mang lại khả năng mở rộng gần như không giới hạn và hiệu năng cao ngay trong phạm vi một bucket duy nhất, đáp ứng những use case data lake và AI/ML khắt khe nhất trong tương lai.
Kết luận: Cỗ máy của khả năng mở rộng (#conclusion%3A-the-engine-of-scalability)
Đến đây, chúng ta đã đi hết hành trình hiệu năng cao của một client request: từ kết nối ban đầu tại Beast frontend, xuyên qua các RADOS pool chuyên biệt, rồi đi sâu vào cơ chế tinh vi của bucket index. Bạn đã hiểu vì sao OMAP là xương sống của việc liệt kê object, và Dynamic Bucket Resharding đóng vai trò cỗ máy mở rộng như thế nào, cho phép một bucket đơn lẻ phình lên tới hàng tỷ object mà vẫn giữ được hiệu năng. Chúng ta đã bóc tách những cơ chế cốt lõi xử lý việc tìm kiếm và liệt kê object ở quy mô cực lớn.
Tuy nhiên, cho tới giờ phần deep dive này mới chỉ tập trung vào index — tức là các con trỏ tới dữ liệu. Còn bản thân dữ liệu thì sao? Và còn phần metadata ở control plane, thứ định nghĩa user, account cùng các quy tắc chi phối toàn bộ hệ thống, thì thế nào?
Trong Phần 2 của loạt bài, chúng ta sẽ trả lời những câu hỏi đó. Chúng ta sẽ chuyển hướng sang mô hình head/tail đầy tinh tế trong cách RGW bố trí dữ liệu, xem xét phần metadata cốt lõi của hệ thống, và khám phá các tiến trình nền vững chắc quản lý dữ liệu xuyên suốt toàn bộ vòng đời của nó.
Nhóm tác giả xin cảm ơn IBM đã hỗ trợ cộng đồng bằng cách dành thời gian cho chúng tôi thực hiện những bài viết này.