Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Mẹo Cần Thiết Về Scale Database Cho Dự Án Đang Phát Triển

Khi bạn đang làm việc trên một dự án trẻ, bạn liên tục đưa ra quyết định sẽ làm cho nó dễ hoặc khó scale sau này. Đôi khi tốt khi chọn lợi ích ngắn hạn, để tích lũy chút technical debt để ship nhanh hơn. Nhưng những lúc khác chúng ta gánh technical debt vì chúng ta không biết có lựa chọn thay thế.

Tôi có thể nói điều này vì tại đây Honeybadger, chúng ta đã làm một vài điều khiến cuộc sống khó khăn hơn nhiều so với cần thiết. Se chúng ta hiểu một vài điểm then chốt, scaling sẽ ít đau đớn hơn nhiều.

Dùng UUIDs

Khi bạn nói "primary key", hầu hết chúng ta nghĩ đến một số auto-increment. Điều này hoạt động tốt cho hệ thống nhỏ, nhưng nó giới thiệu một vấn đề lớn khi bạn scale.

At any given moment only one database server can generate the primary key. That means all writes have to go through a single server. That's bad news if you want to do thousands of writes per second.

Sử dụng UUIDs làm primary keys bypass vấn đề này. Nếu bạn không quen với chúng, UUIDs là các định danh duy nhất trông như thế này: 123e4567-e89b-12d3-a456-426655440000.

Đây là cách Wikipedia mô tả chúng:

When generated according to the standard methods, UUIDs are for practical purposes unique, without requiring a central registration authority or coordination between the parties generating them. The probability that a UUID will be duplicated is not zero, but is so close to zero as to be negligible.

Thus, anyone can create a UUID and use it to identify something with near certainty that the identifier does not duplicate one that has already been created to identify something else, and will not be duplicated in the future. Information labeled with UUIDs by independent parties can therefore be later combined into a single database, or transmitted on the same channel, without needing to resolve conflicts between identifiers.

Khi bạn dùng UUIDs làm primary keys, tất cả writes không còn phải đi qua một database đơn lẻ. Thay vào đó bạn có thể spread chúng ra nhiều server.

Ngoài ra chúng cho bạn sự linh hoạt để làm những việc như generate id của một record trước khi nó saved vào database. Điều này có thể hữu ích nếu bạn muốn gửi record đến cache hoặc search server, nhưng không muốn chờ transaction database hoàn thành.

Bật UUIDs mặc định trong Rails apps dễ dàng. Chỉ cần edit file config:

# config/application.rb
config.active_record.primary_key = :uuid

Bạn có thể dùng UUIDs cho một bảng riêng lẻ với Rails migrations:

create_table :users, id: :uuid do |t|
  t.string :name
end

Đây là một tùy chọn cấu hình đơn giản mà, nếu bạn bật nó khi bắt đầu phát triển, sẽ cứu bạn khỏi một thế giới phiền toái khi bạn thử scale. Là bonus thêm, nó sẽ làm cho bots và bad actors khó hơn đoán private URLs của bạn.

Đếm và counters

Khi bạn bắt đầu nhìn, counts và counters ở khắp nơi. Email client của bạn hiển thị số email chưa đọc. Blog có footer pagination dùng tổng số bài viết để tính số trang.

Có hai vấn đề scaling với counters:

  1. Database queries như select count(*) from users vốn dĩ chậm. Chúng thực sự loop qua từng record trong recordset để generate kết quả. Nếu bạn có một triệu record, nó sẽ mất một lúc.
  2. Cố gắng tăng tốc counters bằng "counter caches" có hiệu quả, nhưng chúng giới hạn khả năng spread writes của bạn qua nhiều database server.

Giải pháp dễ nhất là tránh dùng counters nơi có thể. Điều này dễ hơn nhiều khi bạn đang làm thiết kế ban đầu.

Ví dụ, bạn có thể chọn page theo date-range thay vì theo count. Bạn có thể chọn không hiển thị một thống kê hơi hữu ích mà sẽ là địa ngục để generate sau này. Bạn hiểu ý.

Hết hạn & kho dữ liệu

Có một giới hạn trên về lượng dữ liệu bạn có thể lưu trong một bảng postgres đơn lẻ cho trước RAM, CPU và disk IO bạn có. Nghĩa là sẽ đến lúc bạn cần chuyển dữ liệu cũ ra khỏi bảng chính.

Hãy xem một case đơn giản. Bạn muốn xóa record hơn một năm trong database vài gigabyte.

Nếu bạn chưa bao giờ deal với vấn đề này trước đây, bạn có thể bị cám dỗ làm như sau:

MyRecords.where("created_at < ?", 1.year.ago).destroy

Vấn đề là query này sẽ mất ngày hoặc tuần để chạy. Database của bạn quá lớn.

Đây là một vấn đề đặc biệt đau đớn, vì thường bạn không nhận ra bạn có nó cho đến khi quá muộn. Hầu như không ai nghĩ về chiến lược xóa dữ liệu khi công ty trẻ và database có 1,000 record.

Nếu bạn kịp lên kế hoạch trước, có một giải pháp dễ dàng. Chỉ cần partition tables của bạn. Thay vì viết tất cả dữ liệu vào my_records bạn viết dữ liệu tuần này vào my_records_1 và tuần sau vào my_records_2. Khi đến lúc xóa tuần trước, chỉ cần drop table my_records_1. Khác với delete, query này hoàn thành rất nhanh.

Có thể partition bằng field khác ngoài date, cũng được. Bất cứ gì hợp lý cho use-case của bạn.

Thậm chí có một postgres extension tên pg_partman lo tất cả chi tiết và cho phép bạn partition database không thay đổi một dòng mã. Hoặc, nếu bạn thích partition được quản lý trong Ruby, có một gem tiện lợi tên partitionable

Lời cuối

Lần sau khi bạn thấy mình xây dựng dự án từ đầu, tôi khuyến khích bạn dành vài phút suy nghĩ về scaling. Đừng ám ảnh nó. Đừng dành ngày lo lắng về dùng HAML hay ERB. Nhưng hãy tự hỏi xem có easy wins nào bạn có thể đạt được chỉ bằng cách lên kế hoạch trước không.