6 quy tắc để xây dựng ứng dụng web hiệu quả

Trong bài viết này, chúng ta sẽ cùng nhau tìm hiểu về một số quy tắc được coi là cần thiết để xây dựng một ứng dụng web có hiệu quả. Những quy tắc căn bản đó là:

  • 1: Tránh việc tối ưu hóa quá sớm
  • 2: Tối thiểu hóa lượng công việc cần để giải quyết một vấn đề
  • 3: Tạm hoãn những công việc không cần thực hiện ngay
  • 4: Sử dụng cache nếu có thể
  • 5: Hiểu và tránh trường hợp truy vấn N+1 trong cơ sở dữ liệu quan hệ
  • 6: Chuẩn bị trước cho tình huống mở rộng quy mô ứng dụng sau này

Quy tắc 1: Tránh việc tối ưu hóa quá sớm

Nhiều lập trình viên thường có thói quen dành quá nhiều thời gian cho việc tối ưu những đoạn code không quan trọng. Điều này thường được lý giải bởi việc họ không biết phần nào là phần quan trọng hoặc phần nào cần được tối ưu hơn. Để tránh điều này, bạn nên có một phiên bản đầu cho đoạn code mà không cần quan tâm tới tối ưu. Sau đó, sử dụng một công cụ Profiler để phân tích và tìm ra điểm hạn chế của đoạn code đó. Điều này, giúp bạn tập trung cải thiện vào những phần thật sự quan trọng hơn. Nếu bạn đang làm việc trên PHP thì có rất nhiều công cụ giúp bạn profile code: xdebug, xhprof, Symfony profiler, The Stopwatch component, Blackfire.io, Tideways, …

Quy tắc 2: Tối thiểu hóa lượng công việc cần để giải quyết một vấn đề

Thông thường , đoạn code của bạn sẽ cần load rất nhiều thứ chỉ để sinh ra kết quả như mong muốn. Đặc biệt đối với những thư viện và frameworks phức tạp. Ví dụ như tạo kết nối với database hay đọc file, dù chúng chả liên quan gì đến việc sinh ra kết quả của đoạn mã hiện tại. Một số kỹ thuật và design patterns dưới đây có thể giải quyết vấn đề trên

  • Autoloading: một đặc điểm của PHP cho phép yêu cầu file chứa định nghĩa của một lớp chỉ khi sử dụng đến lớp đó.
  • Dependency Injection: truyền components thông qua hàm khởi tạo hoặc hàm setter
  • Lazy Loading: một design pattern quan trọng dùng để tạm hoãn việc khởi tạo một đối tượng cho đến khi cần. Nó được sử dụng nhiều nhất với những đối tượng mà xử lý những tài nguyên lớn nhưng database hay file.

Quy tắc 3: Tạm hoãn những công việc không cần thực hiện ngay

Lấy ví dụ trong trường hợp gửi email sau khi người dùng thực hiện một tác vụ nào đó đối với ứng dụng web như thay đổi password hay hóa đơn hoàn tất. Sẽ có một lượng lớn công việc trước khi email gửi thành công. Tuy nhiên, về phía người dùng, việc hiển thị ra một thông báo nào đó lại cần phải được thực hiện ngay khi có thể và những công việc để xử lý việc gửi email kia nên tạm hoãn lại. Cách phổ biến nhất là dùng hàng chờ tác vụ (job queues), nghĩa là bạn phải lưu một lượng dữ liệu tối thiểu để thực hiện tác vụ bị trì hoãn trong hàng đợi và tác vụ này sẽ được thực hiện sau. Tác vụ cần thực hiện ngay đó là: hiển thị thông báo cho người dùng. Một số thư viện để triển khai hàng đợi như: Resque, Laravel Queues, Gearman, Beanstalkd,…

Quy tắc 4: Sử dụng cache nếu có thể

Trong lập trình, chúng ta có thể tập trung vào nhiều mức cache: Byte Code Cache: một đặc điểm chung của nhiều ngôn ngữ thông dịch (PHP, Python, Ruby, …) cho phép tránh việc thông dịch mã nguồn lập đi lập lại nếu không có thay đổi.

  • Application Cache: là mức cache phụ thuộc vào từng ứng dụng. Một ví dụ điển hình như việc cache kết quả truy vấn database đối với mỗi request của người dùng, dễ thấy ở một số cache servers như Memcached, Redis hay Gibson.
  • HTTP Cache: Việc trao đổi dữ liệu giữa client và server có thể tiêu tốn khá nhiều thời gian. Việc cache dữ liệu sử dụng HTTP Cache headers như Etag hay Cache-Control sẽ giải quyết vấn đề này.
  • Proxy Cache: kỹ thuật này sử dụng một server chuyên biệt để nhận tất cả những yêu cầu và phản hồi HTTP, cũng như lưu trữ những trang web mà người dùng yêu cầu. Trong trường hợp này, kết quả trả về sẽ từ server này thay vì yêu cầu app server xử lý lại. Những proxy servers có thể kể đến như Varnish, Ngnix, Squid.

Quy tắc 5: Hiểu và tránh trường hợp truy vấn N+1 trong cơ sở dữ liệu quan hệ

Tình huống truy vấn N+1 là một vấn đề rất hay gặp phải khi xử lý trong cơ sở dữ liệu quan hệ. Trong tình huống này, sẽ có N+1 truy vấn để lấy ra N bản ghi. Để hiểu hơn, ta xét ví dụ sau:

<?php
function getUsers() {  
  //... retrieve the users from the database (1 query)
  return $users;
}

function loadLastLoginsForUsers($users) {  
  foreach ($users as $user) {
    $lastLogins = ... // load the last logins for the user (1 query, executed n times)
    $user-&gt;setLastLogins($lastLogins);
  }

  return $users;
}

$users = getUsers();
loadLastLoginsForUsers($users);  

Đoạn code trên load tất cả users trong truy vấn đầu tiên và sau đó, đối với mỗi user, nó lại load thời gian người dùng đó đăng nhập lần cuối. Đoạn code này sinh ra truy vấn N+1:

SELECT id FROM Users; -- ids: 1, 2, 3, 4, 5, 6...  
SELECT * FROM Logins WHERE user_id = 1;  
SELECT * FROM Logins WHERE user_id = 2;  
SELECT * FROM Logins WHERE user_id = 3;  
SELECT * FROM Logins WHERE user_id = 4;  
SELECT * FROM Logins WHERE user_id = 5;  
SELECT * FROM Logins WHERE user_id = 6;  

Truy vấn này thường xảy ra với mối quan hệ “has many”, đặc biệt khi sử dụng ORM. Thông thường, vấn đề này có thể được giải quyết như truy vấn dưới đây:

SELECT id FROM Users;
SELECT * FROM Logins WHERE user_id IN (1, 2, 3, 4, …);

Quy tắc 6: Chuẩn bị trước cho tình huống mở rộng quy mô ứng dụng sau này

Khả năng mở rộng ( theo chiều ngang) có thể hiểu là khả năng hệ thống có thể thích nghi và duy trì các chức năng mà không cần quan tâm đến hiệu năng khi mà số lượng người dùng ( và requests) tăng lên. Mở rộng theo chiều ngang là một chiến lược mở rộng riêng biệt trong đó có việc sử dụng nhiều thành phần làm clustor khi triển khai ứng dụng. Điều này giúp hệ thống có thể duy trì được hiệu năng khi có nhiều requests đồng thời được gửi đến. Tuy nhiên, có 2 vấn đề chính cần lưu ý khi mở rộng theo cách này:

  • Sessions: thường thì dữ liệu session sẽ được lưu trên hệ thống file mà ứng dụng được triển khai. Sử dụng chiến lược mở rộng này không những chậm mà còn có thể khiến ứng dụng không hoạt động khi có 2 clustor cùng xử lý request ( dữ liệu session trên mỗi clustor khác nhau). Giải pháp cho vấn đề này là lưu session vào một database nào đó.
  • Hệ thống file người dùng: Giống như vấn đề trên nhưng xảy ra với những files mà người dùng lưu trên hệ thống. Chính vì vậy, ta cần lưu files của người dùng ở những nơi lưu trữ chuyên biệt như Amazon S3 hay Rackspace Cloudfiles. Nếu không, ta vẫn có thể lưu tại máy chủ ứng dụng nhưng đồng bộ giữa các cluster. Trường hợp này, NFS hay GlusterFS có thể được dùng để tạo ra hệ thống tập tin chia sẻ.

Kết luận

Khi nắm rõ các quy tắc trên, việc xây dựng một ứng dụng web hiệu quả không còn quá khó khăn.

Tham chiếu: 6 Rules of thumb to build blazing fast web server applications