#179 - Kiến trúc đơn hướng trong lập trình giao diện
Những bài viết hay
Building Data Platforms I The ETL bias — medium.com
Bạn đã bao giờ tự hỏi một Data Engineering team được hình thành như thế nào và mục đích của team đó là gì? Khi nào một Data Engineering team sẽ giúp ích gì cho việc phát triển của một business, đặc biệt khi đó là một data-driven business?
Trước khi bàn luận đến việc xây dựng một Data platform, hãy bàn luận đến một thuật ngữ phổ biến lâu đời hơn, đó là ETL. ETL là viết tắt của Extract, Transform and Load và trở nên khá phổ biến vào những năm 1970. Trong thập kỷ đó, các công ty bắt đầu có nhiều kho dữ liệu và muốn lưu giữ thông tin liên quan ở một nơi để phân tích. ETL đã trở thành tiêu chuẩn thực tế để thực hiện các loại hành động này.
Đó là định nghĩa về ETL. Vậy đâu là Data Product đầu tiên mà một công ty cần? Rất đơn giản, công ty cần thu thập các metrics và KPI về sản phẩm chính đã được triển khai. Chúng ta chưa cần bàn luận về Data platform, đây chỉ đơn thuần là cho phép mọi người trong công ty sử dụng dữ liệu để phân tích. Ở giai đoạn này, thông thường domain của sản phẩm chỉ ở mức giới hạn với ít các tính năng, lượng giữ liệu nhỏ, phát triển trên nền kiến trúc monolith hoặc ít các components. Ở giai đoạn này, mọi người có thể happy với một Data Product xây dựng trên nền các ETL đơn giản.
Tuy nhiên khi sản phẩm ngày càng phát triển, với nhiều các tính năng, nhiều domain, xây dựng trên nền kiến trúc Microservices với nhiều team product phát triển hơn. Các teams có thể quyết định điều chỉnh các công nghệ databases khác nhau vì chúng phù hợp với domain và phạm vi mà hệ thống của họ hoạt động. Vì các hệ thống giao tiếp qua API nên không có vấn đề gì khi một service sử dụng một công nghệ database khác. Một thời gian sau khi công ty quyết định phá vỡ kiến trúc monolith, các kỹ sư có thể deliver code nhanh hơn với nhiều các tính năng mới hơn và hoạt động ổn định. Nhiệm vụ chuyển đổi sang Microservices đã thành công!
Vậy khi đó Data Product chúng ta xây dựng ban đầu sẽ ra sao sao? Trong một khoản thời gian, các ETL của công ty đã phát triển và trở nên phức tạp hơn. Thay vì phải truy vấn một cơ sở dữ liệu để tìm nạp dữ liệu, ETL phải kết nối với nhiều nguồn dữ liệu. Bên cạnh sự gia tăng rõ ràng về độ phức tạp này, có nhiều vấn đề khác phát sinh như Ownership của các ETL khi sản phẩm có nhiều domains và teams phát triển hơn, sự đa dạng trong việc sử dụng các database khác nhau giữa các teams, tính ổn định của các ETL pipelines khi schema của input datasource của thể thay đổi bất cứ lúc nào, các ETL sẽ break thường xuyên hơn và các kỹ sư cần dành nhiều thời gian hơn để sửa chữa chúng, ngoài ra các kỹ sư cho rằng logic truy vấn nguồn dữ liệu quá phức tạp và họ thiếu kiến thức sâu về một cơ sở dữ liệu cụ thể, và rồi mọi người trong công ty sẽ liên tục phàn nàn về một điều, đó là MISSING DATA ... Có quá nhiều vấn đề mới phát sinh và giải pháp chính là: xây dựng một Data Engineering team.
Hẳn chúng ta đã từng nghe một câu rằng "Data is the New Oil". Tuy nhiên, các bạn có thể không lạ lẫm một điều rằng, ở nhiều công ty Data Engineering thường không được coi là "First class citizen". Nhiều công ty thường tập trung nhiều nguồn lực hơn để xây dựng nhiều tính năng mới cho sản phẩm, không quan tâm nhiều về các vấn đề liên quan đến data để tránh làm chậm quá trình phát triển sản phẩm. Nếu data là tài sản quý giá nhất trong một công ty, thì có hợp lý không khi chỉ có một nhóm chịu trách nhiệm về nó?
Để các công ty thành công và sử dụng data hiệu quả cho lợi thế của họ, có lẽ họ cần phải thay đổi tư duy của mình. Trong những năm gần đây, Agile và DevOps đã mang lại nhiều thay đổi, giúp cải thiện hiệu quả 2 vấn đề quan trọng là phát triển software và xây dựng có sở hạ tầng. Ngày nay, chúng giúp giải quyết thêm một vấn đề nữa, đó là Data.
VALORANT's 128-Tick Servers — technology.riotgames.com
Valorant là một game của Riot Games, đạt được 14 triệu active user hằng tháng chỉ sau 1 năm ra mắt. Trong 1 trận đấu, 10 người chơi với 10 client sẽ kết nối với 1 server để xử lí và đồng bộ các sự kiện trong game. Để đảm bảo tính chân thật và công bằng của game, với tốc độ xử lí của client ngày càng tăng, server yêu cầu phải phải đạt tốc độ xử lí 128 khung hình trên giây (128-tick), tức là 1 khung hình trong 7.8125 ms. Kể cả với nguồn đầu tư lớn, ngân sách chỉ cho phép 1 core cpu cho ít nhất 3 game, tức 1 khung hình phải xử lí trong tối đa 2.6ms, chưa kể 10% overhead của hệ điều hành, scheduling và các phần mềm khác. Đội ngũ Valorant bắt đầu chặn đường tối ưu hoá từ mốc 50ms, và cuối cùng đạt được tốc độ 2ms/khung hình, tất cả nhờ tối ưu hoá code, chỉnh sửa phần cứng và điều chỉnh hệ điều hành. Đội ngũ chia vấn đề thành nhiều phần nhỏ hơn để tối ưu như:
Replication: Hiện đội ngũ sử dụng Property Replication của Unreal Engine 4, tự động đồng bộ trạng thái giữa server và các client mà không phải code nhiều. Tuy nhiên cơ chế của nó khá chậm vì hoạt động theo kiểu "polling", phải quét qua tất cả variable được đánh dấu trong từng khung hình, sau đó so sánh với data từ 10 client, đồng bộ, đóng gói và sau đó gửi đến client. Cách sửa là thay thế bằng mô hình "push", chỉ server gọi đến client khi có sự kiện thay đổi. Trong nhiều trường hợp, cách tối ưu này giúp tăng hiệu suất từ 100 đến 10000 lần!
Animation: Mặc dù không có giao diện, nhưng để biết một phát bắn có trúng hay không, server cũng phải render animation và hitbox, tốn rất nhiều chi phí ở server. Đội ngũ tối ưu bằng cách chỉ render animation mỗi 4 khung hình, giảm 75% chi phí, và ngừng render hitbox khi người chơi đang chế độ mua hàng, giảm thêm 33% chi phí
Tối ưu chip, đổi từ Intel Xeon E5 -> Intel Xeon Scalable để tận dụng cơ chế non-inclusive cache, tăng hiệu suất 30%
Điều chỉnh cấu hình phần CFS của Linux, làm cho các process khi phải chờ 1 core xử lí thì lập tức chuyển sang core khác với độ trễ 0ms thay vì 0.5ms như thông thường, giúp tăng 4% hiệu suất.
Tận dụng công nghệ hyperthreading của chip, giúp một core vật lý có thể host 2 thread cùng lúc.
Trong bài viết, tác giả có miêu tả rõ hơn chi tiết quá trình phân tích, lí do dẫn đến các quyết định và bài học rút ra trong chặn đường tối ưu.
Unidirectional User Interface Architectures - André Staltz — staltz.com
Đây là một bài viết giới thiệu về Unidirectional User Interface Architectures (kiến trúc đơn hướng trong lập trình giao diện), bao gồm các định nghĩa cũng như các kiểu kiến trúc phổ biến như: Flux, Redux, The Elm Architecture, MVI... Tác giả đưa ra một cái nhìn tổng quát cũng như những sự khác biệt và đặc thù của mỗi kiểu kiến trúc. Cuối bài tác giả đưa ra một kiểu kiến trúc mới cũng như cách nhìn của bản thân về "the best user interface architecture". Sau đây là tóm tắt về những kiểu kiến trúc được liệt kê trong bài:
Flux: Một trong những unidirectional architecture đầu tiên, được thiết kế và phát triển dành riêng cho React.
Redux: Biến thể phổ biến nhất của Flux, không còn phụ thuộc vào react và có thể áp dụng cho mọi UI framework.
Best: Được bắt nguồn từ MVC, nơi mà ở đó Controller được chia thành hai thành phần đơn hướng: Behavior và Event.
Model-View-Update (MVU): Hay còn được gọi là Elm architecture, MVU có nhiều điểm tương đồng với Redux chủ yếu vì Redux đã được lấy cảm hứng từ chính Elm architecture. Đây là một kiểu kiến trúc lập trình hàm (functional programming) thuần tuý bởi nó được phát triển trên ngôn ngữ Elm - một ngôn ngữ lập trình hàm cho lập trình Web.
Model-View-Intent (MVI): Được giới thiệu là một reactive unidirectional architecture thuần tuý dựa trên RxJs (nhưng hoàn toàn có thể sử dụng với bất cứ ngôn ngữ nào hỗ trợ observable streams pattern). Với architecture này tất cả mọi thành phần đều là Observable event stream. Một View với MVI đơn giản là một function có input là một stream of user events và output là một stream of View.
Nested Dialogues: Một unidirectional architecture mới được xây dựng trên MVI. Bắt đầu với việc coi mỗi một MVI function là một dialogue, tác giả đã khái quát hoá khái niệm Dialogue vượt qua biên giới của một UI program thuần tuý. Ví dụ như một Dialogue có thể là một program tương tác giữa users và http sever, ở đây Dialogue sẽ nhận input là một stream của user events và output là một stream của Http Responses.
Góc Distributed System
Thiết kế hệ thống distributed priority queue của Facebook
Bài viết tuần trước về Async@Facebook có đề cập tới việc sử dụng hệ thống priority queue để phân phối job tới các worker, tuần này góc DS giới thiệu cụ thể hơn về hệ thống Queue đó. Hệ thống này có tên là Facebook Ordered Queueing Service (FOQS), là hệ thống queue tập trung phục vụ cho các dịch vụ backend của Facebook, cũng là phần lõi của hệ thống Async.
Một số điểm trong thiết kế của FOQS bao gồm:
Sử dụng MySQL shared để lưu trữ item.
Sử dụng một hệ thống tập trung khác là Facebook Shard Manager để quản lý bài toán sharding.
Sử dụng Thrift để giao tiếp với các dịch vụ backend.
Phía producer: khi enqueue một item, client cần thiết lập thuộc tính cho item bao gồm:
Namespace và topic: là logical group mà item đó thuộc về.
Priority: số nguyên 32 bit mô tả độ ưu tiên của item. Giá trị càng thấp độ ưu tiên càng cao.
Metadata và Payload của item: dạng binary. Producer đặt bất kì thông tin gì vào theo nhu cầu.
Dequeue delay: client chỉ định rõ thời gian tối đa delay trong queue (hiểm nôm na là deadline cần phải dequeue).
Lease duration: khoảng thời gian từ lúc item được dequeue tới khi phía consumer phản hồi lại đã xử lý item thành công. Nếu không nhận được phản hồi, queue sẽ tiến hành gửi lại item.
FOQS unique ID định danh cho item.
TTL: là thời gian tối đa item được tồn tại trong queue, nếu vượt quá nó sẽ bị xoá vĩnh viễn.
Phía consumer: khi lấy item từ queue sẽ gọi qua API Dequeue với 2 tham số: topic và count. Queue sẽ trả về tối đa count items trong topic đó. item được lấy theo thứ tự ưu tiên như sau:
Lấy các item có Priority thấp nhất, tương ứng mức độ ưu tiên cao nhất.
Đối với những item cùng Priority, lấy những item có Dequeue Delay thấp nhất (sát với deadline cần phải xử lý nhất).
Hai thuộc tính Priority và Dequeue delay thể hiện rõ cách tính mức độ ưu tiên của một item trong FOQS.
Khi consumer xử lý item, nó cần phản hồi lại cho FOQS biết item được xử lý thành công hay không, để FOQS xác định có cần phải gửi lại item hay không qua hai bản tin ACK/NACK. Kỹ thuật cụ thể xử lý bản tin ACK/NACK với các sharded instances bạn tìm hiểu kĩ hơn trong bài viết.
Để xử lý truy vấn API Dequeue, FOQS thực hiện thao tác Reduce (do mỗi FOQS host sẽ được map vào một số lượng sharded MySQL nhất định) trên các hosts để tìm ra items có độ ưu tiên cao nhất và lưu chúng vào một cấu trúc dữ liệu là Prefetch Buffer; từ đó trả kết quả cho API. Để tối ưu tốc độ truy vấn, mỗi shard sẽ duy trì in-memory danh sách index của các primary keys của các items nó quản lý, sắp xếp theo priority. Các Dequeue Worker sẽ tổng hợp thông qua k-way merge và select trên những index đó. Prefetch Buffer được cập nhật liên tục theo thời gian, các topics được lấy items ra sẽ được làm đầy bằng các items tiếp theo.
Một số vấn đề khác về thiết kế FOQS: Pull/Push model, Checkpointing, Disaster Readiness bạn tham khảo bài viết chi tiết ở đây
Góc Lập Trình
Đề ra tuần này:
Cho một mảng các số nguyên arr
và một số k
với 1<= k <= arr.size()
.
Hãy tính giá trị lớn nhất của các mảng con có độ dài là k
.
Ví dụ với mảng arr = [10, 5, 2, 7, 8, 7], k = 3
, chúng ta sẽ có kết quả: [10, 7, 8, 8]
Giải thích:
10 = max(10, 5, 2)
7 = max(5, 2, 7)
8 = max(2, 7, 8)
8 = max(7, 8, 7)
Các bạn có thể thử sức tại đây:
Lời giải tuần trước:
Ta có thể dễ dàng nhận thấy, số đường đi từ điểm xuất (0, 0) tới đích (m-1, n-1) bằng tổng số đường đi từ điểm xuất phát tới ô bên trái và phía trên của đích. Ta có thể giải bài toán phương pháp đệ quy như sau:
def uniquePaths(m:Int, n:Int):Int={
// base case
if(m==1 || n==1) return 1
uniquePaths(m-1, n) + uniquePaths(m, n-1)
}
Để giảm chi phí tính toán của chương trình, ta có thể tối ưu bài này bằng phương pháp quy hoạch động như sau:
def uniquePaths(m:Int, n:Int):Int={
val dp= Array.fill(m, n)(1) for(r<-1 until m){
for(c<-1 until n){
dp(r)(c)= dp(r)(c-1)+dp(r-1)(c)
}
} dp(m-1)(n-1)
}
Các bạn có thể thử làm bài này tại đây.
Code & Tools
This Week Sponsors
Discover your career at KMS with many available jobs, especially with a 1-month joining bonus: CLICK HERE.
More surprisingly, your next move could be even greater when referred by your buddies who are working at KMS:
One-month joining bonus
FULL 13th-month salary
Cut off HR screening and phone interview
Only 1 all-included interview
Employment results within 3 working days
We hope it impresses you in case you’re considering KMS Technology as your next career destination or you know someone who does.
Quotes
Any fool can write code that a computer can understand. Good programmers write code that humans can understand.
- Martin Fowler