Sunday, 17 March 2019

Tăng tốc Django API Service tới 90% với cacheops

1. Code chạy đúng là được rồi mà?

Code chạy đúng thôi là chưa đủ, luôn phải tối ưu thêm về mặt tốc độ.

Bạn có thể viết code đẹp như tranh, nhưng nếu một request của khách mất đến 10s để xử lý, thì 99% là khách hàng của bạn sẽ bỏ đi ngay lập tức (trừ khi bạn là trang .gov hoặc khi bạn cần đăng ký tín chỉ).

Ok, bạn tiếp tục "outsource" những phần xử lý nặng nề cho Celery hoặc RQ để khách đỡ phải nhìn màn hình trắng. Nhưng kể cả vậy thì nó cũng không làm vấn đề biến mất. Nó chỉ giúp khách của bạn biết rằng ở bên dưới bạn vẫn đang vật lộn xử lý chứ không "chết hẳn". Thay vì màn hình trắng thì giờ là hình xoay xoay.

Và lúc đó, bạn cần phải "tối ưu" lại code của mình rồi.

Và sau khi vò đầu bứt tai ngồi đoán, imort pdb; pdb.set_trace() loạn xạ, thêm các kiểu đo đếm thời gian, bạn bó tay. Vì đó không phải cách bạn nên làm để tối ưu code.

2. Analysis

Đầu tiên, ta cần phải phân tíchđo lường. Bạn có thể ngồi đoán mò cả ngày xem code bị nghẽn ở đâu, hoặc dùng một cách khoa học hơn để biết phải sửa chỗ nào.

Và việc đó gọi là `profilling` code. Python có sẵn thư viện để profile, đó là cProfile. Còn Django thì có django-debug-toolbar và Django Rest Framework có django-silk.

Django Silk
Khi bạn truy cập 1 API, Silk sẽ profile lại các SQL và thời gian thực hiện, số lần thực hiện, được gọi ở dòng nào, hàm nào v.v.. Và nó còn lưu lại rất nhiều các thông tin hữu ích khác nữa liên quan đến request đó.

Django silk lưu lại các SQL query



và cả chỗ được gọi nữa
(Lưu ý: bạn chỉ nên setup ở môi trường dev hoặc local, vì thời gian profile và lưu lại vào db nhiều khi còn lâu hơn thời gian code thực thi)

3. Tối ưu code

Sau khi bạn biết được query/đoạn code nào bị lặp nhiều nhất, hay chạy lâu nhất, sai logic hay không, thì bạn có thể chỉnh lại và xem có tăng được chút hiệu quả nào không (thường là sẽ có, không ít thì nhiều).

Ví dụ như trường hợp của mình, sau khi phân tích và tối ưu hoá lại, kết quả là khá đáng kể.

phần màu cam nhỏ nhỏ là thời gian của Django Silk khi profilling.
210 query là đã tối ưu 1 lần, trước đó khoảng gần 300 query!
Nhưng thế vẫn là chưa đủ. Vì sẽ đến 1 thời điểm mà việc bạn tối ưu code, lại dẫn đến việc tăng thời gian thực hiện, hoặc việc thực hiện thay đổi sẽ tốn nhiều thời gian và công sức, dẫn đến khả năng gặp lỗi logic. Sự đánh đổi ở đây lớn hơn lợi ích nó mang lại, vì vậy không đáng để làm.

Tỷ như ở đây mình tối ưu được số query, nhưng lại làm tăng thời gian thực hiện lên.


Vì thế bạn nên tối ưu theo một hướng khác nữa, đó là Cache.

4. Cache

Cache có nghĩa là thay vì mỗi lần client request, bạn lại thực hiện lại đầy đủ các bước và logic, thì bạn sẽ lưu lại kết quả của request này, lần sau ai đó request đến thì bạn chỉ việc ung dung lấy ra và đưa lại cho họ mà không cần phải tính toán lại. Điều này làm giảm tải rất nhiều cho server và tăng một tốc độ đáng kể. (Và thường sẽ sử dụng Redis hay Memcache để lưu)

Nhưng nếu database của bạn thay đổi, mà bạn không xử lý việc đó để update cache, thì khả năng rất cao là dù bạn nhanh, nhưng bạn sẽ sai. Vì thế bạn nên dùng một thư viện để quản lý việc cache, trừ khi đó là những chỗ rất đặc biệt phải tự code.

Django có sẵn cả một thư viện giúp bạn làm việc đó. Nhưng nếu bạn dùng Django Rest, bạn nên ngó qua thư viện django-cacheops (django-cacheops chỉ support Redis).

Thư viện này giúp chúng ta cache lại các queryset, theo dõi sự thay đổi của database để không bị sai sót (nhưng bạn vẫn cần cực kỳ cẩn thận và theo dõi, test lại đầy đủ các case để cân nhắc chỗ nào cần cache, chỗ nào không).


Yep, sau khi setup xong, lần đầu chạy số query đã giảm đáng kể (nhờ cache lại những model ít thay đổi như User, Config v.v..). Lần tiếp theo chạy, số query giảm về còn 0!

Feeling so good!

5. Kết luận

Trước khi tối ưu, mỗi lần user request đến API trên, bên mình mất đến 7 - 8s(!!!) cho một lần load. Còn hiện giờ, thời gian load đã giảm còn khoảng 0.3s. Một sự thoả mãn không hề nhẹ.

Dĩ nhiên, vẫn phải rà soát và test lại từng chỗ một để đảm bảo tính chuẩn xác, và có những đoạn code phải bỏ cache đi vì tần suất thay đổi quá lớn, nhưng dù sao về mặt tổng quát thì tất cả các API đều được tăng tốc độ (vì số lượng query giảm đi rất nhiều).

Còn nhiều thứ để tối ưu hơn nữa, nhưng tạm thời thế cũng ổn rồi. Bạn có thể xem thêm cách phân tích dữ liệu profile trả về qua khoá học của Peter Norvig, hoặc google, đọc docs.

Nếu quan tâm đến performance của Python, bạn có thể đọc thêm High Performance Python.
Hoặc sách High Performance Django tại đây: https://mega.nz/#!YBEGwCYT!KmNWDmu7i2EJ-1CXT7ZtdEcaJV6m7JE3ZWvB95N4utc

Đỗ Anh Tú

No comments:

Post a Comment