2 điểm bởi GN⁺ 2023-08-12 | 1 bình luận | Chia sẻ qua WhatsApp
  • Nút thắt cổ chai Postgres của một ứng dụng SaaS đơn lẻ trở nên nghiêm trọng, khiến mức sử dụng CPU duy trì ở 60~80% và có lúc tăng lên 100%, dẫn đến một sự cố ngắn
  • Mở rộng theo chiều dọc bằng cách chuyển sang instance DB lớn hơn đã chạm giới hạn, nên rất khó tiếp tục hấp thụ thêm tải theo cùng cách đó
  • Ghi sharding và microservices có thể nâng cao dung lượng và độ linh hoạt vận hành, nhưng vẫn liên tục để lại chi phí phức tạp từ backup, giám sát, migration, ORM cho tới topology mạng
  • Trên thực tế, trong 3 tháng nhóm đã loại bỏ các truy vấn nặng, tối ưu mã Rails, tinh chỉnh Postgres và tách một số truy vấn chỉ đọc đắt đỏ sang DB bản sao
  • Kết quả là mức sử dụng CPU DB cao nhất theo tuần giảm từ 90% xuống 30%, tạo thêm dư địa để tận dụng hệ thống hiện tại lâu hơn trước khi chuyển sang kiến trúc thế hệ tiếp theo

Nút thắt Postgres và điểm kết của mở rộng theo chiều dọc

  • Khi tải của một ứng dụng SaaS đơn lẻ tăng lên, hiệu năng Postgres trở thành nút thắt cổ chai chính
    • Mức sử dụng CPU duy trì trong khoảng 60~80%
    • Ít nhất một lần đã tăng vọt lên 100%, gây ra một sự cố ngắn
  • Trước đây, mỗi khi DB trở nên quá bận, nhóm lại thay bằng một instance lớn hơn để mua thêm thời gian
    • Cách này giúp có thể tập trung vào những việc khác như phát triển tính năng
    • Khi đó họ đã dùng instance lớn nhất, nên không thể mở rộng theo chiều dọc thêm nữa

Kiến trúc thế hệ tiếp theo hấp dẫn nhưng đắt đỏ

  • Những lựa chọn chính được thảo luận là ghi shardingmicroservices
  • Ghi sharding là cách đặt các cụm DB độc lập và ghi dữ liệu vào một DB cụ thể theo chiến lược phân vùng
    • Về mặt tiềm năng, nó có thể tăng dung lượng lên 2~3 bậc độ lớn
  • Microservices chia monolith thành nhiều dịch vụ, mỗi dịch vụ có kho dữ liệu riêng
    • Có thể chọn kho dữ liệu phù hợp với workload của từng dịch vụ
  • Cả hai cách đều mở rộng lựa chọn về khả năng chịu lỗi và độ phục hồi vận hành, nhưng mục tiêu trước mắt là đưa hiệu năng DB về trạng thái có thể kiểm soát

Phức tạp một khi đã đưa vào sẽ liên tục tạo chi phí

  • Sự gia tăng phức tạp không chỉ dừng ở chi phí triển khai cấu trúc mới mà còn kéo theo chi phí chú ý về sau
  • Nếu chọn sharding DB, mọi quyết định kỹ thuật trong tương lai đều phải xử lý thêm lớp phức tạp mới
    • Backup
    • Giám sát
    • Migration
    • ORM
    • Topology mạng
  • Microservices cũng tạo ra gánh nặng tương tự; việc duy trì kiến trúc bổ sung có thể làm chậm hoặc khiến phải từ bỏ việc chuyển giao tính năng

Trước tiên tìm dư địa trong hệ thống hiện tại

  • Ngay cả khi có vẻ cần một cuộc chuyển đổi kiến trúc lớn, hệ thống hiện có thường vẫn còn dư địa bổ sung
  • Chỉ bằng điều chỉnh workload, tinh chỉnh hiệu năng và thêm các hệ thống phụ trợ, có thể mua thêm vài tháng hoặc vài năm
  • Nếu các lựa chọn này khả thi, đáng để thử chúng trước khi xây dựng hẳn một hệ thống thế hệ tiếp theo

Các tối ưu hóa đã thực hiện trên thực tế

  • Ở công việc đầu tiên, hai kỹ sư đã dành khoảng 3 tháng chủ yếu để xử lý các vấn đề hiệu năng DB
    • Không có một mẹo thần kỳ duy nhất nào
    • Dùng telemetry để tìm các truy vấn nặng
    • Xác định nơi phát sinh truy vấn trong codebase Rails
    • Tối ưu hoặc loại bỏ truy vấn
    • Tinh chỉnh nhiều thiết lập Postgres
  • Ở công việc thứ hai, hai kỹ sư khác đã sửa codebase để một số truy vấn chỉ đọc đắt đỏ chạy trên DB bản sao
    • Tách truy vấn SELECT xuất hiện thường xuyên nhất do cơ chế polling của web client gây ra khỏi DB chính

Kết quả và nguyên tắc vận hành

  • Gộp hai công việc lại, mức sử dụng CPU DB cao nhất theo tuần đã giảm từ 90% xuống 30%
    • Mức sử dụng CPU cao nhất theo tuần: {l:90,30}
  • Dư địa CPU tăng lên đáng kể, và khả năng giảm tải khỏi DB chính cũng lớn hơn nhiều
  • Khi chạm vào nhiều phần của codebase và có nhiều lập trình viên cùng cộng tác, kiến thức phân tán về hệ thống hiện tại cũng được tích lũy
  • Phức tạp không phải lúc nào cũng xấu, và đến một lúc nào đó vẫn sẽ phải chuyển sang cấu trúc phức tạp hơn trước khi chạm tới giới hạn căn bản của kiến trúc DB
  • Nhưng cho đến trước thời điểm đó, nếu vắt kiệt hệ thống hiện tại trước, bạn có thể làm việc lâu hơn với một hệ thống nhàm chán và đơn giản nhất có thể, điều này cũng có lợi hơn về chi phí và tính thực dụng

1 bình luận

 
GN⁺ 2023-08-12
Các ý kiến trên Hacker News
  • Nhìn vào các vấn đề hiệu năng cơ sở dữ liệu, suy nghĩ nóng nhất của tôi cho các dự án mới là: hãy thiết kế sao cho hot path của ứng dụng hoàn toàn không dùng join
    Lưu trữ rẻ, nên cứ phi chuẩn hóa toàn bộ và cập nhật tất cả trong transaction là được. Thật sự đáng kinh ngạc khi bỏ join đi thì nhanh hơn đến mức nào. Các truy vấn phân tích ad-hoc có thể được sao chép sang một cơ sở dữ liệu khác dành cho phân tích
    Tôi có cảm xúc phức tạp với DynamoDB của Amazon, nhưng điểm rằng muốn dùng đúng thì trước hết phải lên kế hoạch mẫu sử dụng rồi mới xác định schema là điều đáng mang sang cả cơ sở dữ liệu quan hệ. Dạo này tôi thậm chí cho rằng nếu không nhằm mục đích phân tích thì join là không cần thiết. Các cơ sở dữ liệu chính đều có thuộc tính ACID, còn lưu trữ thì rẻ đến mức khó tin, nên cứ phi chuẩn hóa là xong
    Để tránh hot partition, tốt hơn nên dùng thứ gì đó gần với UUID thay vì số nguyên. Không phải thuốc chữa bách bệnh và cũng có nhược điểm, nhưng bạn có thể quen với hiệu năng “luôn ổn” có thể mở rộng ngang, thay vì hiệu năng tuyệt vời của số nguyên rồi một ngày nào đó sẽ sụp đổ
    Một suy nghĩ còn nóng hơn nữa là đánh index cho mọi cột, nhưng đó là chủ đề để bàn vào ngày khác

    • Thành thật mà nói tôi hoàn toàn không đồng ý. Khi xây startup, trong 1–5 năm đầu chúng tôi gần như không quan tâm đến hiệu năng; đến năm thứ 6, một vấn đề hiệu năng ở một bảng cụ thể bùng phát và chúng tôi tối ưu nó bằng vài tháng công sức của các kỹ sư
      Nhìn từ góc độ nợ kỹ thuật, nếu cố làm mọi thứ nhanh ngay từ đầu thì chi phí sẽ cao hơn nhiều. Tốc độ phát triển sẽ chậm đi đáng kể, và có khả năng chúng tôi đã thất bại trong vài cuộc khủng hoảng
      Thay vào đó, chúng tôi trả thêm vài nghìn đô la mỗi tháng cho chi phí máy móc so với nhu cầu thực tế, và tiết kiệm được vài tháng nhân lực vào thời điểm chưa tuyển đủ kỹ sư và chi phí cơ hội của phát triển tính năng rất lớn. Vì không thể biết trước đâu sẽ là nút thắt cổ chai, để làm mọi thứ nhanh ngay từ đầu có lẽ cần nhiều hơn 10–20 lần công việc. Một số nút thắt khá bất ngờ
      Join có thể gây hại ở quy mô lớn, nhưng phần lớn startup ít nhất là giai đoạn đầu không có vấn đề về quy mô. Phi chuẩn hóa có thể là một tối ưu hóa tốt, nhưng bạn phải trả chi phí tốc độ là đồng bộ mọi bản sao mỗi khi có thay đổi. Sẽ có ai đó tạo ra lỗi không cập nhật một trường phi chuẩn hóa không phải nguồn chính, khiến người dùng thấy dữ liệu cũ. Thông thường, dùng join rồi tối ưu sau bằng read-aside cache hoặc tương tự sẽ rẻ hơn về tổng chi phí so với việc bẻ cong schema
    • Nếu join được index đúng cách và diễn ra trong cùng một server, nó không nên ảnh hưởng lớn đến hiệu năng. Câu “bỏ join đi thì nhanh hơn nhiều” là không đúng nếu dùng join đúng cách
      Ngược lại, ghi một dữ liệu vào 20 nơi thay vì 1 nơi thì chậm hơn nhiều về hiệu năng, truy vấn cũng cực kỳ phức tạp và dễ phát sinh lỗi. Sẽ có chuyện cập nhật 18 nơi nhưng quên mất 2 nơi
      Người ta nêu lưu trữ rẻ như lợi thế của phi chuẩn hóa, nhưng lưu trữ ở đây là vấn đề nhỏ nhất. Bề mặt lỗi lớn hơn rất nhiều và hiệu năng ghi kém mới là vấn đề thật sự, và hiệu năng ghi đó rất dễ ăn mòn cả hiệu năng đọc
    • Có các ứng dụng “cao (tall)” và ứng dụng “rộng (wide)”. Hầu hết mọi lời khuyên về thiết kế và tối ưu cơ sở dữ liệu đều dành cho ứng dụng cao
      Ứng dụng cao về cơ bản chỉ làm một việc, những thứ còn lại bổ trợ cho việc đó. Phần lớn các Big Tech mà bạn có thể nghĩ đến thuộc loại này. Trong mô hình dữ liệu chỉ có vài khái niệm vận hành thật sự quan trọng
      Facebook thực chất chỉ có người, bài đăng và quảng cáo. Netflix thì khoảng tài khoản và chương trình. Sản phẩm Amazon cốt lõi là người bán, người mua, hàng hóa, và có thể thêm vài thứ hậu cần phía sau
      Lý do có nhiều ứng dụng cao là vì chúng dễ. Chúng dễ hơn nhiều so với ứng dụng rộng thường được gọi là “enterprise”. Lý do phần mềm enterprise tệ là vì nó khó, và đây là vùng ít được khám phá nhất cũng như có cơ hội khổng lồ. Những người chơi hiện hữu như Oracle đang làm rất tệ ở đây, và nếu bạn bước vào với tư duy kiểu ứng dụng cao thì bạn cũng sẽ như vậy
      Những lời khuyên như “tuyệt đối đừng dùng join”, “thiết kế xoay quanh một bảng duy nhất” có lý với ứng dụng cao, nhưng là lời khuyên khủng khiếp với ứng dụng rộng. Thỉnh thoảng có thể thấy các công ty ứng dụng rất cao thất bại thảm hại khi thử làm việc ngoài năng lực cốt lõi, vì họ đầy những người tôn sùng các lời khuyên này
      Lời khuyên này dành cho các công ty đã thành công, đang làm việc dễ và đã hái hết những quả thấp. Ngay cả các ứng dụng cao chưa trở thành nạn nhân của thành công cũng không cần xẻ nát mô hình dữ liệu vì hiệu năng. Chỉ những công ty đã thành công khổng lồ và đang vắt kiệt giọt hiệu năng cuối cùng mới bận tâm đến chuyện đó, mà chính những công ty ấy lại là nơi ít cần lời khuyên nhất. Các lời khuyên xoay quanh ứng dụng cao kiểu “FAANG làm vậy thì bạn cũng nên làm”, “nếu có 1 tỷ người dùng thì sao?” làm ô nhiễm đầu óc của những người muốn làm việc thú vị hơn là hiển thị quảng cáo cho hàng tỷ người
    • Trong vài năm qua, tôi thấy nhiều nỗi đau do các nhà phát triển dựa vào phi chuẩn hóa hơn là các vấn đề hiệu năng join trên bảng lớn
      Với các join lớn, về sau vẫn có cách sửa hiệu năng như đẩy vào materialized view hoặc ETL sang kho lưu trữ dạng cột. Nhưng nếu ai đó sao chép cột subtotal_cents vào các model Order, Invoice, Payment, NotificationEmail, UserProfileRecentOrders, rồi để 296 nơi tham chiếu hoặc cập nhật nó, thì con đường quay lại trạng thái lành mạnh sẽ rất dài
    • Tôi hầu như chưa từng thấy bản thân join là nút thắt hiệu năng. Nếu có một nguyên tắc duy nhất mà tôi quan sát được thì đó là “đừng làm bảng quá lớn
      Thường vấn đề phát sinh ở các bảng có tính chất bản ghi lịch sử. Dữ liệu cần cho vận hành hằng ngày chỉ là một phần rất nhỏ trong bảng thực tế, nhưng thao tác trên một bảng khổng lồ thì kiểu gì cũng chậm, bất kể có index nào. Bản thân việc thêm nhiều index hơn cũng trở thành vấn đề
      Ít nhất trong cơ sở dữ liệu quan hệ truyền thống, chỉ đánh index cho mọi cột là chưa đủ; cần tạo đúng index tổ hợp cột có thể được sử dụng. DynamoDB có thể khác
  • Hãy vắt kiệt tối đa những gì mình có, và sau khi đã vắt kiệt một thời gian, hãy thay đổi cách nhìn nhận vấn đề; khi vắt thêm ở chỗ này, chỗ kia, rồi cả chỗ nữa, bạn sẽ đột nhiên nhận ra vẫn còn rất nhiều thứ còn lại
    Tôi đã tối ưu một monolith khổng lồ trong khoảng hai tháng: từ mức dưới 2.000 RPS, nơi PM và đội ngũ nghĩ rằng không còn gì để vắt ra nữa, lên dưới 3.200 RPS nhờ thay phần cứng; chỉnh thêm vài ngày lên 4.000 RPS; cố thêm một chút lên 10.000 RPS; rồi khoảng một tuần sau lên 40.000 RPS
    Có người nói “thế này là đủ rồi, không cần đi xa hơn nữa”, nhưng sau khi thay đổi khá nhiều thứ, nó nhảy lên hơn 2 triệu RPS trên một máy duy nhất, và một tháng sau thì xử lý ổn định hơn 40 triệu RPS với độ trễ thấp. Vẫn còn chút dư địa để đẩy tiếp
    Hiện tại chúng tôi thậm chí chưa dùng đến 5% năng lực có thể khai thác. Chỉ riêng việc thay đổi cách nghĩ về vấn đề đã tạo ra mức thay đổi như vậy. Việc chuyển từ máy chủ cũ sang máy chủ mới chỉ giúp tăng từ 1.800 RPS lên nhỉnh hơn 3.000 RPS, chỉ có vậy. Thêm phần cứng không sửa được vấn đề gốc, còn thêm độ phức tạp chỉ là đẩy vấn đề về sau. Khi thay đổi cách nghĩ về vấn đề, bản thân vấn đề và câu trả lời cũng thay đổi

    • Tôi thật sự tò mò monolith đó đã làm gì tệ đến mức có thể vắt ra được hiệu năng như vậy. Không biết truy vấn có tệ không, có quá nhiều truy vấn không, hoàn toàn không có cache không, hay những việc có thể xử lý đồng thời lại đang được làm theo kiểu đồng bộ
    • Tôi tò mò liệu bạn có thể chia sẻ nội dung hoặc giao thức của các request này là gì, và kích thước payload trung bình của mỗi request và response khoảng bao nhiêu không
  • Bổ sung cho câu “tách monolith thành nhiều dịch vụ liên kết với nhau, mỗi dịch vụ có kho dữ liệu riêng có thể mở rộng theo điều kiện của nó”, ở thời điểm này không nhất thiết phải tách mọi microservice có thể. Chỉ cần hỏi “phần tách nào sẽ tạo tác động lớn nhất?”
    Trong trường hợp của tôi, tôi tách một phần dữ liệu chuỗi thời gian từ Mongo sang Cassandra. Cấu trúc bảng của Cassandra phù hợp hơn nhiều. Tập dữ liệu đó có schema được định nghĩa rõ ràng, và Cassandra có thể đóng gói dữ liệu hiệu quả hơn nhiều. Ở phần đó không cần sự linh hoạt của tài liệu JSON
    Vì dữ liệu đó chiếm phần lớn tổng dữ liệu, nên sau đó Mongo trở nên khá ổn. Chỉ cần đúng một lần tách. Về mặt kỹ thuật, trước và sau vẫn đều là monolith, chỉ là cùng một dịch vụ ghi vào hai database
    Trớ trêu là về sau có một kiến trúc sư kiểu lý thuyết suông muốn gộp tất cả dữ liệu vào một document store JSON, và đã có nhiều cuộc trao đổi kiểu “chúng ta đã đi con đường đó rồi và biết nó kết thúc ở đâu”

    • Thật thú vị khi ý tưởng microservice bị ném ra như một “giải pháp” quá hiển nhiên. Không phải vậy
      Mở rộng theo chiều dọc mới nên là giải pháp đầu tiên hiển nhiên. Đây là điểm nhiều người bỏ lỡ, và bài viết cũng có đề cập phần nào: mở rộng theo chiều dọc gần giống mở rộng theo chiều ngang mà không phá vỡ tính nhất quán của database
      Còn rất nhiều dư địa để vắt, và hiếm khi cần dễ dàng đưa ra các antipattern như bỏ qua join hay kiểm chứng dữ liệu chỉ vì gặp vấn đề hiệu năng
    • Trong bài viết, theo một nghĩa nào đó họ cũng đã tách ra. Vì họ chuyển một số truy vấn SELECT nặng sang replica
    • Thú vị là tôi thường gặp vấn đề ngược lại. Tôi phải liên tục giải thích vì sao Cassandra không phù hợp với dữ liệu có vòng đời tương đối ngắn và được cập nhật thường xuyên. Lý do là tombstone
    • Việc một server có thể đọc từ hai kho dữ liệu cũng rất quan trọng. Giống như read replica được nói ở bình luận cùng nhánh, không có gì ngăn việc đọc cả Postgres và Redis trong cùng một process
  • Nếu các lập trình viên biết đọc kết quả EXPLAIN/ANALYZE và biết lập chỉ mục đúng cách cũng như tối ưu truy vấn, có thể tránh được vô số quyết định thiết kế quá mức
    Chỉ cần ghi lại các truy vấn, lọc ra những truy vấn chạy cực thường xuyên hoặc mất nhiều thời gian, cache những truy vấn chạy thường xuyên, và tối ưu những truy vấn nặng. Làm việc này một cách có hệ thống sẽ giúp hệ thống khỏe mạnh hơn
    Theo kinh nghiệm của tôi, những thứ giúp ích nhiều là APM, log truy vấn chậm, replica đọc/ghi của database, partitioning và sharding

    • Bản thân việc đọc output của explain cũng có thể là một thử thách khá lớn. Database là một thế giới hoàn toàn riêng, đặc biệt nếu bạn hầu như không viết SQL trực tiếp
      Các công cụ như https://explainmysql.com cho thấy rõ hơn trên thực tế cần tối ưu điều gì, nên trở thành một hệ thống dễ tiếp cận hơn cho những lập trình viên có đủ kiến thức để cấu hình database nhưng chưa đủ để hiểu cách nó được dùng bên trong
      Tôi có cảm giác ai đó đã đang xây một hệ thống AI mà chỉ cần đưa schema và log vào là nó sẽ kỳ diệu chạy ra SQL cần cải thiện. Không chắc có nên tin hay không, nhưng nhiều công ty hẳn sẽ muốn dùng thứ đó hơn là tuyển DBA chuyên trách
    • Tôi tò mò liệu có tài liệu hay nào để hiểu execution plan của SQL không. Trong dự án hiện tại, chúng tôi gặp nhiều vấn đề về hiệu năng truy vấn MS SQL Server
      Tôi không biết có phải luôn chỉ định index hint cho từng truy vấn hay không. Đôi khi có vẻ truy vấn không dùng index dù index đó tồn tại. Tôi nghĩ nếu dùng execution plan của SQL thì có thể hiểu vấn đề này tốt hơn
    • Tôi không đếm nổi đã thấy bao nhiêu phiên “kiến trúc thế hệ tiếp theo”, mà phần lớn có thể được thay thế bằng một đợt thẩm định thực trạng của implementation hiện tại
      Không thể sửa code tệ bằng một kiến trúc mới. Nó chỉ đẩy vấn đề lùi lại một thời gian mà thôi
  • Tôi nhớ đến một câu mình thích. “Ta ra trận với đội quân mình đang có, chứ không phải đội quân mình muốn có hay muốn có trong tương lai”
    Có thể bạn sẽ muốn bỏ qua sự thật rằng câu này xuất phát từ Donald Rumsfeld. Dù vậy, ông ấy cũng có vài câu rất hay như “unknown unknowns”
    Khi làm việc trong đội nhóm, tôi thường nhớ đến câu này. Không phải ai cũng hoàn toàn đồng ý, hay có cùng cách hiểu và mục tiêu chung. Có người có thể làm việc theo cách kém hiệu quả hoặc không phải cách tôi ưa thích. Nhưng có đội vẫn tốt hơn là không có, nên phải tìm cách tốt nhất để đạt mục tiêu với đội mình đang có
    Điều này cũng áp dụng rất đúng với hệ thống

    • Rumsfeld có khá nhiều câu trích dẫn hay. Phần lớn chúng xuất hiện trong bối cảnh giải thích vì sao chiến tranh Iraq lại trở nên hỗn loạn đến vậy, và chính tình huống đó lại là nơi rất cần kiểu lãnh đạo mà nếu chỉ nhìn vào các câu trích dẫn của ông thì có vẻ ông đã có thể mang lại
    • Mỗi khi nghe tên Rumsfeld, tôi lại nhớ đến cảnh ông ấy từ chối phủ nhận trong hơn 10 phút rằng mình không phải là thằn lằn
      https://www.youtube.com/watch?v=XH_34tqxAjA
    • “Không kế hoạch tác chiến nào sống sót sau khi chạm trán với kẻ địch”
      https://www.google.com/search?q=no+battle+plan+survives
    • Câu của Mattis “kẻ địch cũng có một lá phiếu” cũng là một lời nhắc thực tế rất hay. Dù mọi người rất dễ nổi giận, nó hữu ích trong các bối cảnh như bảo mật, quyền riêng tư, DRM
    • Tôi cũng thích một câu tương tự của Steven Pressfield. “Vận động viên biết rằng sẽ không bao giờ có ngày anh ta thức dậy mà không đau đớn. Anh ta phải thi đấu khi đang chấn thương”
      Câu này áp dụng với chính chúng ta tốt hơn là với hệ thống
  • Theo kinh nghiệm của tôi, trong các web app xây trên ORM, khi tải cơ sở dữ liệu trở thành vấn đề thì có rất nhiều quả thấp dễ hái có thể đạt được bằng tối ưu truy vấn
    Vượt ra ngoài những điều cơ bản như “có vấn đề N+1 không”, ORM đôi khi không tạo ra truy vấn tối ưu. Tôi không muốn xây một web app production phức tạp mà không có ORM, nhưng thỉnh thoảng cần có khả năng thoát khỏi ORM
    Cần profile các truy vấn đang chạy thật trong production và tiêu tốn nhiều tài nguyên nhất. Nhìn vào các truy vấn đó và hiểu hình dạng của các bảng liên quan. Có khi ORM dùng join nhưng thực ra cần subquery, và cũng có trường hợp ngược lại. Đôi khi cần tiền tổng hợp một phần kết quả, hoặc điều chỉnh điều kiện WHERE trong các join phức tạp
    Tôi từng thấy một truy vấn do ORM sinh ra, chạy tương đối thường xuyên, đang giết chết cơ sở dữ liệu; sau vài chỉnh sửa nhỏ, thời gian chạy từ hơn 20 giây giảm xuống dưới 1 giây

    • Hiện tôi đang làm việc với SQLAlchemy, một ORM Python, và việc khiến INSERT dùng RETURNING không hề tầm thường. Cần đặt tùy chọn không trực quan là expire_on_commit=False
      Tùy chọn này cũng không bảo đảm sẽ dùng RETURNING; nó chỉ dùng nếu database driver và cơ sở dữ liệu hỗ trợ, và ORM hỗ trợ tổ hợp driver/cơ sở dữ liệu đó. SQL được sinh ra có xuất hiện trong log, nhưng không có API để kiểm tra trực tiếp, nên nếu không tự bắt log rồi cào ra thì không có cách nào buộc test suite xác nhận việc dùng RETURNING. May là trong framework Pytest thì việc này khá dễ
      Tôi thích ORM, nhưng những chuyện như thế này phức tạp đến mức gây bực bội ở nhiều tầng. Tôi cũng hiểu SQLAlchemy là một thư viện khổng lồ nên không thể mọi thứ đều dễ. Dù vậy, ví dụ này cho thấy rõ sự đánh đổi khi dùng ORM
      Tôi biết nếu dùng trực tiếp insert() trong Core thì sẽ làm được như ý. Ở đây tôi đang nói đến trường hợp .add() một đối tượng ORM vào AsyncSession
    • Vấn đề lớn của ORM nằm ở chỗ hướng đối tượng không phải là cách tốt để tổ chức phần mềm hay dữ liệu trong phần lớn domain
      Phần lớn business logic nên được diễn đạt bằng ngôn ngữ của đại số quan hệ cùng vài phần mở rộng thì tốt hơn OOP
  • Câu “chi phí thật sự của việc tăng độ phức tạp, và thường là chi phí lớn hơn nhiều, là sự chú ý” cũng chính là tải nhận thức
    Tôi đã mệt mỏi khi làm việc với các hệ thống microservice vẫn có downtime trong khi không ai biết toàn bộ hệ thống vận hành ra sao. Phần lớn thực ra là distributed monolith, nơi thay đổi trải trên nhiều service và phải triển khai theo thứ tự. Dữ liệu phải được sao chép, công việc phải được đồng bộ, và trạng thái phải được chia sẻ
    https://www.youtube.com/watch?v=y8OnoxKotPQ

    • Microservice mà “không ai biết toàn bộ hệ thống vận hành ra sao” là một mùi kiến trúc rất phổ biến. Ý tưởng ban đầu là không ai có thể và cũng không nên cần biết toàn bộ
      Nhưng nếu muốn sửa hoặc chỉnh hệ thống mà cần ai đó hiểu toàn bộ, đó là tín hiệu mạnh rằng bạn đã vi phạm các quy tắc như single responsibility hoặc abstraction đúng thông qua API. Dù vậy, theo kinh nghiệm của tôi, chuyện này rất phổ biến. Để debug một pipeline gồm N microservice, thường phải chạy và build cả N service trên máy local
      Nói nghiêm ngặt thì đây là một monolith cộng thêm network partition và biến động build/deploy vô hạn. Cuối cùng, tôi xem đó là một môi trường làm việc cực kỳ khó, vượt quá năng lực của bất kỳ lập trình viên con người nào
  • Tôi thích bài này. Trong vài tháng qua tôi đã cố truyền đạt cùng thông điệp đó với quản lý, nhưng không thành công mấy
    Cụm Redshift quá tải của chúng tôi đã sập nhiều lần, và vì đã nâng tối đa lên node RA3 rồi nên giờ chúng tôi sắp bắt đầu một đợt migration lớn sang “hạ tầng thế hệ tiếp theo”. Hạ tầng thế hệ tiếp theo ở đây nghĩa là ba cụm Redshift được quản lý bằng CDK
    Hạ tầng mới phức tạp hơn hiện trạng rất nhiều, và tôi không chắc nó sẽ là viên đạn bạc mà mọi người kỳ vọng

  • Đây là giải pháp nhàm chán. Nếu không xây các hệ thống cực kỳ quan trọng liên quan trực tiếp đến sinh mạng và thân thể, nó nên là câu trả lời mặc định. Tổng chi phí sở hữu chắc chắn thấp hơn nhiều
    Tôi đã thấy quá nhiều trường hợp nếu không có tài nguyên để vận hành một hệ thống dự phòng lớn, thì chính độ phức tạp mà hệ thống dự phòng thêm vào lại trở thành vấn đề. Tập trung vào sự đơn giản thì tốt hơn
    Nếu để hỗ trợ độ phức tạp mà phải bổ sung nhiều người, trong khi xét về tiền bạc và đánh giá rủi ro thì không cần làm vậy, phương án đơn giản hơn tốt hơn rất nhiều. Không phải tôi chưa từng thấy những trường hợp cuối cùng cần một dự án khổng lồ để tiến lên, nhưng đôi khi ngay cả dự án đó vẫn nhỏ hơn tổng chi phí đã phải gánh để tiếp tục chịu đựng độ phức tạp cho đến thời điểm ấy. Điều này phụ thuộc rất nhiều vào thứ bạn đang xây dựng

  • Việc tìm ra các tối ưu hiệu năng trong hệ thống và vắt kiệt phần hiệu năng còn lại để có được giải pháp thật sự rất thú vị
    Tôi nhớ đến cuốn Understanding_Software_Dynamics của Richard L. Sites. Cuốn sách này dạy cách đo lường và khắc phục các vấn đề về độ trễ, cũng như việc giảm độ trễ ở quy mô lớn có thể tạo ra mức tiết kiệm lớn đến mức nào
    Việc đo lường và suy luận về những vấn đề như vậy thì khó, nhưng giải pháp thường lại đơn giản. Ví dụ, ở trang 9, ông nói: “[a] simple change paid for 10 years of my salary.”
    Một ngày nào đó tôi cũng muốn thử thực hiện một tối ưu có sức ảnh hưởng như vậy

    • Tôi từng làm việc như thế ở Google ít nhất một lần. Google sử dụng lượng tài nguyên máy móc khổng lồ và cũng có các công cụ hiệu năng xuất sắc, nên đó là một môi trường màu mỡ
      https://research.google/pubs/pub36575/
      Tuy nhiên xung quanh cũng có rất nhiều người thông minh, nên nếu bạn tìm thấy một cơ hội lớn thì thường sẽ có lý do vì sao người khác chưa nhảy vào. Đó có thể là lý do kỹ thuật, cũng có thể là lý do tổ chức
      Một ví dụ cho trường hợp sau: Google thường không thưởng lớn cho loại công việc này trừ khi đang có áp lực về tài nguyên. Có thể tôi đã nhận được khoảng 100 đô la tiền thưởng đồng nghiệp nhờ một tối ưu nào đó, nhưng hoàn toàn không có chuyện được hoa hồng 10%, thăng chức, hay quyền không đi làm trong 10 năm mà vẫn nhận lương. Nói chung, công ty muốn kỹ sư làm việc cho tăng trưởng doanh thu hơn là cắt giảm chi phí. Chính sách này có đúng hay không thì vượt quá bậc lương của tôi
    • Điểm khiến tôi băn khoăn trong giải pháp cuối cùng là họ chỉ tối ưu truy vấn sau khi đã nâng instance lên cấu hình lớn nhất có thể
      Lúc đó có vẻ như họ không còn có thể nâng cấu hình thêm chỉ bằng vài cú nhấp trong console quản trị nữa, nên mới phải thật sự động não để giải quyết vấn đề dung lượng. Nếu tối ưu phần mã cụ thể đó sớm hơn, có lẽ ngay từ đầu đã không cần đến cấu hình instance lớn như vậy