|
# [fit] Halve Your Memory Usage |
|
# With These |
|
# [fit] 12 Weird |
|
# [fit] Tricks |
|
|
|
Heroku and AWS hate him! |
|
@nateberkopec |
|
|
|
--- |
|
|
|
 |
|
|
|
--- |
|
|
|
--- |
|
|
|
 |
|
|
|
--- |
|
|
|
# Ruby is a garbage collected language. |
|
# But my memory is growing. |
|
# Therefore, memory leak. |
|
|
|
--- |
|
|
|
 |
|
|
|
--- |
|
|
|
# Solution 1: |
|
# [fit] Dial Back |
|
# [fit] The Instance Counts |
|
|
|
## Discover true steady-state memory usage per instance |
|
|
|
--- |
|
|
|
 |
|
|
|
--- |
|
|
|
# [fit] Myth: |
|
# Memory usage should look like a long, flat line |
|
|
|
--- |
|
|
|
 |
|
|
|
--- |
|
|
|
 |
|
|
|
--- |
|
|
|
 |
|
|
|
--- |
|
|
|
# [fit] Aim for |
|
# [fit] 300MB |
|
# [fit] per instance |
|
|
|
## This also applies to Sidekiq |
|
|
|
--- |
|
|
|
# Solution 2: |
|
# [fit] Stop allocating |
|
# [fit] so many |
|
# [fit] objects at once |
|
|
|
--- |
|
|
|
# [fit] Myth: |
|
# "Shouldn't GC clean the unused objects up after the job completes?" |
|
|
|
--- |
|
|
|
# [fit] Translated: |
|
# [fit] Memory goes down, |
|
# [fit] right? |
|
|
|
--- |
|
|
|
 |
|
|
|
--- |
|
|
|
# [fit] Thresholds |
|
# [fit] Heap frag |
|
# [fit] free isn't free |
|
|
|
--- |
|
|
|
# [fit] Thresholds, |
|
# [fit] not timers. |
|
|
|
--- |
|
|
|
# [fit] Slots run out |
|
# [fit] oldmalloc |
|
# [fit] malloc |
|
|
|
--- |
|
|
|
# [fit] Heap |
|
# [fit] fragmentation |
|
|
|
--- |
|
|
|
 |
|
|
|
--- |
|
|
|
 |
|
|
|
--- |
|
|
|
 |
|
|
|
--- |
|
|
|
# [fit] GC::INTERNAL_CONSTANTS |
|
|
|
```ruby |
|
{ |
|
:RVALUE_SIZE=>40, |
|
:HEAP_PAGE_OBJ_LIMIT=>408, |
|
:HEAP_PAGE_BITMAP_SIZE=>56, |
|
:HEAP_PAGE_BITMAP_PLANES=>4 |
|
} |
|
``` |
|
|
|
--- |
|
|
|
# [fit] Malloc and free |
|
# [fit] are suggestions, |
|
# [fit] not commands |
|
|
|
--- |
|
|
|
 |
|
|
|
--- |
|
|
|
 |
|
|
|
--- |
|
|
|
# [fit] Heap fragmentation |
|
# [fit] can cause long-term |
|
# [fit] slow "leaks" |
|
|
|
--- |
|
|
|
 |
|
|
|
--- |
|
|
|
# End result: |
|
# Ruby Memory usage = |
|
# Maximum Memory Pressure |
|
|
|
--- |
|
|
|
 |
|
|
|
--- |
|
|
|
# [fit] Allocating |
|
# [fit] less |
|
|
|
Or, fix your god damn N+1s |
|
|
|
--- |
|
|
|
# Solution 2a: Use an APM - Scout, Skylight, New Relic |
|
|
|
--- |
|
|
|
# Solution 2b: Use |
|
## `memory_profiler` |
|
# or `oink` |
|
|
|
--- |
|
|
|
 |
|
|
|
--- |
|
|
|
# oink |
|
|
|
``` |
|
-- SUMMARY -- |
|
Worst Requests: |
|
1. Feb 02 16:26:06, 157524 KB, SportsController#show |
|
2. Feb 02 20:11:54, 134972 KB, DashboardsController#show |
|
3. Feb 02 19:06:13, 131912 KB, DashboardsController#show |
|
4. Feb 02 08:07:46, 115448 KB, GroupsController#show |
|
5. Feb 02 12:19:53, 112924 KB, GroupsController#show |
|
6. Feb 02 13:03:00, 112064 KB, ColorSchemesController#show |
|
7. Feb 02 13:01:59, 109148 KB, SessionsController#create |
|
8. Feb 02 06:11:17, 108456 KB, PublicPagesController#join |
|
9. Feb 02 08:43:06, 94468 KB, CommentsController#create |
|
10. Feb 02 20:49:44, 82340 KB, DashboardsController#show |
|
``` |
|
|
|
--- |
|
|
|
# memory_profiler |
|
|
|
``` |
|
allocated memory by gem |
|
----------------------------------- |
|
rubygems x 305879 |
|
|
|
allocated memory by file |
|
----------------------------------- |
|
/home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb x 285433 |
|
/home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/basic_specification.rb x 18597 |
|
/home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems.rb x 2218 |
|
/home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/specification.rb x 1169 |
|
/home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/defaults.rb x 520 |
|
/home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_gem.rb x 80 |
|
/home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/version.rb x 80 |
|
``` |
|
|
|
--- |
|
|
|
# [fit] Make your own: |
|
# [fit] objectspace & |
|
# [fit] gc.stat |
|
|
|
--- |
|
|
|
# GC.stat - log it! |
|
|
|
```ruby |
|
{ |
|
:count => 9, |
|
:heap_allocated_pages => 74, |
|
:heap_sorted_length => 75, |
|
:heap_allocatable_pages => 0, |
|
:heap_available_slots => 30164, |
|
:heap_live_slots => 29863, |
|
:heap_free_slots => 301, |
|
:heap_final_slots => 0, |
|
# etc etc |
|
} |
|
``` |
|
|
|
--- |
|
|
|
# ObjectSpace.count_objects |
|
|
|
```ruby |
|
{ |
|
:TOTAL => 30164, |
|
:FREE => 235, |
|
:T_OBJECT => 297, |
|
:T_CLASS => 944, |
|
:T_MODULE => 45, |
|
:T_FLOAT => 4, |
|
# etc etc |
|
} |
|
``` |
|
|
|
--- |
|
|
|
# Solution 2c: If all else fails, move to Rake tasks |
|
## Throwaway VMs are better than bloated VMs |
|
|
|
--- |
|
|
|
 |
|
|
|
--- |
|
|
|
# [fit] Solution 3: |
|
# [fit] Gemfile audit |
|
# [fit] with `derailed` |
|
|
|
--- |
|
|
|
``` |
|
$ bundle exec derailed bundle:mem |
|
TOP: 54.1836 MiB |
|
mail: 18.9688 MiB |
|
mime/types: 17.4453 MiB |
|
mail/field: 0.4023 MiB |
|
mail/message: 0.3906 MiB |
|
action_view/view_paths: 0.4453 MiB |
|
action_view/base: 0.4336 MiB |
|
``` |
|
|
|
--- |
|
|
|
# [fit] Myth: |
|
# [fit] Dependencies |
|
# [fit] are free! |
|
|
|
--- |
|
|
|
# [fit] require false |
|
# [fit] for assets! |
|
|
|
```ruby |
|
gem 'sass', require: false |
|
``` |
|
|
|
--- |
|
|
|
# sprockets/lib/sprockets/autoload.rb |
|
```ruby |
|
module Sprockets |
|
module Autoload |
|
autoload :Babel, 'sprockets/autoload/babel' |
|
autoload :Closure, 'sprockets/autoload/closure' |
|
autoload :CoffeeScript, 'sprockets/autoload/coffee_script' |
|
autoload :Eco, 'sprockets/autoload/eco' |
|
#etc etc etc |
|
``` |
|
|
|
--- |
|
|
|
# [fit] Solution 4: |
|
# [fit] jemalloc |
|
|
|
--- |
|
|
|
> emphasizes fragmentation avoidance and scalable concurrency support. |
|
|
|
--- |
|
|
|
# [fit] LD_PRELOAD |
|
## [fit] **or** |
|
# [fit] --with-jemalloc |
|
|
|
--- |
|
|
|
# [fit] Solution 5: |
|
# [fit] Use copy-on-write |
|
## Puma, Unicorn or Passenger w/preloading |
|
|
|
--- |
|
|
|
# [fit] Copy-on-write |
|
# [fit] increases shared |
|
# [fit] memory |
|
|
|
--- |
|
|
|
 |
|
|
|
--- |
|
|
|
# [fit] Myth: |
|
# [fit] Memory usage = |
|
# [fit] sum of RSS |
|
|
|
Memory is surprisingly difficult to measure |
|
|
|
--- |
|
|
|
# [fit] memory can be |
|
# virtual/real |
|
# shared/private |
|
# resident/swapped |
|
|
|
--- |
|
|
|
# [fit] It isn't perfect |
|
# [fit] but it's a start |
|
|
|
--- |
|
|
|
# [fit] Solution 6: |
|
# [fit] Use a threaded |
|
# [fit] webserver |
|
|
|
Puma, Passenger Enterprise. |
|
Increase concurrency w/lighter methods |
|
|
|
--- |
|
|
|
# [fit] Mini-Myth: |
|
# [fit] My application |
|
# [fit] isn't thread-safe |
|
|
|
--- |
|
|
|
 |
|
|
|
--- |
|
|
|
# [fit] minitest/hell |
|
|
|
--- |
|
|
|
# [fit] Solution 7: |
|
# [fit] Keep Ruby |
|
# [fit] and gems up-to-date |
|
|
|
--- |
|
|
|
# [fit] Ruby 2.2+ |
|
# [fit] Rails 4.2+ |
|
# [fit] Watch out for Ruby 2.4 |
|
|
|
--- |
|
|
|
# [fit] Solution 8: |
|
# [fit] Tune malloc |
|
|
|
--- |
|
|
|
# [fit] `MALLOC_ARENA_MAX` |
|
|
|
--- |
|
|
|
# [fit] `mallopt` |
|
|
|
--- |
|
|
|
# [fit] Solution 9: |
|
# [fit] Tune GC |
|
|
|
--- |
|
|
|
# If you can't read gc.c and understand these variables yourself, don't touch them (yet) |
|
|
|
--- |
|
|
|
# GC Tuning can fix: |
|
# Too many free slots |
|
# Slow startup |
|
# Too many or too few GCs |
|
|
|
--- |
|
|
|
# Performance |
|
# Birds of Feather |
|
# Tomorrow @ 1:15pm |
|
|
|
--- |
|
|
|
# 350+ pages, 18+ hours of video |
|
# [fit] railsspeed.com |
|
|
|
--- |
|
|
|
 |
|
|
|
--- |
|
|
|
# 10kb |
|
# [fit] speedshop.co |
|
|
|
--- |
|
|
|
# [fit] Thanks! |
|
## Slides and notes on Twitter |
|
## @nateberkopec |
|
## speedshop.co & railsspeed.com |
|
|
|
Tell me your problems! |