Shard database với activerecord-turntable
Bài đăng này đã không được cập nhật trong 8 năm
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ụng
Ta 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::Turntable
Shard: 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 Turntable
turntable.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"
end
Migration
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
hayhas_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.
All rights reserved