The performance of the optimization of Ruby on Rails ways to explore

By Craig Warren,2015-06-05 09:32
20 views 0
The performance of the optimization of Ruby on Rails ways to explore

The performance of the optimization of Ruby on

    Rails ways to explore

    1. Lead to slow your Rails application no more than the following two


    1. In should not Ruby and Rails as the preferred place using Ruby and Rails.(in Ruby and Rails did

    not good at doing work)

    2. Excessive memory consumption lead to need to use a lot of time for garbage collection.

    Rails is a pleasant frame, and Ruby is a simple and elegant language.But if it is abused, it will affect performance.Have a lot of work is not suitable for in Ruby and Rails, you'd better use other tools, for example, database obvious advantages on big data processing, R language is particularly suited to do statistical work related.

    Memory is the leading cause of slow many Ruby application.Rails performance optimization of the 80-20 rule is this: 80% of acceleration is derived from the optimization of memory, the remaining 20% belongs to other factors.Why memory consumption is so important?Because the more you allocated memory, Ruby GC (Ruby's garbage collection mechanism) needs to be done.Rails has occupied a lot of memory, and every application has juststarted take an average of nearly 100 MB of memory.If you don't pay attention to the control memory, your program memory growth more than 1 g, it is very likely.Need to recycle so much memory, it is no wonder that most of the time the execution of a program is occupied by GC.

    2 how do we make a Rails application run faster?

    There are three ways to make your application faster: expansion, caching, and code optimization.

    It is easy expansion in today.Heroku is basically for you to do this, while Hirefire make a process more automatic.Other hosting environment provides a similar solution.In a word, can you use it.But please keep in mind the scale and improve performance is not a silver bullet.If your application only response to a request, within five minutes of expansion is no use.Also is to use Heroku + Hirefire almost could easily lead to overdraw your bank account.I have seen Hirefire me an expansion of application to 36 entity, let me pay the $3100.I immediately manually instance reduced to 2, and the code is optimized.

    Rails cache is also easy to implement.Rails 4 blocks in the cache is very good.The Rails documentation is in the cache knowledge of good information.But compared with expansion, the cache does not become the ultimate performance problem solution.If your code should not be an ideal running, then you will find that you will get more and more resource consumption in the cache, until the cache can no longer bring speed boost.

    Let your Rails application faster is the only reliable way of code optimization.This is memory optimization in the Rails of the scene.And take for granted that if you accept my advice, and avoid using Rails for outside the scope of its design capacity, you will have less code to optimization.

    2.1 to avoid memory intensive Rails features

    Rails features to spend a lot of memory lead to additional garbage collection.The list below.

    2.1.1 serializer

    String of serializer read from the database is a practical method of Ruby data type.

class Smth < ActiveRecord::Base

     serialize :data, JSON



    Smth.find(...).data = { ... }

    It consumes more memory to efficient serialization, see for yourself:

class Smth < ActiveRecord::Base

     def data



     def data=(value)

     write_attribute(:data, value.to_json)



    It will be twice as long as the memory overhead.Some people, including myself, see the Rails JSON serializer memory leaks, about 10% of the amount of data on every request.I don't understand the reasoning behind this.I don't know whether there is a situation that is reproducible.If you have experience, or if you know how to reduce memory, please let me know.

    2.1.2 active record

    With ActiveRecord manipulate data easily.But the essence ActiveRecord is packing your data.If you have a 1 gb of data table, ActiveRecord said will take 2 g, in some cases even more.Yes, 90% of the time, you get the extra convenience.But sometimes you don't need, for instance, batch updates can reduce ActiveRecord overhead.The following code, namely not instantiate any model, also won't run verification and correction.

    Book.where('title LIKE ?', '%Rails%').update_all(author: 'David')

    Behind the scenes, it is only execute SQL update statement.

update books

     set author = 'David'

     where title LIKE '%Rails%'

    Another example is iteration over a large dataset. Sometimes you need only the data. No typecasting, no updates. This snippet just runs the query and avoids ActiveRecord altogether: result = ActiveRecord::Base.execute 'select * from books'

    result.each do |row|

     # do something with row.values_at('col1', 'col2')


    2.1.3 string callback

    Rails callback like before/after save, before/after the action, and a lot of use.But you write this way may affect your performance.There are three ways you can write, such as: before saving the callback:

before_save :update_status

    before_save do |model|



    before_save “self.update_status”

    Before the two ways can very good run, but the third.Why is that?Because execution Rails callback need to store the execution context (variables, constants, global instance, etc.) is a time when the callback.If your application is very big, you end up copying a large amount of data in the memory.Because the callback can perform at any time, within your program before the end of may not be recycled.

    A symbol, the callback in each request saved me 0.6 seconds.

    2.2 write less Ruby

    This is my favorite one.My university professor of computer science class like to say that there is no such thing as the best code.Sometimes to do the task at hand need other tools.The most commonly used is the database.Why is that?Because Ruby is not good at dealing with large data sets.Very, very bad.Remember, Ruby takes up a very large memory.So, for example, with 1 gb of data you may need to 3 g or more memory.It will take a few seconds to recycling the 3 g.Good database can deal with the data of a second.Let me give some examples.

    2.2.1 attribute preloading

    Sometimes the standardized model of property from another database access.For example, imagine that we're building a TODO list, including task.Each task can have one or more tags.The canonical data model is as follows:

    ; Tasks

    ; id

    ; name

    ; Tags

    ; id

    ; name

    ; Tasks_Tags

    ; tag_id

    ; task_id

    Loading tasks and their Rails label, you should do so:

    This code has a problem, it created object for each tag, spend a lot of memory.Alternative solutions that will label preloading in the database.

tasks = <<-END



     select from tags inner join tasks_tags on ( = tasks_tags.tag_id)


     ) as tag_names


     > 0.018 sec

    It only takes memory to store a list of additional, an array of labels.It is no wonder that

    three times faster.

    2.2.2 data collection

    I said any code to summarize the data set or analyze the data.These operations can be simple summary, or some of the more complex.Team, for example.Suppose we have a staff, department, wage data set, we need to calculate the employee's wages ranking in a department.

SELECT * FROM empsalary;

depname | empno | salary


     develop | 6 | 6000

     develop | 7 | 4500

     develop | 5 | 4200

     personnel | 2 | 3900

     personnel | 4 | 3500

     sales | 1 | 5000

     sales | 3 | 4800

    You can use the Ruby number:

salaries = Empsalary.all

    salaries.sort_by! { |s| [s.depname, s.salary] } key, counter = nil, nil

    salaries.each do |s|

     if s.depname != key

     key, counter = s.depname, 0


     counter += 1

     s.rank = counter


    Empsalary list data from 100 k program in 4.02 seconds.Alternative Postgres query,

    using the window function to do the same work in more than four times 1.1 seconds.

SELECT depname, empno, salary, rank()

    OVER (PARTITION BY depname ORDER BY salary DESC) FROM empsalary;

depname | empno | salary | rank


     develop | 6 | 6000 | 1

     develop | 7 | 4500 | 2

     develop | 5 | 4200 | 3

     personnel | 2 | 3900 | 1

     personnel | 4 | 3500 | 2

     sales | 1 | 5000 | 1

     sales | 3 | 4800 | 2

    Four times the speed has been impressive, sometimes you get more, to 20 times.From my own experience as an example.I have a 3 d OLAP cube and 600 k rows of data.My program slicing and polymerization.In Ruby, it took about 90 seconds to complete 1 gb of memory.Equivalent SQL queries within 5 to complete.

    2.3 the optimization Unicorn

    If you are the use of Unicorn, then the following optimization techniques will apply.Unicorn is the fastest web server in the Rails framework.But you can still make it more running faster.

    2.3.1 preload App

    Unicorn can create a new worker processes, preload Rails applications.This has two benefits.First, the main thread can pass the written copy mechanism of friendly GC (Ruby 2.0 above), the data Shared memory.Operating system will be transparent copy these data, to prevent the worker changes.Second, the preload reduced the worker processesstart time.Rails is common worker process to restart further the (later), so the worker to restart the faster the speed, we can get better performance.

    If need to open the application of preload, the only need to unicorn in the configuration file to add a line:

    preload_app true

    In 2.3.2 Request GC between requests

    Please keep in mind, the GC the processing time of the meeting of the application of 50% of the time.This is not the only problem.GC is usually unpredictable, and trigger when you don't want it to run to run.So, how to deal with you?

    First of all, we would have thought that, if fully disable the GC?This seems to be a very bad idea.Your application will probably soon fill 1 gb of memory, and you have not yet found in a timely manner.If your server is running a few worker at the same time, then your application will soon there will be a memory, even if your application is in the custody of the server.Not to mention the Heroku is only 512 m memory limit.

    In fact, we have a better way.So if we can't avoid the GC, we can try to make the determination of the timing of the GC to run as far as possible, and in the spare time.In between these two request, for example, run the GC.This is easy to through the configuration Unicorn.

    For Ruby 2.1 previous versions, the one unicorn module called OobGC:

require 'unicorn/oob_gc'

     use(Unicorn::OobGC, 1) # "1" 表示"强制GC1request后运行"

    The Ruby version 2.1 and later, it is best to use gctools

    ( :

require 'gctools/oobgc'


    But between the request run GC have some matters needing attention.Most importantly, the optimization technology is perceived.That is to say, users will feel performance boost.But the server needs to do more work.Unlike when needed to run the GC, this technology needs the server frequently run GC. So, you want to make sure you have enough resources to run the server GC, and the other worker is running in the process of GC, have enough worker to handle the user's request.

    The growth of 2.4 co., LTD

    I have to show you some examples of application takes up 1 gb of memory.If your memory is enough, then take up such a large block of memory is not a big problem.But Ruby may not return to the memory to the operating system.Let me explain why.

    Ruby through two heap to allocate memory.All of Ruby objects in storage in Ruby own heap.Each object 40 bytes (64 - bit operating system).When the object need more memory, it will allocate memory in the heap of the operating system.After the object is garbage collection and release, occupied the heap memory in the operating system will be returned to the operating system, but Ruby's own heap of memory will only simple tag is available for free, will not be returned to the operating system.

    This means that Ruby will only increase will not reduce.Imagine, if you read the 1 million rows from a database, 10 listed in each row.Then you need to allocate at least 10 million objects to store the data.Usually Ruby worker afterstart to take up 100 MB of memory.In order to meet so many data, the worker needs an additional 400 MB of memory (10 million objects, each object takes up 40 bytes).Even if the object was finally back, the worker still use 500 MB of memory.

    Here need to declare, Ruby GC can reduce the size of the heap.But I haven't found this function in actual combat.Because in a production environment, trigger a heap of reducing conditions rarely appear.

    If your worker can only increase, the most obvious solution is to memory whenever it takes too much time, on the resumption of the worker.Some hosting services will do so, for example Heroku.Let's take a look at other ways to implement this function.

    Against 2.4.1 internal memory control

    Trust in God, but lock your car believe in God, but don't forget to lock the car.(moral: most foreigners have religious beliefs, believe that god is omnipotent, but in daily life, who can expect god to help you? "faith is faith, but have a difficult time Still want to rely on oneself..There are two ways to make your application to realize self memory limit.I do tube them, Kind (friendly) and hard (mandatory).

    Kind of friendly memory limit is forced memory size after each request.If the worker memory is too big, so the worker will be over, the and unicorn will create a new worker.That is why I do it "kind".It does not lead to interrupt your application.

    Process memory size, use RSS metrics in Linux and MacOS or OS gem on the Windows.The I to show in the Unicorn in the configuration file how to implement the restrictions:

class Unicorn::HttpServer


     alias process_client_orig process_client

     undef_method :process_client

     def process_client(client)


     rss = `ps -o rss= -p #{}`.chomp.to_i / 1024

     exit if rss > KIND_MEMORY_LIMIT_RSS



    Hard disk memory limit is by asking the operating system to kill your work process, if it

    has a lot of growth.You can call on Unix setrlimit to set RSSx restrictions.As far as I know,

    this is only valid on Linux.MacOS implementation is broken.I'd appreciate any new


    The this snippet from Unicorn hard limit configuration files:

after_fork do |server, worker|



    class Unicorn::Worker


     def set_memory_limits

     Process.setrlimit(Process::RLIMIT_AS, HARD_MEMORY_LIMIT * 1024 * 1024)



Report this document

For any questions or suggestions please email