Tuesday, August 13, 2013

Apache Optimization

All the important configuration options are stored by Apache in a config file called httpd.conf that is located at /usr/local/apache/conf/httpd.conf. We will start by opening this file in your favorite text editor. 

For example: 
vi /usr/local/apache/conf/httpd.conf
MaxClients 

Total number of concurrent connections. 
Locate it in the configuration file. This should be set to a reasonable value. I suggest using this formula to determine the right value for your server. 

MaxClients = 150 x RAM (GB) 

So for example if you have 2 GB or RAM set this value to 300.
There is no reason for you to set it any higher unless you have a specific problem with this value. A high value can lead to a complete server hang in case of a DOS attack. A value too low can create timeout problems for your clients if the limit is reached. 

ServerLimit 

This value should be same as MaxClients 

ServerLimit = 150 x RAM (GB) 

MinSpareServers and MaxSpareServers 

MaxSpareServers and MinSpareServers control how many spare (unused) child-processes Apache will keep alive while waiting for more requests to put them to use. Each child-process consumes resources, so having MaxSpareServers set too high can cause resource problems. On the other hand, if the number of unused servers drops below MinSpareServers, Apache will fork (an expensive operation) new child-processes until MinSpareServers is satisfied. 

Leave those values to: 

MinSpareServers 5 
MaxSpareServers 10

If you have more them 2 GB of RAM and you run a resource intensive website consider increasing MaxSpareServers. 

MaxRequestsPerChild 

Controls the number of request the a child serves before the child is killed. This should not be set too low as it will put an unnecessary load on the apache server to recreate the child. I suggest setting it to: 

MaxRequestsPerChild 1000 for 1 GB RAM

10,000 for 2 GB and 0 for more than 2 GB RAM 

KeepAlive and MaxKeepAliveRequests 

KeepAlive provides long-lived HTTP sessions which allow multiple requests to be sent over the same TCP connection. In some cases this has been shown to result in an almost 50% speedup in latency times for HTML documents with many images, but having keepalive on is also a resource intensive setting. 

Here comes the big question: To KeepAlive or not to KeepAlive? 
Well the opinions are mixed here, some say to KeepAlive some say not to. 

KeepAlive off 

If you want to hear my option I would say NOT to KeepAlive if you are running a shared hosting business or if you want to get the most out of your hardware. You should KeepAlive only if the loading time of your pages is the most important factor in your business and you have the money to invest in a more powerful hardware. If you decide to KeepAlive I suggest you set MaxKeepAliveRequest low to something like 2 seconds. 

StartServers 

Sets the number of child server processes created on startup. This setting depends greatly on the type of webserver you run. If you run low traffic websites on that server set it low to something like 5. If you have resource intensive websites on that server you should set it close to MaxClients. 

StartServers 5 

Timeout 

The amount of time Apache will wait for three things: the total amount of time it takes to receive a GET request, The amount of time between receipt of TCP packets on a POST or PUT request, the amount of time between ACKs on transmissions of TCP packets in responses. 

The default value is 300. You should set time to something a bit lower. A setting of 150 is probably ok. This will also help in case of small DOS attacks like to ones targeting some phpBB forums. Do NOT set it any lower then 90 as your users will start having timeout problems.

Timeout 150 

After you have done all the necessary changes you can go ahead and restart Apache. 
There is an extra step that you have to do so that the changes that you done to httpd.conf aren’t lost when a recompile is done. 
To also save the changes in the database you will have to run: 

/usr/local/cpanel/bin/apache_conf_distiller –update
You can check to see if the changes were accepted and will not be discarded at the next apache recompile by running 

/usr/local/cpanel/bin/build_apache_conf
Sample values: 

MinSpareServers 5
MaxSpareServers 10
KeepAlive off
ServerLimit 1400
MaxClients 1400
MaxRequestsPerChild 0
Timeout 150


KeepAlive
KeepAlive là một tính năng của HTTP 1.1 cho phép truyền dữ liệu nhiều lần qua một kết nối duy nhất, kết nối này sẽ đóng sau một khoảng thời gian timeout nào đó không có dữ liệu nào hoặc tổng số lần truyền đạt tới một số maximum nào đó.
Ưu điểm của nó là giảm thiểu được số lần thực hiện quy trình đóng/mở kết nối liên tục, giảm số kết nối mở đồng thời, dẫn đến giảm CPU và memory. Rất hữu ích nếu trang web cần đến nhiều lần request để tải hình ảnh, CSS, JS v.v…
Nhược điểm của nó là nếu không được cấu hình chính xác thì có thể gây tác dụng ngược vì server sẽ phải giữ process/thread trong một khoảng thời gian khá lâu, làm tăng memory và không thể giải phóng tài nguyên để phục vụ các client khác. Bật tính năng này là vô ích nếu trang web chỉ cần đến một vài request.
Trong Apache, việc chỉnh tính năng này liên quan tới các thông số:
  • KeepAlive (On hoặc Off)
  • MaxKeepAliveRequests là số lượng request tối đã được truyền trong một connection, nên giữ số này lớn hơn số resources tải cùng mà bạn ước lượng. Ví dụ, 1 trang web với 100 cái hình, 5 CSS files, 10 JS files… thì con số này có thể là 120.
  • KeepAliveTimeout là số giây timeout nếu không có request nào tiếp theo thì connection sẽ được đóng. Nên giữ số này thấp chừng 3-5s, ví dụ như server đặt tại Việt Nam, phục vụ khách Việt Nam thì tốc độ xử lí 1 request thường chỉ dưới 1s, trừ những file quá bự. Nếu số này cao quá thì sẽ trở thành phản tác dụng như trên đề cập vì server phải giữ connection quá lâu.
Nếu chạy dưới MPM worker thì mới nên bật KeepAlive, còn với MPM prefork thì nên tắt, nếu không chỉ vài giây sau khi restart, Apache sẽ ngốn sạch CPU và memory của bạn :D. Để dễ dàng linh động, mình đã bỏ config KeepAlive ở global và bật/tắt tùy thuộc vào MPM, ví dụ như thế này:
?
1
2
3
4
5
6
7
8
9
<IfModule prefork.c>
KeepAlive Off
IfModule>
<IfModule worker.c>
KeepAlive On
IfModule>

MaxClients, MaxRequestsPerChild…

Đây là một số thông số liên quan đến việc cấu hình MPM.
Đối với MPM prefork:
  • StartServers là số process được tạo ngay sau khi start cái web server.
  • MinSpareServers là số process rảnh rỗi tối thiểu được Apache giữ lại cho dù không có request nào.
  • MaxSpareServers là số process rảnh rỗi tối đa được Apache giữ lại cho dù không có request nào.
  • ServerLimit là số process tối đa được tạo ra. Lưu ý rằng mỗi process phục vụ một request, một trang web ngoài HTML ra còn có thêm hình ảnh, JS, CSS… mỗi thứ đó là một request. Thông thường mỗi browser được cấu hình là chỉ gửi tối đa 2 requests đồng thời tới một domain nên chúng ta không lo bị “ngập lụt” nếu website có quá nhiều request :D.
  • MaxClients tương tự như ServerLimit nhưng số này chỉ được nhỏ hơn hoặc bằng ServerLimit.
  • MaxRequestsPerChild là số request mà mỗi process sẽ phục vụ trước khi kết thúc, nếu để là 0 thì process sẽ chạy mãi mãi.
Tùy vào lượng RAM của server và loại hình ứng dụng web ta có một số công thức áp dụng.
  • Nếu server khá busy và lượng RAM đủ lớn thì mình sẽ set StartServers = MinSpareServers = MaxSpareServers = ServerLimit = MaxClients và MaxRequestsPerChild = 0. Có nghĩa là bất kì lúc nào cũng có một số process tồn tại để sẵn sàng xử lí request. Việc này trông có vẻ lãng phí nhưng thực sự hữu ích nếu server của bạn rất bận rộn vì việc terminate hay fork 1 process cũng đều tốn một lượng thời gian nào đó.
  • Nếu RAM không dư dả lắm nhưng server cũng khá bận rộn thì set MaxRequestsPerChild cao cao một chút (mặc định chỉ là 500). Sau khoảng số lượng requests đó thì process sẽ bị killed, memory được giải phóng để fork process mới.
  • Nếu RAM không nhiều còn server bận rộn hay rảnh rỗi từng thời điểm, ta có thể set StartServers = MinSpareServers và hơi nhỏ, MaxSpareServers trung bình và MaxRequestsPerChild cao một chút (tương tự như số 2).
  • Nếu server không bận rộn, sẽ không có gì để chơi vì không cần tinh chỉnh gì nhiều :D.
Đối với MPM worker:
  • ServerLimit là số process tối đa được phép tạo ra.
  • StartServers là số process được tạo sau khi start Apache.
  • ThreadLimit là số thread tối đa trong mỗi process.
  • ThreadsPerChild là số thread trong mỗi process, số này nhỏ hơn hoặc bằng ThreadLimit.
  • MaxClients là tổng số thread tối đa được tạo, mỗi thread phục vụ một request. Lưu ý rằng MaxClients phải là bội số của ThreadsPerChild và phải nhỏ hơn hoặc bằng ServerLimit * ThreadsPerChild. Ví dụ ServerLimit là 8, ThreadsPerChild là 128 thì MaxClients tối đa là 1024.
  • MinSpareThreads là số thread rảnh rỗi tối thiểu được giữ lại dù không phải phục vụ request nào, số này là bội số của ThreadsPerChild và phải nhỏ hơn hoặc bằng ServerLimit * ThreadsPerChild.
  • MaxSpareThreads là số thread rảnh rỗi tối đa được giữ lại dù không phải phục vụ request nào, số này là bội số của ThreadsPerChild và phải nhỏ hơn hoặc bằng ServerLimit * ThreadsPerChild.
  • MaxRequestsPerChild số requests tối đa được phục vụ của mỗi thread, sau khi đạt được số này, thread sẽ bị killed để tạo thread khác.
Các quy ước tinh chỉnh những con số này cũng dựa trên các điều kiện tương tự như đối với prefork, nhưng ta có thể dễ dàng nhận thấy worker MPM tiết kiệm rất nhiều tài nguyên và MaxClients của worker có thể lớn gấp hàng chục lần MaxClients của prefork, có nghĩa là chúng ta có thể phục vụ nhiều request hơn.
Những lí thuyết phía trên do mình tự đúc kết và không đề cập tới một con số cụ thể vì nó tùy thuộc vào từng điều kiện. Để chỉnh chính xác những thông số này thì phải cần tới kinh nghiệm với chính server của bạn. Nó chỉ giúp chúng ta ước lượng, và sau một thời gian theo dõi, bạn sẽ tìm được giá trị phù hợp. Tuy nhiên khi cấu hình những điều này, ta cần chừa RAM để cho các OS và các apps khác, ví dụ như MySQL hoặc PHP-FPM… Nên bắt đầu bằng những con số nhỏ, nếu tình hình vẫn ổn thì tăng dần cho tới khi đạt giá trị thích hợp nhất.

.htaccess

Đây là một tính năng nổi tiếng của Apache mà chắc là 99.99% PHP developers đều biết. Nó cho phép các webmasters trên môi trường shared hosting chỉnh một số cấu hình của Apache trên thư mục của họ.
Nếu sysadmin cho phép một folder được quyền override một số hoặc tất cả configs (AllowOverride khác None) thì mỗi khi có một request tới folder đó hoặc con của nó, Apache sẽ tìm kiếm các file .htaccess ở mỗi mức của folder để parse và config. Điều này ảnh hưởng không nhỏ đến performace của website.
Nhưng nếu server là của chúng ta thì không cần phải dùng tới .htaccess, mọi configs trong đó đều có thể chuyển vào config trong vhost, Apache chỉ load và parse một lần duy nhất.

Nén response dạng text với mod_deflate

Gzip là một chuẩn nén hiệu quả hơn zip và nhanh hơn bzip nên được ứng dụng vào rất nhiều nơi, trong đó có HTTP. Nếu một browser (thực ra tất cả browser chính hiện nay) hỗ trợ gzip thì web server sẽ nén và việc truyền file diễn ra nhanh hơn rất nhiều.
PHP có hàm ob_start(‘ob_gzhandler’), đặt nó ở trên cùng của file sẽ giúp nén nội dung output của trang đó. Đối với Apache, tính năng này được hỗ trợ bằng mod_deflate, có thể nén tất cả các dạng file do nó send, như vậy xử lí được nhiều hơn PHP chỉ nén nội dung trang hiện tại.
Chúng ta chỉ nên nén các dạng file text vì như vậy mới hiệu quả, nén các dạng binary như hình ảnh, video, mp3… chẳng có tác dụng gì:D
?
1
2
3
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml application/x-javascript text/css text/javascript
IfModule>

Client cache và ETag/Expires

Một trang động kèm theo nhiều resource tĩnh như hình ảnh, CSS, JS… chúng ta có thể vừa tăng tốc tải web vừa giảm băng thông bằng cách kích hoạt tính năng cache trên client. Hầu hết các browser hiện đại đều hỗ trợ tính năng này thông qua một số HTTP header như ETag, Last-Modified, Expires, Cache-Control…
ETag (entity tag) là một chuỗi unique do web server sinh ra dựa trên một số yếu tố như server, checksum, thời gian tạo/sửa… Mỗi khi browser cần tải URL đó, nó sẽ gửi kèm cái ETag của URL được lưu trước đó, web server so sánh với ETag mới nhất của file, nếu trùng nhau thì gửi về status 304 Not Modified, browser sẽ không cần phải tải lại URL đó.
Nhưng theo Yahoo!, vấn đề với ETag đó là nếu chúng ta chia tải trên nhiều server, khi browser lưu ETag từ server này nhưng sau đó lại request tới 1 server khác thì ETag lúc này lại thay đổi, điều này hạn chế ích lợi của ETag. Vậy tốt nhất là ép browser không dùng ETag bằng cách tắt tính năng sinh ra ETag trên web server. Để làm việc này ta cần có mod_headers và đặt 2 dòng này vào đâu đó trong config của Apache:
Header unset ETag
 FileETag None
Thay vào đó, ta sẽ dùng mod_expires để sinh ra một số header cho browser cache nội dung, ví dụ như thế này:
?
1
2
3
4
5
6
7
8
9
10
11
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType application/x-javascript "access plus 1000 days"
ExpiresByType text/css "access plus 1000 days"
ExpiresByType image/jpeg "access plus 1000 days"
ExpiresByType image/gif "access plus 1000 days"
ExpiresByType image/x-icon "access plus 10 days"
ExpiresByType image/png "access plus 1000 days"
ExpiresByType application/x-shockwave-flash  "access plus 1000 days"
ExpiresByType video/x-flv "access plus 1000 days"
IfModule>
Trong đó, ta thông báo với client rằng một số file có dạng trên sẽ hết hạn trong vòng 1000 ngày nữa, trong 1000 ngày này không cần phải tải lại cho khỏi tốn băng thông. Vấn đề phát sinh là làm sao để buộc browser tải bản mới nếu chúng ta đổi file trong vòng 1000 ngày đó? Chẳng có cách nào yêu cầu client clear cache hết, chỉ có cách đơn giản đó là gắn số phiên bản vào file, ví dụ như icon-top-1.gif, lần sau thay đổi thì là icon-top-2.gif…
Bạn có thể cài extension Y Slow vào Firefox hoặc Chrome để kiểm tra xem mình cấu hình có hiệu quả không. Nếu một số mime-type không tồn tại, hãy thêm nó vào /etc/mime.types của server giúp cho Apache phát hiện mime-type của file chính xác hơn.