Tản mạn về scaling database
Tiếp theo chủ đề dọa ma với database của bài report tháng trước, kì này chúng ta cùng tìm hiểu một "ông kẹ" khác khá hay được anh em lôi ra khè nhau khi trà đá chém gió về làm database : Scaling. Cá rằng 99.99% số lập trình viên quần đùi chân đất, trong 1 ngày đẹp trời cao hứng ngồi thiết kế DB cho ...
Tiếp theo chủ đề dọa ma với database của bài report tháng trước, kì này chúng ta cùng tìm hiểu một "ông kẹ" khác khá hay được anh em lôi ra khè nhau khi trà đá chém gió về làm database : Scaling. Cá rằng 99.99% số lập trình viên quần đùi chân đất, trong 1 ngày đẹp trời cao hứng ngồi thiết kế DB cho một app / project bất kì, khi đem thiết kế đó ra cho thiên hạ chiêm ngưỡng, kiểu gì cũng có con giời lao vào chỉ chỏ hỏi han mấy câu đại loại kiểu : Thế mày đã tính đến chuyện sau này có 10 tỷ users chưa? Sau này to như google facebook rồi thì mày truy vấn dữ liệu hết mấy năm? ... Đại loại ta vẫn hiểu, scaling là khi cơ sở dữ liệu của ta phình to ra, phải optimize làm sao cho nó vẫn chạy ngon, và việc này rất khó. Thế nhưng optimize thì cụ thể là nó để phục vụ mục tiêu gì, có những tiêu chí nào, nó khó ra làm sao ... mấy câu hỏi kiểu thế, cũng ko dám chắc là trong giới quần đùi chân đất chúng ta, thằng nào cũng biết, hỏi phát trả lời được luôn. Thế nên trong bài viết này, chúng ta hãy cùng nhau mổ xẻ ông kẹ này một chút.
Thách thức của việc scaling
Disclaimer: Technically, "scaling" is not a single activity. But in this article, i will refer to it as such, for the sake of simplicity. When i said "scaling", please interpret it as a gross term, basically means that making a complex system ( in this case, mostly a database ) greater ( larger, faster ).
Nếu bạn đi hỏi anh Google, scaling là gì, tôi tin là sẽ có rất nhiều kết quả, rất nhiều bài viết ngắn có dài có, dễ hiểu có phức tạp có, của đủ các thể loại chuyên gia giải thích về vấn đề này. Bài viết này xin được phép mượn một ví dụ, mà tôi thấy thuộc hàng ngắn gọn và dễ hiểu nhất của tác Paul King , để minh họa.
Hãy tưởng tượng, chúng ta có một danh sách, với 10 cái tên. Đây chính là database của ta, trong tình trạng đơn giản gọn nhẹ nhất. Để tìm kiếm 1 cái tên trong danh sách này, ta chỉ việc đi từ đầu đến cuối, rất đơn giản và nhẹ nhàng phải không.
Giả sử bây giờ danh sách của chúng ta phình to ra thành 1 triệu cái tên, như một cuốn danh bạ điện thoại chẳng hạn. Lúc này nếu ta vẫn lò dò từng dòng một để tìm một cái tên thì đến mùa quýt không xong. Và đây chính là vấn đề đầu tiên gặp phải khi scaling một database : Khả năng tìm kiếm . Giải pháp cho vấn đề này có khá nhiều, tiêu biểu và đơn giản nhất chính là việc, giống như cuốn danh bạ điện thoại được sắp xếp theo thứ tự alphabet và đánh dấu chỉ mục, ta cũng đánh index cho database của mình.
Tiếp theo, tưởng tượng tiếp, danh sách của ta lúc này bỗng dưng trở nên nổi tiếng quá, cùng lúc có cả triệu người muốn đọc thì sao? Không lẽ lúc này bắt họ xếp hàng dài chờ đến lượt mình được vinh hạnh chiêm ngưỡng. Đây là vấn đề thứ hai gặp phải khi scaling một database : Tính đồng thời ( nghĩa là khả năng truy cập của nhiều đối tượng cùng một lúc ). Cũng có nhiều kĩ thuật để giải quyết vấn đề này, ví dụ như ta có thể in danh sách của ta ra 1 triệu bản để mọi người cùng đọc (replication), hay ta có thể phát 1 triệu bản in này đến tay từng người dùng, để giải quyết luôn cả vấn đề về Khả năng tìm kiếm, giúp họ tìm nhanh hơn (distributed).
Tiếp tục nghĩ ra kịch bản chơi khó anh em, giờ nếu có người trong cái danh bạ của ta bỗng nhiên thay đổi số điện thoại thì sao. Dĩ nhiên là ta phải update lại danh bạ này rồi, nhưng làm sao để thực hiện đồng thời trên 1 triệu bản in mà ta đã phát ra, để đảm bảo là không xảy ra trường hợp có người truy vấn dữ liệu và được trả về dữ liệu cũ, đã out-of-date. Đây là một vấn đề nữa cần giải quyết khi tính đến bài toán scaling: Tính toàn vẹn. Tất nhiên là cũng sẽ có cách để giải quyết bài toán này. Ví dụ như ta thu lại cả 1 triệu bản in kia, không cho bố con thằng nào xem nữa, rồi update danh bạ, in ra 1 triệu bản mới phát cho bà con. Và tất nhiên, nếu bạn đã đọc đến đoạn này của bài viết rồi, thì chắc bạn cũng đã nhận ra một motive quen thuộc: mấy giải pháp kiểu này giải quyết được một vấn đề thì sẽ sinh ra tỉ vấn đề khác. Lock database kiểu kia thì trong thời gian đấy sẽ không ai truy vấn được, nghĩa là ảnh hưởng đến Avaiability. Một giải pháp khác là thay vì in lại tất, thì ta làm một phần phụ lục bổ sung cho cuốn danh bạ của mình. Phụ này sẽ rất nhỏ, chỉ bao gồm thông tin về những dữ liệu thay đổi mới được cập nhật, phát hành đồng thời đến tất cả các user, như thế sẽ đảm bảo được việc dữ liệu ko bị out-of-date, đồng thời user luôn luôn có thể truy vấn dữ liệu ( cách làm này là thêm change logs ), nhưng như thế thì sẽ làm giảm khả năng tìm kiếm , vì bây giờ sau khi đọc dữ liệu từ database, ta còn phải truy vấn đến cả change logs nữa. Một cách làm khác nữa là tạo ra nhiều versions, và đến mỗi thời điểm nhất định, ta sẽ chuyển từ version này sang version khác. Ta đang dùng danh bạ version 1, đến 12:00 ta tạo ra một danh bạ mới, gọi là version 2, và quy định từ 12:01, tất cả users sẽ truy vấn đến danh bạ version 2. Cách làm này giải quyết được vấn đề Khả năng tìm kiếm và Tính đồng thời, nhưng rõ ràng là dữ liệu khi này sẽ không thể đảm bảo luôn là mới nhất.
Nếu chưa đủ đau đầu, thì hãy coi như ta đã giải quyết bài toàn khi có user thay đổi dữ liệu xong rồi, nhưng lúc này , mỗi phút, có cả ngàn user cùng thay đổi dữ liệu một lúc thì sao ? Khi này, tiềm tàng nguy cơ, khi áp dụng giải pháp hoàn hảo của ta trên quá nhiều user cùng một lúc , dẫn tới tình trạng cạnh tranh tài nguyên ( Resource contention - Là tình trạng xảy ra conflict khi có quá nhiều request cùng tìm cách truy cập đến những tài nguyên dùng chung như RAM, bộ nhớ cached... ), dẫn tới điều kiện cạnh tranh ( Race conditions - là khi output của ta bị phụ thuộc vào những yếu tố không thể dự đoán như timing, thứ tự input... khiến cho việc dự đoán kết quả output là không thể ), hay tình trạng khóa cứng ( Deadlocks - là khi có 2 hay nhiều hành động cùng chờ nhau thực thi trước, dẫn tới việc cả hệ thống bị dừng lại ).
Đến đây, có lẽ không cần bàn thêm tới những tình huống khó xoay xở hơn nữa, ví dụ như khi phần cứng cơ sở dữ liệu của ta là phân tán (distributed data centers).
Tổng kết lại
TL;DR cho các thanh niên lười:
-
Về cơ bản, scaling là vấn đề đặt ra khi database của ta được mở rộng về quy mô, làm sao để performance phải không bị ảnh hưởng, hoặc ảnh hưởng ở mức tối thiểu có thể.
-
Mục tiêu lí tưởng cần đạt tới của một database bất kì,là làm sao để bất kì lúc nào truy vấn đến database của ta cũng chỉ trả về một bộ dữ liệu duy nhất, tại mỗi thời điểm duy nhất, chỉ có thể có một người có thể thay đổi dữ liệu, tất cả mọi người đều truy cập đến dữ liệu cập nhật mới nhất, và việc truy vấn dữ liệu trả về tức thì. Quy gọn lại thì có thể rút ra 3 tiêu chí đánh giá một database là Khả năng tìm kiếm( Luôn luôn có thể truy cập dữ liệu, tốc độ thực hiện truy vấn ), Tính toàn vẹn( Dữ liệu mà mỗi user truy cập đều là giống nhau ), và Tính đồng thời( Khi có update, việc update dữ liệu xảy ra đồng thời )
-
Tất cả các vấn đề riêng rẽ của scaling đều có phương án giải quyết. Cái khó khi scaling là các giải pháp này thường chỉ tối ưu được một hay một vài tiêu chí, trong khi đánh đổi việc các tiêu chí khác bị ảnh hưởng. Scaling vì thế không có phương nào là "tốt nhất", mà tùy tình hình, người thiết kế database sẽ phải đánh giá, mình cần tiêu chí nào hơn để tối ưu hóa tiêu chí đó.