First, let's take a look at the NewRelic report.
Average response time of the last 24 h
Pages with high traffic)
Several actions of traffic volume:
TopicsController # show
UsersController # show (miserable, mainly because GitHub API requests are slowed down)
PS: before publishing this article, I made a slight modification. The GitHub request is put into the backend queue for processing. The new result is as follows:
TopicsController # index
HomeController # index
From the preceding report, the response time for Ruby China back-end requests beyond the user homepage is within 100 ms, or even lower.
How do we do this?
Markdown Cache
Fragment Cache
Data Cache
ETag
Static resource cache (JS, CSS, images)
Markdown Cache
When the content is modified, the Markdown result is saved to the database to avoid repeated computation during browsing.
In addition, this item is not specifically put into the Cache, but in the database:
In order to ensure persistence, Memcached can be stopped and lost in large quantities;
Avoid occupying too much cache memory;
Class Topic field: body # stores the original content, which is used to modify field: body_html # stores the calculated results and displays before_save: markdown_body def markdown_body self. body_html = MarkdownTopicConverter. format (self. body) if self. body_changed? EndendFragment Cache
This is the most widely used cache solution in Ruby China, and it is also the reason for the speed improvement.
App/views/topics/_topic.html. erb <% cache ([topic, suggest]) do %> <div class = "topic topic_line topic _ <% = topic. id %> "> <% = link_to (topic. replies_count, "# {topic_path (topic)} # reply # {topic. replies_count} ",: class =>" count state_false ") %>... content omitted </div> <% end %>
Use the cache_key of the topic as the cache views/topics/{number}-# {Update time}/{suggest parameter}/{file content MD5}-> views/topics/19105-20140508153844 /false/bc178d556ecaee49971b0e80b3566f12
For some scenarios where different statuses are displayed based on the user account, you can directly prepare the complete HTML file and control the status through JS, such as the current "like" function.
<script type="text/javascript"> var readed_topic_ids = <%= current_user.filter_readed_topics(@topics) %>; for (var i = 0; i < readed_topic_ids.length; i++) { topic_id = readed_topic_ids[i]; $(".topic_"+ topic_id + " .right_info .count").addClass("state_true"); }</script>
For example
App/views/topics/_reply.html. erb <% cache ([reply, "raw :#{@ show_raw}"]) do %> <div class = "reply"> <div class = "pull-left face"> <% = user_avatar_tag (reply. user,: normal) %> </div> <div class = "infos"> <div class = "info"> <span class = "name"> <% = user_name_tag (reply. user) %> </span> <span class = "opts"> <% = likeable_tag (reply,: cache => true) %> <% = link_to ("", edit_topic_reply_path (@ topic, reply),: class => "edit icon small_edit", 'Data-uid' => reply. user_id,: title => "Modify reply") %> <% = link_to ("", "#", 'Data-pload' => floor, 'Data-login' => reply. user_login,: title => t ("topics. reply_this_floor "),: class =>" icon small_reply ") %> </span> </div> <div class =" body "> <% = sanitize_reply reply. body_html %> </div> <% end %>
Also, the cache_key of reply is used to cache views/replies/202695-20140508081517/raw: false/d91dddbcb269f3e0172bf5d0d27e9088.
At the same time, complicated user permission control is implemented using JS;
<script type="text/javascript"> $(document).ready(function(){ <% if admin? %> $("#replies .reply a.edit").css('display','inline-block'); <% elsif current_user %> $("#replies .reply a.edit[data-uid='<%= current_user.id %>']").css('display','inline-block'); <% end %> <% if current_user && !@user_liked_reply_ids.blank? %> Topics.checkRepliesLikeStatus([<%= @user_liked_reply_ids.join(",") %>]); <% end %> })</script>
Data Cache
In fact, most Model queries in Ruby China are not cached, because according to the actual situation, the MongoDB query response time is very fast, and most of the scenarios are within 5 ms, or even lower.
We will make some data query caches for price comparison, such as: GitHub Repos
def github_repos(user_id) cache_key = "user:#{user_id}:github_repos" items = Rails.cache.read(cache_key) if items.blank? items = real_fetch_from_github() Rails.cache.write(cache_key, items, expires_in: 15.days) end return itemsendETag
ETag is a parameter that can be included in the HTTP Request and Response. It is used to check whether the content has been updated to reduce network overhead.
This is probably the process.
The fresh_when method of Rails can help generate ETag information for your query content.
def show @topic = Topic.find(params[:id]) fresh_when(etag: [@topic])end
Static resource Cache
Please do not underestimate this, the back-end writing is faster, it may also be slowed down (the performance on the browser )!
1. make proper use of Rails Assets Pipeline. You must enable it!
# config/environments/production.rbconfig.assets.digest = true
2. Set the cache Validity Period of CSS, JS, and Image to max in Nginx;
location ~ (/assets|/favicon.ico|/*.txt) { access_log off; expires max; gzip_static on;}
3. Reduce the number of JS, CSS, and Image pages as much as possible. The simple method is to merge them to reduce the HTTP request overhead;
<Head>... there are only two <link href = "// users" rel = "stylesheet"/> <script src = "// users"> </script>...
Some Tips
View statistics logs to prioritize pages with high traffic;
Updated_at is something that helps you clear the cache and make good use of it! Do not ignore data modification!
Pay more attention to the query time in your Rails Log. The page response time of 100 ms is a good status, and users who exceed 200 ms will feel dull.