+3

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 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:

Image

Ở đâ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 BYLIMIT
  • 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.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí