Shard database với activerecord-turntable
Sharding là gì? Sharding là một tiến trình lưu giữ các bản ghi dữ liệu qua nhiều thiết bị để đáp ứng yêu cầu về sự gia tăng dữ liệu. Khi kích cỡ của dữ liệu tăng lên, một thiết bị đơn ( 1 database hay 1 bảng) không thể đủ để lưu giữ dữ liệu. Sharding giải quyết vấn đề này với việc mở rộng phạm vi ...
Sharding là gì?
Sharding là một tiến trình lưu giữ các bản ghi dữ liệu qua nhiều thiết bị để đáp ứng yêu cầu về sự gia tăng dữ liệu. Khi kích cỡ của dữ liệu tăng lên, một thiết bị đơn ( 1 database hay 1 bảng) không thể đủ để lưu giữ dữ liệu. Sharding giải quyết vấn đề này với việc mở rộng phạm vi theo bề ngang (horizontal scaling). Với Sharding, bạn bổ sung thêm nhiều thiết bị để hỗ trợ cho việc gia tăng dữ liệu và các yêu cầu của các hoạt động đọc và ghi.
Đối với những hệ thống có dữ liệu rất lớn thì đến một lúc nào đó, số dũ liệu trong bảng lên đến hàng triệu, việc query trở nên vô cùng ì ạch và tốn rất nhiều dung lượng bộ nhớ. Kỹ thuật sharding giúp ta giải quyết vấn đề này một cách nhanh chogs bằng cách chia nhỏ bảng hay db ra làm các phần khác nhau, chúng có cấu trúc dữ liệu giống nhau nhưng lưu các dữ liệu khác nhau để giảm tải thay cho việc chỉ dùng 1 bảng.
Xem hình ví dụ dưới đây ta có thể hình dung đươc hiệu quả của kỹ thuật sharding:
Ở đây, ta đã mở rộng phân bố theo chiều ngang để lưu giữ từng phần dữ liệu lên các db khác nhau.
ActiveRecord::Turntable
ActiveRecord::Turntable là công cụ mở rộng để thực hiện việc sharde dự liệu cho active record. Hiện tại thì Turntable chỉ support cho CSDL mysql.
cách sử dụngTa cài đặt Turntable thông qua Gemfile
gem 'activerecord-turntable'
Sau đó
bundle install
Tiếp đó ta cần chạy khởi động turntable
active_record:turntable:install
Sau khi chay xong sẽ khởi tạo cho ta file config trong #{Rails.root}/config/turntable.yml
Một vài thuật ngữ cần chú ý khi dùng ActiveRecord::TurntableShard: Là một database được chia nhỏ theo chiều ngang. Hay dễ hiểu hơn một shard là một hoặc nhiều server trong một cluster chịu trách nhiệm với một tập các dữ liệu lưu trữ
Cluster Chính là các database được tạo nên từ shard.
Master ActiveRecord::Base connection
Config Turntableturntable.yml
development: clusters: user_cluster: # <-- cluster name algorithm: range_bsearch # <-- `range`, `range_bsearch` or `modulo` seq: user_seq: # <-- sequencer name seq_type: mysql # <-- sequencer type connection: user_seq_1 # <-- sequencer database connection setting shards: - connection: user_shard_1 # <-- shard name less_than: 100 # <-- shard range(like mysql partitioning) If you are using a modulo algorithm, it doesn't need it. - connection: user_shard_2 less_than: 200 - connection: user_shard_3 less_than: 2000000000
database.yml
connection_spec: &spec adapter: mysql2 encoding: utf8 reconnect: false pool: 5 username: root password: root socket: /tmp/mysql.sock development: <<: *spec database: sample_app_development seq: # <-- sequence database definition user_seq_1: <<: *spec database: sample_app_user_seq_development shards: # <-- shards definition user_shard_1: <<: *spec database: sample_app_user1_development user_shard_2: <<: *spec database: sample_app_user2_development user_shard_3: <<: *spec database: sample_app_user3_development
Để ví dụ, ta tạo migrate bảng user
bundle exec rails g model user name:string
Và sửa file migrate để tạo các sequence cho bảng user
class CreateUsers < ActiveRecord::Migration # Specify cluster executes migration if you need. # Default, migration would be executed to all databases. # clusters :user_cluster def change create_table :users do |t| t.string :name t.timestamps end create_sequence_for(:users) # <-- create sequence table end end
Để thực thi việc tạo bảng và shard, ta chạy
bundle exec rake db:create bundle exec rake db:migrate
Thêm turntable vào model class như sau:
class User < ActiveRecord::Base turntable :user_cluster, :id sequencer :user_seq has_one :status end class Status < ActiveRecord::Base turntable :user_cluster, :user_id sequencer :user_seq belongs_to :user end
Bây giờ ta sẽ thử tạo 1 user, ta có thể thấy khi tạo dữ liệu, thì chỉ thao tác trên database user_shard_1.
> User.create(name: "hoge") (0.0ms) [Shard: user_seq_1] BEGIN (0.3ms) [Shard: user_seq_1] UPDATE `users_id_seq` SET id=LAST_INSERT_ID(id+1) (0.8ms) [Shard: user_seq_1] COMMIT (0.1ms) [Shard: user_seq_1] SELECT LAST_INSERT_ID() (0.1ms) [Shard: user_shard_1] BEGIN [ActiveRecord::Turntable] Sending method: insert, sql: #<Arel::InsertManager:0x007f8503685b48>, shards: ["user_shard_1"] SQL (0.8ms) [Shard: user_shard_1] INSERT INTO `users` (`created_at`, `id`, `name`, `updated_at`) VALUES ('2012-04-10 03:59:42', 2, 'hoge', '2012-04-10 03:59:42') (0.4ms) [Shard: user_shard_1] COMMIT => #<User id: 2, name: "hoge", created_at: "2012-04-10 03:59:42", updated_at: "2012-04-10 03:59:42">
Còn khi thực hiện đếm dữ liệu thì sẽ đếm tất cả các bản ghi trong tất cả 3 shard ( bên trên ta khai báo 3 shard )
> User.count [ActiveRecord::Turntable] Sending method: select_value, sql: #<Arel::SelectManager:0x007f9e82ccebb0>, shards: ["user_shard_1", "user_shard_2", "user_shard_3"] (0.8ms) [Shard: user_shard_1] SELECT COUNT(*) FROM `users` (0.3ms) [Shard: user_shard_2] SELECT COUNT(*) FROM `users` (0.2ms) [Shard: user_shard_3] SELECT COUNT(*) FROM `users` => 1
Sequence
Sequence là công cụ để clust DB, nó có tác dụng giữ các primary key để sinh ra các id unique cho các shard.
Để config sequence với mysql ta thực hiện như sau:
- database.yml
development: ... seq: # <-- sequence database definition user_seq_1: <<: *spec database: sample_app_user_seq_development
- turntable.yml
development: clusters: user_cluster: # <-- cluster name .... seq: user_seq: # <-- sequencer name seq_type: mysql # <-- sequencer type connection: user_seq_1 # <-- sequencer database connection
Thêm dòng lệnh sau vào file migrate để thực hiện sequence
create_sequence_for(:users) # <-- this line creates sequence table named `users_id_seq`
Cuối cùng ta định nghĩa sequence trong class model
class User < ActiveRecord::Base turntable :id sequencer :user_seq # <-- this line enables sequencer module has_one :status end
Transaction
Turntable có 2 phương thức thực hiên transaction:
- shards_transaction
Ví dụ
user = User.find(2) user3 = User.create(name: "hoge3") User.shards_transaction([user, user3]) do user.name = "hogehoge" user3.name = "hogehoge3" user.save! user3.save! end
- cluster_transaction: khi gọi cluster_transaction thì tất cả các shard trong 1 clust này sẽ đều được thực hiện
User.user_cluster_transaction do # Transaction is opened all shards in "user_cluster" endMigration
Nếu ta muốn thực hiện clust hay shard thì chỉ việc khai báo trong file migration. còn nếu không khai báo thì sẽ mặc định thực hiện trên tất cả databases.
- Nếu chỉ sử dụng clust
class CreateUsers < ActiveRecord::Migration clusters :user_cluster .... end
- Sử dụng shard
class CreateUsers < ActiveRecord::Migration shards :user_shard_01 .... end
Một số hạn chế
- Turntable không hỗ trợ một vài câu lệnh SQL như ORDER BY , GROUP BY và LIMIT
- Các thiết lập relation has many through hay has_and_belongs_to_many
Một vài chú ý
- Để query đến 1 shard nào đó ta sử dụng with_shard
AR::Base.connection.with_shard(shard1) do # something queries to shard1 end
- Còn nếu muốn query đến tất cả các shard
User.connection.with_all do User.order("created_at DESC").limit(3).all end
- để truy cập đến đối tượng của shard,
ActiveRecord::Base.connection.shards # {shard_name => shard_obj,....} ActiveRecord::Base#turntable_shard # Returns current object's shard ActiveRecord::Base.connection.select_shard(shard_key_value) #=> shard
Connection Management
Bộ quản lý kết nối middleware của rails luôn luôn giữ kết nối của ActiveRecord mỗi khi 1 process còn sống. Tuy nhiên dử dụng Turntable có thể gây quá tải các kết nối vid thế ta phải có cơ chế cho middleware để ngắt bớt các kết nối của mỗi request. Để thực hiện điều này ta thêm câu lệnh sau trong config/initialize
app.middleware.swap ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::Turntable::Rack::ConnectionManagement
Kết luận
Đối với những hệ thống có lương dữ liệu lớn thì công nghệ clust và shard tỏ ra rất hữu hiệu để giải quyết vấn đề chia tách một dữ liệu kích thước lớn cho rất nhiều server nhằm giảm tải. Tuy nhiên đây lại là một kỹ thuật rất phức tạp đồi hỏi sự nghiên cứu kỹ lưỡng để có thể đưa vào vận hành một cách trơn tru. Bào viết trên đây mới chỉ là bước sơ khai cho những ai mới nghe đến công nghệ này hiẻu qua được các khái niệm cơ bản và muốn thử tích hợp vào ứng dụng rails.