12Th9

Tăng tốc độ phản hồi từ môi trường development

Chào các bạn, là tôi đây, Naoshi Hoshi – developer của PIXTA.
Chúc các bạn có một giáng sinh an lành và ấm áp bên bạn bè và người thân nhé.
Trở lại với thực tại thì năm nay, tôi cũng tổ chức giáng sinh đấy các bạn ạ.
Tôi lên kế hoạch sẽ đi mua gà rán KFC, sushi, thêm một ít bánh ngọt ở tiệm Cozy Corner và cả Chan Meri nữa. Tôi sẽ vừa ngồi lướt web vừa đón giáng sinh một mình. :((

Đây là bài viết vào ngày 25 trong lịch sự kiện PIXTA 2017. Tôi hi vọng bài viết này có thể cung cấp những thông tin hữu ích đến cho các bạn.
Ở một bài viết trước đây trên Texta, tôi đã giới thiệu với các bạn về việc đưa Docker vào sử dụng trong môi trường development của fotowa.

Docker cũng là nền tảng chính được sử dụng trong môi trường development của PIXTA. Tuy nhiên, vẫn mất đến những 60 giây để load và hiển thị được trang chủ của PIXTA.
Chính vì vậy tôi muốn giới thiệu đến các bạn một số điểm có thể giúp tăng tốc của môi trường development.

 

Khái quát về môi trường phát triển

Nhiều developer tại PIXTA hiện đang code trên MacOS (*1). Thêm nữa, khi cài đặt Docker dành cho Mac và docker-sync, Docker Compose sẽ cho phép bạn đóng gói tất cả các ứng dụng, thư viện, service… vào trong một container nhờ đó bạn có thể dễ dàng xây dựng một môi trường phát triển chỉ với một câu lệnh duy nhất.

Trước khi thực hiện một số bước để tăng tốc cho môi trường  development, chúng ta cần tuân theo các bước cơ bản sau:

  • Tái hiện lỗi
  • Tìm kiếm và thực hiện giải pháp
  • Kiểm chứng hiệu quả của giải pháp
  • Tìm nguyên nhân chính (nút thắt) và quay lại làm từ bước 1

Để giải quyết vấn đề nhanh chóng, điều quan trọng là phải kiểm chứng các giả thuyết dựa trên những vấn đề phát sinh ở thực tế. Để làm được điều đó, chúng ta cần phải tái hiện được lỗi, và quan trọng là phải nắm được chính xác nguyên nhân gì đã gây nên lỗi đó. Có thể nói xác định được nguyên nhân gây phát sinh lỗi là yếu tố ảnh hưởng lớn nhất đến tính chính xác của việc kiểm chứng giả thuyết.

 

Tái hiện lỗi

Khi tiến hành tái hiện lỗi, trước hết các bạn cần xem vấn đề phát sinh ở đâu bằng cách kiểm tra log của Rails (log/development.log).

Started GET "/" for 172.21.0.1 at 2017-12-25 03:32:36 +0000
 ActiveRecord::SchemaMigration Load (23.0ms)  SELECT `schema_migrations`.* FROM `schema_migrations`
 ActiveRecord::SchemaMigration Load (22.6ms)  SELECT `schema_migrations`.* FROM `schema_migrations`
 Currency Load (10.1ms)  SELECT `currencies`.* FROM `samples`
 Currency Load (9.8ms)  SELECT `currencies`.* FROM `samples`
Processing by SampleController#index as HTML
#<ActionDispatch::Request::Session:0x0056081879bca8>
{:analysis=>{}, :_csrf_token=>"2mE6LEmIjbJ/GuU9SvsN5PUu7qiivU4fKX2/pifFOQk="}
Dalli::Server#connect cache.pixta.jp:11211
hogehoge.pixta.jp:11211 failed (count: 0) SocketError: getaddrinfo: Name or service not known
Session::DalliStore#get: No server available

Started GET "/assets/i18n.self.js?body=1" for 172.21.0.1 at 2017-12-25 03:35:14 +0000
Started GET "/assets/i18n/shims.self.js?body=1" for 172.21.0.1 at 2017-12-25 03:35:15 +0000
Started GET "/assets/sample1.js?body=1" for 172.21.0.1 at 2017-12-25 03:35:16 +0000
Started GET "/assets/sample2.js?body=1" for 172.21.0.1 at 2017-12-25 03:36:21 +0000
Started GET "/assets/sample3.js?body=1" for 172.21.0.1 at 2017-12-25 03:37:53 +0000
Started GET "/assets/sample4.js?body=1" for 172.21.0.1 at 2017-12-25 03:38:24 +0000

Trong ví dụ này, chúng ta có thể dễ dàng nhìn thấy 2 điểm gây phát sinh lỗi:

  • Compile assets trực tiếp
  • Không sử dụng cache

Sau khi kiểm tra log, có thể thấy trước khi render xong, cần mất rất nhiều thời gian để có kết quả từ việc compile Assets bằng Dynamic Sprockets. Hơn nữa, cache không hoạt động nên mỗi lần truy cập vào DB, dữ liệu quá nặng dẫn đến xử lí chậm.


Tìm kiếm và thực hiện giải pháp

Đối với vấn đề xảy ra trong Rails như đã đề cập ở trên, giải pháp được đưa ra là chúng ta phải thay đổi setting file. Cụ thể chúng ta sẽ sửa file config/enviroments/development.rb.

◆ Precompile Assets

Các bước để cài đặt config/enviroments/ các bạn vui lòng tham khảo trong Rails guide nhé!

Cài đặt liên quan đến compile sẽ được thay đổi như sau:

 

#Chỉ định có nén file Compiled Assets hay không.
Bằng cách nén file, chúng ta có thể giảm dung lượng data, giúp tăng tốc độ xử lí.
config.assets.compress = true

#Định nghĩa chương trình sử dụng để nén file JavaScript.
Hiện tại :uglifier là chương trình đang được sử dụng nhiều nhất.
config.assets.js_compressor = :uglifier

# Định nghĩa chương trình sử dụng để nén file CSS.
config.assets.css_compressor = :scss

# Chỉ định compile dynamic Sprockets.
config.assets.compile = false

# Chỉ định dừng việc hợp nhất và nén các file Assets để debug.
config.assets.debug = false

Một số lưu ý trong trường hợp precompile.

  • Trường hợp có chỉnh sửa file JS hoặc file CSS, các bạn chú ý dùng lệnh rake assets:precompile lại một lần nữa.
  • Vì có config.assets.debug = false config.assets.compress = true nên, khi xảy ra lỗi sẽ rất khó để debug.

Nội dung trên đã được đưa vào webpack và khởi động bằng option --progress --watch nên sau khi phản ánh nội dung file được sửa, có thể chạy build lại và phát hiện ra lỗi nên không có vấn đề gì. (*2)

◆ Sử dụng cache

Cách cài đặt bộ nhớ cache, các bạn cũng tham khảo trong Rails guide nhé!
Vì memcached đã được đóng gói trong một container, nên chúng ta chỉ cần chỉ định memcached.

# Thiết lập kho lưu trữ bộ nhớ cache để sử dụng bộ nhớ đệm trong Rails
config.cache_store = :dalli_store, "memcached.pixta.jp", { compress: true }

# Định nghĩa kho lưu trữ bộ nhớ cache để sử dụng trong.
config.assets.cache_store = :dalli_store, "memcached.pixta.jp", { compress: true }

 

Kiểm chứng hiệu quả của giải pháp

Sau khi thực hiện một vài thay đổi cho file như trên và chạy câu lệnh, tốc độ truy cập vào trang chủ nhanh hơn và thời gian truy cập giảm xuống còn khoảng 10 giây thay vì 60 giây như trước đây.

  • rake assets:precompile
  • yarn webpack --config config/webpack.config.babel.js --progress --watch

Mặc dù, tốc độ truy cập nhanh hơn, nhưng vẫn khá rắc rối khi vẫn mất 10 giây để chờ sửa và update source code.
Khi vào kiểm tra log, tôi phát hiện ra tốc độ trong Rails đã được tăng lên, nhưng tôi vẫn phải chờ một lúc trước khi xử lí đầu tiên được bắt đầu.
Nhìn từ đây, có vẻ như đang có vấn đề với xử lí trên Web server (Apache HTTP Server, Passenger) , nên tôi đã kiểm tra log của Web server. (*3)
Cụ thể, tôi đã kiểm tra access.log error.log.

172.21.0.5 - - [25/Dec/2017:03:39:33 +0000] "GET /sample HTTP/1.1" 200 315 "-" "Typhoeus - https://github.com/typhoeus/typhoeus"
172.21.0.1 - - [25/Dec/2017:03:39:32 +0000] "GET / HTTP/1.1" 200 17108 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36"
172.21.0.1 - - [25/Dec/2017:03:40:55 +0000] "GET /assets/frontend/vendor.js HTTP/1.1" 304 - "http://dev.pixta.jp/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36"

Tôi đã kiểm tra nhưng không thấy chỗ nào đáng nghi. Tuy nhiên, tôi vẫn phải chờ vài giây trước khi dữ liệu được load vào access.log
Tôi đã thử truy cập rất nhiều lần nhưng kết quả vẫn như vậy.

 

Tái hiện lỗi (lần 2)

Có vẻ như tôi không thể tìm ra nguyên nhân từ log như trên. Chính vì vậy, tôi quyết định kiểm tra các file cài đặt ở Apache HTTP Server.


<IfModule mpm_prefork_module>
   StartServers          16
   MinSpareServers       16
   MaxSpareServers       32
   ServerLimit           400
   MaxClients            400
   MaxRequestsPerChild   10000
</IfModule>

<IfModule mpm_worker_module>
   StartServers          4
   MaxClients            1024
   MinSpareThreads       64
   MaxSpareThreads       192
   ThreadsPerChild       64
   MaxRequestsPerChild   10000
</IfModule>




LoadModule alias_module modules/mod_alias.so
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authz_default_module modules/mod_authz_default.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule autoindex_module modules/mod_autoindex.so
LoadModule deflate_module modules/mod_deflate.so
LoadModule dir_module modules/mod_dir.so
LoadModule env_module modules/mod_env.so
LoadModule expires_module modules/mod_expires.so
LoadModule headers_module modules/mod_headers.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule mime_module modules/mod_mime.so
LoadModule negotiation_module modules/mod_negotiation.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule status_module modules/mod_status.so

Có vẻ như có 2 điểm trong file setting gây ảnh hưởng đến hoạt động.

  • Setting MPM (Multi-Processing Module)
  • Các LoadModule không cần thiết.

Ngoài 2 điểm trên, thì hầu hết các setting nhỏ khác đều không có vấn đề gì.

 

Tìm kiếm và thực hiện giải pháp (lần 2)

◆ Cài đặt MPM

Trên Apache HTTP server được chuẩn bị sẵn các module tiếp nhận và xử lí các request khác nhau được gọi là MPM (Multi Processing Module).
Để kiểm tra setting hiện tại, tôi đã chạy lệnh sau:

$ /usr/sbin/apachectl -V
Server version: Apache/2.2.15 (Unix)
Server built:   Mar 22 2017 06:52:55
Server's Module Magic Number: 20051115:25
Server loaded:  APR 1.3.9, APR-Util 1.3.9
Compiled using: APR 1.3.9, APR-Util 1.3.9
Architecture:   64-bit
Server MPM:     Prefork
      threaded:     no
          forked:     yes (variable process count)

Khi chạy câu lệnh để kiểm tra, tôi nhận thấy Prefork đang được cài đặt.
Apache HTTP Server hỗ trợ 3 cơ chế MPM gồm:

  • Prefork
  • Worker
  • Event

Chi tiết về từng cơ chế hoạt động của MPM được giải thích trong tài liệu sau:
Multiple Processing Module (MPM)

Prefork: mỗi request gửi đến server sẽ được một process xử lí. Trong khi đó, cơ chế hoạt động của worker là mỗi request gửi đến server sẽ được xử lý bởi một thread (các thread có thể chia sẻ memory với nhau), do đó tiết kiệm RAM hơn nên sử dụng worker sẽ giúp tăng tốc độ xử lí.
Để chuyển đổi MPM từ prefork sang worker, bạn chỉ cần thêm HTTPD=/usr/sbin/httpd.worker vào file etc/sysconfig/httpd và chạy lại.

# The default processing model (MPM) is the process-based
# 'prefork' model.  A thread-based model, 'worker', is also
# available, but does not work with some modules (such as PHP).
# The service must be stopped before changing this variable.
#
HTTPD=/usr/sbin/httpd.worker

Sau đó, tôi thu được kết quả như sau:

$ /usr/sbin/apachectl -V
Server version: Apache/2.2.15 (Unix)
Server built:   Mar 22 2017 06:53:18
Server's Module Magic Number: 20051115:25
Server loaded:  APR 1.3.9, APR-Util 1.3.9
Compiled using: APR 1.3.9, APR-Util 1.3.9
Architecture:   64-bit
Server MPM:     Worker
      threaded:     yes (fixed thread count)
          forked:     yes (variable process count)

◆ Các LoadModule không cần thiết

Khi kiểm tra các LoadModule trong httpd.conf, tôi phát hiện ra có những module không cần thiết đang được load lên môi trường phát triển.

Đây cũng không phải là vấn đề gì quá lớn, bạn chỉ cần đọc hướng dẫn, kiểm tra mục đích sử dụng của từng module và xóa những module không cần thiết đi. Mục đích cuối cùng là cố gắng chỉ giữ lại những module thực sự cần thiết.

 

Kiểm chứng hiệu quả của giải pháp (lần 2)

Bằng cách thay đổi cơ chế hoạt động của MPM sang cơ chế worker, và chỉ giữ lại những LoadModule thực sự cần thiết, chúng ta đã rút ngắn thời gian xử lí từ 10 giây xuống còn 3 giây.
Tuy nhiên, thường thì sẽ mất thêm một giây kể từ khi Rails application bắt đầu xử lí đầu tiên.
Như đã nói ở trên, đa số các kĩ sư của PIXTA sử dụng môi trường MacOS để phát triển. Vì thế, cần phải đồng bộ các file giữa host và các container bằng cách sử dụng docker-sync. Nhưng có một vướng mắc mà chúng ta cần giải quyết: mặc dù việc chọn đồng bộ hóa ở thời điểm gắn kết đã được giải quyết bằng PR, nhưng file đồng bộ lần đầu và tốc độ đồng bộ hóa của MacOS và Docker đều bị chậm.

 

Tái hiện lỗi (lần 3)

Tôi đã thử cài đặt Docker và Docker Compose trên instance EC2 elementary và truy cập nó ngay khi khởi động môi trường phát triển, thì thời gian xử lí đã giảm từ 3 giây xuống còn 1,5 giây. Từ đó có thể thấy, tính tương tác của host OS hiện nay không được tốt lắm.

 

Tìm kiếm và thực hiện giải pháp (lần 3)

Đội dev của PIXTA ở Việt Nam đa số sử dụng hệ điều hành Ubuntu.
Khi chuẩn hóa cách tạo môi trường phát triển bằng Docker, do phát sinh sự khác nhau khi cài đặt Docker trên Mac và Ubuntu nên sẽ có 2 loại tài liệu hướng dẫn.

Vì vậy, lần này tôi quyết định giới thiệu Vagrant để cải thiện tốc độ đồng bộ hóa của MacOS và Docker, và để tiếp tục chuẩn hóa môi trường development.

Vốn dĩ, có tài liệu dành cho Ubuntu và asset dành cho docker compose, nhưng chúng ta chỉ đơn giản là tạo môi trường ảo bằng Vagrant và chỉ post nội dung xử lý mà đã được viết trong provisioning block của Vagrantfile, nên có thể dễ dàng áp dụng Vagrant.

 

Kiểm chứng hiệu quả của giải pháp

Như đã đề cập ở trên, khi sử dụng Docker để xây dựng môi trường ảo, xử lí được thực hiện nhanh hơn, rút ngắn thời gian từ 3 giây còn 1,5 giây.
Đồng thời, tốc độ xử lí input/output file như bundle install rake assets:precompile khi tạo container trên môi trường ảo cũng sẽ nhanh hơn. Thời gian xử lí từ 240 giây giảm xuống 60 giây.

 

Kết luận

Tôi nghĩ đến việc có thể tăng tốc độ xử lí bằng cách sửa đổi tập tin cấu hình Rails, mà đã bỏ qua việc cài đặt các phần mềm trung gian, hay là để ý đến tính tương tác của Docker và OS.
Kết quả mà chúng ta nhìn thấy ngay được chính là thời gian xử lí đã giảm từ 60 giây xuống còn 1,5 giây. Mặt khác, chúng ta có thể thấy service trung gian đóng vai trò rất quan trọng trong việc giúp đội dev cải thiện performance.
Ở các bước đã đề cập ở trên, bạn có thể nhìn thấy vấn đề được giải quyết một cách khá đơn giản, nhưng trong thực tế cần thời gian để điều tra và tìm hiểu. Chính vì vậy, thông qua quá trình điều tra và tìm hiểu cách giải quyết đó có thể nâng cao kĩ năng của developers. Tôi nghĩ rằng đây là cách tốt để tích lũy kiến thức và nâng cao kĩ năng của mỗi cá nhân.

 

* 1 : Bạn có thể sử dụng những hệ điều hành khác

* 2 :  Nếu điểm này không rõ ràng thì sẽ chẳng giải quyết được gì.

* 3 : Khi debug cho LogLevel có thể nhìn thấy các thông tin chi tiết.