Vấn đề memory bloat trong Ruby apps là topic thảo luận thường xuyên. Bài này xem Ruby memory management sai như thế nào, và prevent app blow up.
Trước tiên hiểu bloat nghĩa gì trong context app memory.
Memory Bloat Trong Ruby Là Gì?
Memory bloat: app memory usage tăng đột ngột không giải thích rõ ràng. Tăng có thể nhanh hoặc chậm, nhưng thường continuous. Khác memory leak (kết quả multiple executions theo thời gian).
Memory bloat là một trong những điều tồi tệ nhất xảy ra cho Ruby app production. Nhưng avoidable nếu proper measures. Ví dụ: thấy spike memory usage → check signs memory bloat trước troubleshoot khác.
Cấu Trúc Memory Ruby
Ruby memory usage xoay quanh các elements manage system resources: Ruby language, host OS, system kernel.
Ngoài ra, garbage collection đóng vai trò vital quyết định Ruby memory managed và reused như thế nào.
Ruby Heap Pages Và Memory Slots
Ruby organize objects vào segments gọi heap pages. Toàn bộ heap space (available memory) chia used và empty sections. Heap pages chia thành slots equal-sized, mỗi slot chứa 1 unit-sized object.
Alloc memory cho object mới: Ruby tìm free slots trong used heap space. Không tìm thấy → alloc heap page mới từ empty section.
Memory slots ~40 bytes. Data overflow slots lưu ở vùng khác ngoài heap page, mỗi slot lưu pointer đến external info.
System memory allocator làm all allocations trong Ruby runtime: heap pages và external data pointers.
Operating System Memory Allocation Trong Ruby
Memory allocation calls từ Ruby language được handle bởi host OS memory allocator.
Thường allocator = group C functions: malloc, calloc, realloc, free.
- Malloc: memory allocation, alloc free memory cho objects. Input size, return pointer đến start index allocated block.
- Calloc: contiguous allocation, alloc consecutive memory blocks. Hữu ích alloc object arrays known length.
- Realloc: re-allocation, re-alloc memory với size mới.
- Free: clear pre-allocated memory locations. Input pointer đến start index block cần free.
Garbage Collection Trong Ruby
GC process của language runtime ảnh hưởng lớn đến memory utilization.
Ruby có advanced GC dùng tất cả API methods trên optimize memory consumption.
Thú vị: GC process halts complete application! Đảm bảo không new object allocation trong GC. Do đó GC routine nên infrequent và as quick as possible.
Hai Nguyên Nhân Phổ Biến Của Memory Bloat
Hai lý do lớn: fragmentation và slow release.
Memory Fragmentation
Memory fragmentation: object allocations scattered all over memory, giảm contiguous free memory chunks. Không alloc được objects không có contiguous blocks, dù free memory > enough trên disk. Vấn đề xảy ra mọi language, mỗi language có methods giải quyết.
Fragmentation xảy ra 2 levels: language level và memory allocator level.
Fragmentation At Ruby Level
Do design GC process. GC marks heap page slot free → reuse slot alloc object khác. Nếu heap page toàn free slots → page released về memory allocator.
Nhưng nếu tiny số slots không marked free trên heap? Page không released về allocator. Nhiều slots các heap pages alloc/free simultaneously bởi GC. Improbable entire heap pages released cùng lúc. Dù GC free memory, không thể reused bởi allocator vì memory blocks partially occupied.
Fragmentation At Memory Allocator Level
Memory allocator facing similar problem: release OS heaps khi entirely free. Improbable entire OS heap freed at once do random nature GC.
Allocator cũng provision OS heaps từ system memory cho app. Sẽ provision new OS heaps, dù existing heaps có enough free memory. Perfect recipe cho spike memory metrics.
Slow Release (Giải Phóng Chậm)
Nguyên nhân quan trọng khác: freed memory release về system chậm hơn rate alloc mới. Không phải conventional/rookie issue nhưng ảnh hưởng memory bloat intensely - thậm chí hơn fragmentation!
Investigate allocator source: allocator designed release OS pages chỉ ở end của OS heaps, và chỉ occasionally. Có lẽ performance reasons nhưng backfire counter-productive.
Cách Fix Ruby Memory Bloat
Biết nguyên nhân, xem fix issues và improve performance qua defragmentation và trimming.
Fix Memory Bloat Với Defragmentation
Fragmentation do GC design, không fix nhiều. Nhưng vài steps reduce chances fragmented memory:
- Declare reference đến object dùng nhiều memory → free manually khi xong việc.
- Declare all static object allocations trong 1 big block. Đặt permanent classes, objects, data trên same heap pages. Sau đó dynamic allocations không lo static heap pages.
- Large dynamic allocations ở đầu code nếu possible. Đặt gần static allocation blocks, keep rest memory clean.
- Small, rarely cleared cache → group với permanent static allocation đầu. Cân nhắc remove altogether improve memory management.
- Dùng jemalloc thay glibc standard allocator. Small tweak giảm Ruby memory consumption lên 4x. Caveat: không compatible mọi environment, test kỹ trước production.
Trimming Fix Ruby Memory Bloat
Override GC process, release memory thường xuyên hơn fix slow release. API làm được: malloc_trim. Modify Ruby gọi function này trong GC process.
Modified Ruby 2.6 code gọi malloc_trim trong gc.c function gc_start:
gc_prof_timer_start(objspace);
{
gc_marks(objspace, do_full_mark);
// BEGIN MODIFICATION
if (do_full_mark) {
malloc_trim(0);
}
// END MODIFICATION
}
gc_prof_timer_stop(objspace);
Lưu ý: Không recommended production apps (có thể unstable). Hữu ích khi slow release gây major hits performance, sẵn sàng thử bất kỳ solution nào.
Wrap-up Và Next Steps
Memory bloats tricky identify, challenging fix.
Bài viết xem 2 reasons chính: fragmentation và slow release → 2 fixes: defragmentation và trimming.
Phải constantly monitor app metrics identify impending bloat incident fix trước khi down app.
Hy vọng giúp bạn steps towards fixing memory bloat Ruby app.
P.S. Đăng ký Ruby Magic newsletter để đọc sớm nhất!
Guest author Kumar Harsh - software developer, writer về Ruby, JavaScript. Website và Twitter.