8

Tôi đã một phân phối/liên cơ sở dữ liệu có cấu trúc như sau:Tối ưu hóa băng thông mạng qua việc tập hợp cơ sở dữ liệu phân tán

  1. Các cơ sở dữ liệu nằm rải rác trên ba vị trí địa lý ("nút")
  2. Nhiều cơ sở dữ liệu được nhóm tại mỗi node
  3. Cơ sở dữ liệu quan hệ là sự kết hợp giữa PostgreSQL, MySQL, Oracle và MS SQL Server; cơ sở dữ liệu không quan hệ là một trong hai MongoDB hay Cassandra
  4. khớp nối lỏng trong mỗi nút và trên liên nút đạt được thông qua RabbitMQ, với mỗi nút chạy một nhà môi giới RabbitMQ

Tôi đang thực hiện một tập hợp liên nút readonly hệ thống công việc cho các công việc mở rộng liên kết nút (ví dụ cho các công việc không phải là cục bộ cho một nút). Những công việc này chỉ thực hiện các truy vấn "get" - chúng không sửa đổi cơ sở dữ liệu. (Nếu kết quả của các công việc được dự định đi vào một hoặc nhiều cơ sở dữ liệu thì điều này được thực hiện bởi một công việc riêng biệt không phải là một phần của hệ thống công việc liên nút mà tôi đang cố gắng tối ưu hóa.) Mục tiêu của tôi là giảm thiểu băng thông mạng được yêu cầu bởi các công việc này (đầu tiên để giảm thiểu băng thông giữa các nút/WAN, sau đó để giảm thiểu băng thông nội bộ/LAN); Tôi giả định một chi phí thống nhất cho mỗi liên kết WAN, và một chi phí thống nhất khác cho mỗi liên kết mạng LAN. Các công việc không đặc biệt nhạy cảm với thời gian. Tôi thực hiện một số cân bằng tải CPU trong một nút nhưng không phải giữa các nút.

Lượng dữ liệu được truyền qua mạng WAN/LAN cho công việc tổng hợp là nhỏ so với số lượng cơ sở dữ liệu viết cục bộ cho cụm hoặc cơ sở dữ liệu cụ thể, vì vậy sẽ không thực tế khi phân phối đầy đủ cơ sở dữ liệu trên toàn liên bang.

Các thuật toán cơ bản tôi sử dụng để giảm thiểu băng thông mạng là:

  1. Với một công việc chạy trên một tập hợp các dữ liệu được trải rộng ra các liên đoàn, các nút quản lý sẽ gửi một thông điệp cho mỗi các nút khác chứa các truy vấn cơ sở dữ liệu có liên quan.
  2. Mỗi nút chạy tập hợp truy vấn của nó, nén chúng bằng gzip, lưu trữ chúng và gửi kích thước đã nén của chúng đến nút người quản lý.
  3. Người quản lý di chuyển đến nút chứa đa số dữ liệu (cụ thể, với máy trong cụm có nhiều dữ liệu nhất và có lõi nhàn rỗi); nó yêu cầu phần còn lại của dữ liệu từ hai nút khác và từ các máy khác trong cụm, sau đó nó chạy công việc.

Khi có thể, công việc sử dụng cách tiếp cận phân chia và chinh phục để giảm thiểu lượng dữ liệu đồng vị trí cần thiết. Ví dụ, nếu công việc cần tính tổng của tất cả các số liệu bán hàng trên toàn liên đoàn, thì mỗi nút tại địa phương tính tổng doanh thu của nó, sau đó được tổng hợp tại nút người quản lý (thay vì sao chép tất cả dữ liệu bán hàng chưa xử lý sang nút người quản lý) . Tuy nhiên, đôi khi (chẳng hạn như khi thực hiện một kết nối giữa hai bảng được đặt tại các nút khác nhau) thì cần phải có dữ liệu đồng vị trí. Điều đầu tiên tôi đã làm để tối ưu hóa điều này là tổng hợp các công việc, và để chạy các công việc tổng hợp tại mười phút epochs (tất cả các máy đều chạy NTP, vì vậy tôi có thể chắc chắn rằng "mỗi mười phút" có nghĩa là như nhau ở mỗi nút). Mục đích là cho hai công việc để có thể chia sẻ cùng một dữ liệu, làm giảm tổng chi phí vận chuyển dữ liệu.

  1. Cho hai công việc truy vấn cùng một bảng, tôi tạo ra kết quả của mỗi công việc, sau đó tôi lấy giao điểm của hai bộ kết quả.
  2. Nếu cả hai công việc được lập biểu để chạy trên cùng một nút, thì chi phí chuyển mạng được tính bằng tổng của hai kết quả trừ đi giao điểm của hai kết quả.
  3. Hai bộ kết quả được lưu trữ vào các bảng tạm thời của PostgreSQL (trong trường hợp dữ liệu quan hệ) hoặc các bộ khung Cassandra columnfamilies/MongoDB tạm thời (trong trường hợp dữ liệu nosql) tại nút được chọn để chạy các công việc; các truy vấn ban đầu sau đó được thực hiện dựa trên các kết quả kết hợp và dữ liệu được gửi đến các công việc riêng lẻ. (Bước này chỉ được thực hiện trên kết quả kết hợp, dữ liệu resultset cá nhân chỉ đơn giản là được giao cho công việc của mình mà không được lưu trữ trước trên các nhóm/bộ sưu tập/bảng tạm thời.)

Điều này dẫn đến cải thiện băng thông mạng, nhưng 'm tự hỏi nếu có một khuôn khổ/thư viện/thuật toán sẽ cải thiện về điều này. Một tùy chọn mà tôi xem xét là lưu các bộ kết quả vào một nút và tính toán các kết quả được lưu trong bộ nhớ cache này khi xác định băng thông mạng (tức là cố gắng sử dụng lại tập hợp kết quả trên các công việc ngoài tập hợp hiện tại của các công việc đã định trước. công việc chạy trong một kỷ nguyên 10 phút có thể sử dụng bộ kết quả được lưu trong bộ nhớ cache từ một kết quả 10 phút trước đó, nhưng trừ khi công việc sử dụng cùng một kết quả chính xác (nghĩa là trừ khi chúng sử dụng các mệnh đề giống hệt nhau) thì tôi không biết thuật toán mục đích sẽ điền vào các khoảng trống trong resultset (ví dụ, nếu resultset sử dụng mệnh đề "where N> 3" và một công việc khác cần resultset với mệnh đề "where N> 0" thì thuật toán nào tôi có thể sử dụng xác định rằng tôi cần phải lấy liên kết của resultset gốc và với resultset với mệnh đề "where N> 0 AND N < = 3") - Tôi có thể thử viết thuật toán của riêng mình để làm điều này, nhưng kết quả sẽ là buggy vô dụng mess. Tôi cũng cần xác định khi nào dữ liệu được lưu trữ là cũ - cách đơn giản nhất để làm điều này là so sánh dấu thời gian của dữ liệu được lưu trong bộ nhớ cache với dấu thời gian được sửa đổi cuối cùng trên bảng nguồn và thay thế tất cả dữ liệu nếu dấu thời gian đã thay đổi. Tôi muốn chỉ có thể cập nhật các giá trị đã thay đổi với dấu thời gian cho mỗi hàng hoặc mỗi đoạn.

+0

Sẽ dễ dàng hơn khi phân phối đầy đủ các bảng đầy đủ đến từng trang web, thay vì cố gắng xử lý các đoạn từ một phần trong mệnh đề? Dung lượng ổ đĩa là rẻ, nhưng nó phụ thuộc vào tần suất dữ liệu cơ bản thay đổi so với mức độ hẹp của các biến vị ngữ của bạn là liệu wil có làm giảm lưu lượng mạng hay không. – rlb

+0

@rlb Vấn đề là có rất nhiều hoạt động viết trong mỗi cụm, và vì vậy bảng phân phối đầy đủ sẽ có nghĩa là hoạt động viết này sẽ cần phải truyền đến từng cụm ngay cả khi nó không cần thiết. Ví dụ, một cơ sở dữ liệu là một cơ sở dữ liệu tài chính với giá cổ phiếu, có nghĩa là có ** ** nhiều cơ sở dữ liệu viết. Các công việc được liên kết có thể chỉ cần một ảnh chụp nhanh dữ liệu này nhiều nhất mỗi giờ hoặc lâu hơn, một lượng nhỏ băng thông mạng cần thiết để truyền dữ liệu cho mỗi lần cập nhật cổ phiếu cho mỗi cụm. –

+0

Ok hiểu vấn đề về âm lượng. Bạn có quyền kiểm soát những gì được chuyển qua dây không? Chúng tôi đã chuyển từ hàng này sang cột khác cho kết quả và tỷ lệ nén đã tăng lên, vì vậy, độ cao này là một chiến thắng đơn giản, ít rủi ro nhưng không phải là những gì bạn đang yêu cầu chính xác. Sẽ săn lùng quanh văn phòng để tìm một thứ gì đó cho câu hỏi thực tế của bạn nhưng chủ yếu chúng tôi làm việc để tối ưu hóa như một sự tham gia được phân phối, như bạn đề cập có thể flakey nếu không được thực hiện một cách hoàn hảo. – rlb

Trả lời

4

Tôi đã bắt đầu triển khai giải pháp cho câu hỏi.

Để đơn giản hóa bộ đệm trong và cũng để đơn giản hóa cân bằng tải CPU, tôi đang sử dụng cơ sở dữ liệu Cassandra tại mỗi cụm cơ sở dữ liệu ("nút Cassandra") để chạy các công việc tổng hợp (trước đây tôi đã tổng hợp cục bộ) - Tôi đang sử dụng cơ sở dữ liệu Cassandra đơn lẻ cho dữ liệu quan hệ, Cassandra và MongoDB (nhược điểm là một số truy vấn quan hệ chạy chậm hơn trên Cassandra, nhưng điều này được tạo ra bởi thực tế là tập hợp đơn nhất thống nhất cơ sở dữ liệu dễ bảo trì hơn so với các cơ sở dữ liệu tập hợp quan hệ và phi quan hệ riêng biệt). Tôi cũng không còn tổng hợp công việc trong mười phút epochs kể từ khi bộ nhớ cache làm cho thuật toán này không cần thiết.

Mỗi máy trong một nút đề cập đến một họ Cassandra có tên là Cassandra_Cache_ [MachineID] được sử dụng để lưu trữ các key_ids và column_ids mà nó đã gửi đến nút Cassandra. Các Cassandra_Cache columnfamily bao gồm một cột Table, một cột Primary_Key, một cột Column_ID, một cột Last_Modified_Timestamp, một cột Last_Used_Timestamp, và một khóa tổng hợp bao gồm Table | Primary_Key | Column_ID. Cột Last_Modified_Timestamp biểu thị dấu thời gian last_modified của datum từ cơ sở dữ liệu nguồn và cột Last_Used_Timestamp biểu thị dấu thời gian mà tại đó mốc sử dụng cuối cùng được đọc/đọc bởi một công việc tổng hợp. Khi nút Cassandra yêu cầu dữ liệu từ một máy tính, máy tính toán resultset và sau đó có sự khác biệt thiết lập của resultset và bảng | key | cột trong Cassandra_Cache của nó và có Last_Modified_Timestamp giống như các hàng trong Cassandra_Cache của nó (nếu dấu thời gian không khớp thì dữ liệu được lưu trong bộ nhớ cache đã cũ và được cập nhật cùng với Last_Modified_Timestamp mới).Máy địa phương sau đó gửi sự khác biệt thiết lập cho nút Cassandra và cập nhật Cassandra_Cache của nó với sự khác biệt thiết lập và cập nhật Last_Used_Timestamp trên mỗi datum được lưu trữ đã được sử dụng để soạn resultset. (Một lựa chọn đơn giản hơn để duy trì một dấu thời gian riêng cho mỗi bảng | key | column là duy trì một dấu thời gian cho mỗi bảng | khóa, nhưng điều này ít chính xác hơn và bảng | key | cột dấu thời gian không quá phức tạp.) Giữ Last_Used_Timestamps trong đồng bộ giữa Cassandra_Caches chỉ yêu cầu các máy cục bộ và các nút từ xa gửi Last_Used_Timestamp được kết hợp với mỗi công việc, vì tất cả dữ liệu trong một công việc đều sử dụng cùng một Last_Used_Timestamp.

Nút Cassandra cập nhật kết quả của nó với dữ liệu mới mà nó nhận được từ bên trong nút và cũng với dữ liệu mà nó nhận được từ các nút khác. Nút Cassandra cũng duy trì một columnfamily lưu trữ cùng một dữ liệu nằm trong Cassandra_Cache của mỗi máy (ngoại trừ Last_Modified_Timestamp, chỉ cần trên máy cục bộ để xác định khi nào dữ liệu cũ), cùng với id nguồn cho biết dữ liệu đến từ bên trong nút hoặc từ nút khác - id phân biệt giữa các nút khác nhau, nhưng không phân biệt giữa các máy khác nhau trong nút cục bộ. (Một lựa chọn khác là sử dụng Cassandra_Cache hợp nhất thay vì sử dụng một Cassandra_Cache trên mỗi máy cộng với một Cassandra_Cache khác cho nút, nhưng tôi quyết định rằng độ phức tạp thêm không đáng để tiết kiệm không gian.)

Mỗi nút Cassandra cũng duy trì một Federated_Cassandra_Cache, bao gồm các bộ dữ liệu {Database, Table, Primary_Key, Column_ID, Last_Used_Timestamp} đã được gửi từ nút cục bộ đến một trong hai nút còn lại.

Khi một công việc đi qua đường ống, mỗi nút Cassandra cập nhật bộ đệm trong của nó bằng bộ kết quả cục bộ và hoàn thành các công việc phụ có thể được thực hiện cục bộ (ví dụ: trong một công việc để tổng hợp dữ liệu giữa nhiều nút, mỗi nút tổng hợp dữ liệu nút bên trong của nó để giảm thiểu lượng dữ liệu cần được đặt cùng trong liên kết nút liên) - một công việc phụ có thể được thực hiện cục bộ nếu nó chỉ sử dụng dữ liệu trong nút. Sau đó, nút quản lý sẽ xác định nút nào để thực hiện phần còn lại của công việc: mỗi nút Cassandra có thể tính toán chi phí gửi kết quả của nó đến một nút khác bằng cách lấy sự khác biệt của tập kết quả và tập hợp con của tập kết quả đã được lưu trong bộ nhớ cache theo với Federated_Cassandra_Cache của nó, và nút quản lý giảm thiểu phương trình chi phí ["chi phí để vận chuyển resultset từ NodeX" + "chi phí để vận chuyển resultset từ NodeY"]. Ví dụ, chi phí Node1 {3, 5} để vận chuyển kết quả của nó thành {Node2, Node3}, chi phí Node2 {2, 2} để vận chuyển kết quả của nó thành {Node1, Node3}, và chi phí Node3 {4, 3} để vận chuyển kết quả của nó thành {Node1, Node2}, do đó công việc được chạy trên Node1 với chi phí là "6".

Tôi đang sử dụng chính sách trục xuất LRU cho mỗi nút Cassandra; Ban đầu tôi sử dụng chính sách đuổi khỏi lâu đời nhất vì nó đơn giản hơn để thực hiện và yêu cầu viết ít hơn vào cột Last_Used_Timestamp (một lần cho mỗi lần cập nhật dữ liệu thay vì một lần đọc dữ liệu), nhưng việc thực hiện chính sách LRU hóa ra không quá phức tạp và Last_Used_Timestamp viết đã không tạo ra một nút cổ chai. Khi một nút Cassandra đạt tới 20% không gian trống, nó sẽ đưa ra dữ liệu cho đến khi nó đạt tới 30% không gian trống, do đó mỗi lần đuổi được xấp xỉ kích thước 10% của tổng không gian có sẵn. Nút duy trì hai dấu thời gian: dấu thời gian của dữ liệu trong nút cuối cùng được xóa bỏ, và dấu thời gian của dữ liệu liên kết/nút liên kết được xóa cuối cùng; do độ trễ của giao tiếp liên nút tăng tương đối so với giao tiếp giữa nút, mục tiêu của chính sách trục xuất để có 75% dữ liệu được lưu trong bộ nhớ cache là dữ liệu nút giữa và 25% dữ liệu được lưu trong bộ nhớ cache là dữ liệu trong nút , có thể nhanh chóng xấp xỉ bằng cách có 25% của mỗi lần trục xuất là dữ liệu liên nút và 75% của mỗi lần trục xuất là dữ liệu trong nút. Việc đuổi việc hoạt động như sau:

while(evicted_local_data_size < 7.5% of total space available) { 
    evict local data with Last_Modified_Timestamp < 
     (last_evicted_local_timestamp += 1 hour) 
    update evicted_local_data_size with evicted data 
} 

while(evicted_federated_data_size < 2.5% of total space available) { 
    evict federated data with Last_Modified_Timestamp < 
     (last_evicted_federated_timestamp += 1 hour) 
    update evicted_federated_data_size with evicted data 
} 

Dữ liệu bị xóa không được xóa vĩnh viễn cho đến khi nhận được sự thừa nhận từ các máy trong nút và từ các nút khác.

Nút Cassandra sau đó gửi thông báo tới các máy trong nút của nó cho biết last_evicted_local_timestamp mới là gì. Các máy địa phương cập nhật các Cassandra_Caches của chúng để phản ánh dấu thời gian mới và gửi thông báo đến nút Cassandra khi điều này hoàn tất; khi nút Cassandra đã nhận được thông báo từ tất cả các máy cục bộ thì nó sẽ xóa vĩnh viễn dữ liệu cục bộ bị loại bỏ. Nút Cassandra cũng gửi một thông báo đến các nút từ xa với last_evicted_federated_timestamp mới; các nút khác cập nhật Federated_Cassandra_Caches của họ để phản ánh dấu thời gian mới và nút Cassandra xóa vĩnh viễn dữ liệu được liên kết bị xóa khi nhận được thông báo từ mỗi nút (nút Cassandra theo dõi nút nào một phần dữ liệu đến, sau khi nhận được lệnh sự thừa nhận từ NodeX, node có thể xóa vĩnh viễn dữ liệu NodeX đã được gỡ bỏ trước khi nhận được một sự thừa nhận từ NodeY). Cho đến khi tất cả các máy/nút đã gửi thông báo của họ, nút Cassandra sử dụng dữ liệu đã được lưu trong bộ nhớ cache trong các truy vấn của nó nếu nó nhận được kết quả từ một máy/nút đã không loại bỏ dữ liệu cũ của nó. Ví dụ, nút Cassandra có bảng cục bộ | Primary_Key | Column_ID datum mà nó đã loại bỏ, và trong khi đó một máy cục bộ (không xử lý yêu cầu trục xuất) đã không bao gồm bảng | Primary_Key | Column_ID datum trong kết quả của nó vì nó nghĩ rằng nút Cassandra đã có dữ liệu trong bộ nhớ cache của nó; nút Cassandra nhận được resultset từ máy cục bộ, và vì máy cục bộ không thừa nhận yêu cầu trục xuất, nút Cassandra bao gồm dữ liệu được gỡ bỏ trong bộ nhớ cache của chính nó.

Các vấn đề liên quan