![]() |
Services | Software | Partners | Articles | Contact |
Performance Case Study: Scaling of a Drupal intranet for a large multinational corporationA media firm in Germany, jetztdesign GmbH contacted 2bits.com for partnering on an intranet site for a large multinational corporation. The intranet used a lot of contributed modules, with 35 custom developed modules for this application. It would serve a user base of more than 80,000 employees. Testing of the application showed that only 30 concurrent users can be active simultaneously, falling well short of the requirement for 200 users to be active at any given time. Because all the users would be logged in, the traditional methods of scaling Drupal for anonymous traffic did not apply. These include boost, memcache, and the like. A plan was quickly formulated where 2bits.com would get a copy of the site, and perform stress and load testing on it in our lab. We used our standard lab setup with the following machines, all interconnected on a 1000 Mbps ethernet LAN:
We configured the traffic generator for add a new user every 2 seconds, until 200 logged in users are active, maintain the load for 30 minutes, then ramp down the load by one user every 2 seconds. Each user would issue a page request after a random wait period of 1 to 30 seconds. Each user would visit 6 URLs, including the home page, a search page, and a few others (my bookmarks, my notes, ...etc.) We quickly found that we could not go beyond around 50 users, and starting diagnosing the problems with the application that prevents it from scaling. Here are the findings, and the solutions for each: Problem: Search and CREATE TEMPORARYWe found that on each home page, a custom module shows related content from several node types. It does that by using a query on the search table, then creating a temporary table and populating it. This took around 6 seconds for every home page the user visits, which is too heavy to have all users doing it all the time. The compromise was for 2bits.com to write custom caching code for this module. We used cache_set() and cache_get in the appropriate places to cache the home page for each user for an hour, and only generate it if an hour has passed. Here is the code we change, with the parts added marked by 2bits.com in the comments:
// 2bits.com - caching (part 1) // Assume 60 minutes, change this if necessary // Get the cache entry $sql = "SELECT DISTINCT sid FROM {search_index} $result = db_query($search_sql); ... foreach($types in $type) { $create_temp_table = db_query($sql); $result = db_query("SELECT * FROM {tmp_updates} ORDER BY changed desc"); ... while(($search = db_fetch_object($result)) && ($list_limit > count($check_list))){ // 2bits.com - caching (part 2) db_query("DROP TABLE {tmp_updates}"); Problem: LOWER() query on arg(0)We found that on almost every page, there was these queries, which are expensive and long running:
These were traced to custom code that was calling The underlying reason was really the url_alias table:
And this was caused by the pathauto setting for users was just
So, the solution was to change the settings in pathauto to be The code was like this:
if (arg(0) == 'user') { ... After implementing the above changes, there were improvements. We were able to scale the site to handle 200 users, much better than the original 30 to 50, but the response time was not great with an average of 4 seconds. Problem: Locking in MyISAM causing queries to take longerWe observed that excessive locking on several tables was causing queries to wait for the lock for a long time, and hence converted the sessions, watchdog and accesslog tables to InnoDB.
Implementing Drupal's memcache patches and includeOriginally the application had the advcache module and its patches installed. These were shown not to have any measurable improvement for this particular site and the tests performed. We installed memcache, and used a single bin of 256MB running on the same server as the application. This eased off the load for retrieving the variable, menu and other caches that are done for every page view. In addition the custom cache_set()/cache_get() mentioned above would be stored in memcache as well, and not the database. Conclusion
We were now able to get a response time of 0.6 seconds with the system handling 200 concurrently logged in users easily! Although the above graph says MyISAM vs. InnoDB, the graph is the cumulative result of all the changes mentioned above. You can see what a difference it is in response time for each URL visited by each of the 200 users. Moreover, CPU utilization, memory utilization and disk contention were within acceptable levels, showing that there is room to grow and that the above configuration can handle more transactions and more users if we need it to. Is your Drupal site slow? |




You are great
Great. Great. Great.
I always read your article with more and more attention and I always write some notes on my "book fo notes". Tnx again for all of yours articles.
And sorry for my bad English.
M.
--
ziodrupal.net sviluppo siti CMS Drupal.
Thanks
I've found the same problems on one o my site with 2-300 visitors: lock on myisam tables and high number of tmp tables in some module. Thanks for the hints and thans to ZioDrupal that let me found your article.