Showing posts with label webserver. Show all posts
Showing posts with label webserver. Show all posts

Saturday, August 6, 2011

Suhosin


Suhosin is an advanced protection system for PHPinstallations. It was designed to protect servers and users from known and unknown flaws in PHP applications and the PHP core. Suhosin comes in two independent parts, that can be used separately or in combination. The first part is a small patch against the PHP core, that implements a few low-level protections against bufferoverflows or format string vulnerabilities and the second part is a powerful PHP extension that implements all the other protections.
Unlike the PHP Hardening-Patch Suhosin is binary compatible to normal PHP installation, which means it is compatible to 3rd party binary extension like ZendOptimizer.

Wednesday, April 13, 2011

Using Nginx, SSI and Memcache to Make Your Web Applications Faster

If you’d take a look at any web site, you will notice, that almost all of the pages on this given site are pretty static in their nature. Or course, this site could have some dynamic elements like login field or link in the header, some customized menu elements and some other things… But entire page could be considered static in many cases.
When I started thinking about my sites from this point of view, I understood, how great it would be to be able to cache entire page somewhere (in memcache for example) and be able to send it to the user without any requests to my applications, which are pretty slow (comparing to memcache ;-) ) in content generation. Then I came up with a pretty simple and really powerful idea I’ll describe in this article. An idea of caching entire pages of the site and using my application only to generate small partials of the page. This idea allows me to handle hundreds of queries with one server running pretty slow (yeah! it is slow even after all optimizations on MySQL side and lots of tweaks in site’s code) Ruby on Rails application. Of course, the idea is kind of universal and could be used with any back-end languages, technologies or frameworks because all of them are slower then memcache in content “generation”.

So, first of all, let me describe tools I use in my solution (but it does not mean, that you must use the same software – it is just for example):
  • Memcached – for handling all requests to cached information without generation on every request
  • Nginx (with SSI enabled) – for handling all HTTP requests to my site and retrieving information from memcache or from my application back-end.
  • Ruby on Rails – as an example of backend application (could be Python, PHP, Perl, Java, etc).
And now, let me describe generic request handling process with all these tools together. Let’s imagine some user hits your site’s page. Browser sends a request to your web-server. Web server has nginx installed there (frontend) and Rails-based application deployed like described in one of my previous posts or in other people’s posts on the net (nginx+mongrel). So, nginx proxies your request to one of mongrel backend instances and mongrel serves the request.
This was generic process of request handling with 2-tier web server deployment (frontend + backend). In this case all your requests would be handled by mongrel and Rails which is pretty slow. So, we need to lighten up this process by removing unnecessary work from Rails application. First of all, we need to change nginx configuration to let it server pages from memcache and ask backend mongrels only of some request result has not been cached yet:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# Defining mongrel cluster
upstream mongrel {
    server 127.0.0.1:8150;
    server 127.0.0.1:8151;
    server 127.0.0.1:8152;
    server 127.0.0.1:8153;
}

# Defining web server
server {
    listen 216.86.155.55:80;
    server_name domain.tld;

    # All dynamic requests will go here
    location / {
        default_type text/html;
           
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_redirect off;
           
        # All POST requests go to mongrel directly
        if ($request_method = POST) {
            proxy_pass http://mongrel;
            break;
        }
           
        # Say nginx to try to fetch some key from memcache: "yourproject:Action:$uri", like ""yourproject:Action:/posts/1"
        set $memcached_key "yourproject:Action:$uri";
        memcached_pass localhost:11211;

        proxy_intercept_errors  on;
           
        # If no info would be found in memcache or memecache would be dead, go to /fallback location
        error_page 404 502 = /fallback$uri;
    }

    # This location would be called only if main location failed to serve request
    location /fallback/ {
        # This means, that we can't get to this location from outside - only by internal redirect
        internal;
           
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;          
        proxy_redirect off;

        # Pass request to mongrel
        proxy_pass http://mongrel/;
    }

    # Some static location to serve directly w/o bothering backend
    # (here should be more of such static paths or one regex-based location)
    location /images/ {
        root /rails/lazygeeks/public/current/public;
    }
}
Notice: This config is not optimal, but it shows us what we’re going to do here.
So, what do can we see above: nginx tries to fetch a page from memcache first using a key like “yourproject:Action:/some/uri“, then, it this approach failed (no page in cache, or cache is dead), it sends a request to the backend server.
But how pages would appear in the cache?“, you can ask. And you’d be right – we need to put them there manually from our application when first request comes to us and then all other requests to this specific URI will be served from our cache (I’d left this task for my readers to implement because or you can take a look to Dmytro Shteflyuk’s blog – he is going to post some information about how it could be done).
But what if our page would be changed a bit later?” – of course, you’d know when it happened and you’d remove a page from he cache ;-) . For example, you’d decide to implement this scheme for your /post/* URIs in your ultra-popular blog. Then you’ll need to remove a key “mycoolblog:Action:/post/X” from the cache when something changes on this page (like comment added or page edited).
And last, most interesting question you’d ask: “What if we have absolutely dynamic parts on our pages? Login field in the top part of the pages and ‘comment’ form for logged in users“. To solve this problem, we need to do following:
  1. Separate this small page partials generation from the main page logic to a separate URIs like /dynamic/login_field and /dynamic/comment_form.
  2. Add option “ssi on” to both locations in your nginx config: “location /” and “location /fallback” to ask nginx for SSI support in your proxied responses from mongrel and Rails
  3. Add one more location you your config (not mandatory, but it would work faster):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # This location would be called only from SSI tags
    location /dynamic {
        # This means, that we can't get to this location from outside - only by internal redirect
        internal;
               
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;          
        proxy_redirect off;

        # Pass request to mongrel
        proxy_pass http://mongrel;
    }
  4. Replace dynamic parts in your templates with
    1
    <!--# include virtual="full_url_to_your_partial" -->
    where full_url_to_your_partial is something like http://domain.tld/dynamic/login_field.
So, what we’ll get with all these changes? Now, when we have SSI enabled in our nginx service and we’ve replaced out dynamic partials with an appropriate SSI-tags, nginx will get our pages from backend or from cache and will find these SSI tags there. After this it will ask backend processes (with parallel queries, which is really great for scalability) for these small partials. Your application will be able to spend really small amount of resources to generate tiny partials without any complicated logic there. When nginx will get all info it needs, it will compose final page and send it to a user. That’s it! Your site becomes really fast and could serve tons of requests where before this modification it’d kill your brand new N-CPU server with M Gb of RAM ;-)
If you’ll try to implement this schema, you’ll definitely come to an idea of putting all this stuff to some rails plugin and just using some ssi_include some_url helper to output your partials and some simple code to mark, which actions should be cached and which shouldn’t. And, of course, you’ll understand, that you can do all this stuff even without nginx and SSI and without Rails – you can ask for these partials using AJAX requests on clients’ side or even compose both these approaches to fallback to AJAX when you have no SSI or switching between them with simple configuration setting. But anyways, this approach worth a try because it gives you huge performance boost with a pretty small cost of implementation.

Friday, September 10, 2010

Cherokee web server

Cherokee is really really fast. The speed at which any web server can serve requests for content is both directly tied to and limited by the I/O speed of underlying hardware and operating system. In this regard, a web server's performance is measured by the latency incurred after an I/O request to the underlying system has completed.
The primary objective of the Cherokee project is to reduce to zero the latency incurred between the time a dynamic or static system I/O request has completed and the time the resulting content is served to the requesting client. Admittedly, a very tough goal to reach.

In fact, it might be impossible. But achieving that which was once seen as impossible is what drives innovation. And it's innovation that drives the ongoing development of the Cherokee project, bringing us closer to the impossible with each new release.

To show the project's progression towards the ultimate goal, whenever a benchmark is performed it will be published right here. Older releases had impressive but not very thorough benchmarks. Whenever it is possible the conditions of the benchmark will be provided so that anyone can replicate the results. That is, after all, an essential basis of the Scientific method.

The benchmark consisted on half a million requests of a 1.7KiB static file, with 20 concurrent clients, using a 1Gbit/s local network. The results (fastest to slowest) were:
Cherokee:

Server Software:        Cherokee/0.8.1
Server Hostname:        10.0.0.102
Server Port:            80

Document Path:          /index.html
Document Length:        1795 bytes

Concurrency Level:      20
Time taken for tests:   17.819725 seconds
Complete requests:      500000
Failed requests:        0
Write errors:           0
Keep-Alive requests:    500000
Total transferred:      999007442 bytes
HTML transferred:       897506630 bytes
Requests per second:    28058.79 [#/sec] (mean)
Time per request:       0.713 [ms] (mean)
Time per request:       0.036 [ms] (mean, across all concurrent requests)
Transfer rate:          54747.93 [Kbytes/sec] received

Lighttpd:
Server Software:        lighttpd/1.4.19
Server Hostname:        10.0.0.102
Server Port:            80

Document Path:          /index.html
Document Length:        1795 bytes

Concurrency Level:      20
Time taken for tests:   21.248000 seconds
Complete requests:      500000
Failed requests:        0
Write errors:           0
Keep-Alive requests:    470598
Total transferred:      991856958 bytes
HTML transferred:       897503590 bytes
Requests per second:    23531.63 [#/sec] (mean)
Time per request:       0.850 [ms] (mean)
Time per request:       0.042 [ms] (mean, across all concurrent requests)
Transfer rate:          45585.94 [Kbytes/sec] received

NginX:
Server Software:        nginx/0.5.33
Server Hostname:        10.0.0.102
Server Port:            80

Document Path:          /index.html
Document Length:        1795 bytes

Concurrency Level:      20
Time taken for tests:   23.741872 seconds
Complete requests:      500000
Failed requests:        0
Write errors:           0
Keep-Alive requests:    500000
Total transferred:      1006000217 bytes
HTML transferred:       897500000 bytes
Requests per second:    21059.84 [#/sec] (mean)
Time per request:       0.950 [ms] (mean)
Time per request:       0.047 [ms] (mean, across all concurrent requests)
Transfer rate:          41379.30 [Kbytes/sec] received

Apache2.2:
Server Software:        Apache/2.2.8
Server Hostname:        10.0.0.102
Server Port:            80

Document Path:          /index.html
Document Length:        1795 bytes

Concurrency Level:      20
Time taken for tests:   35.438605 seconds
Complete requests:      500000
Failed requests:        0
Write errors:           0
Keep-Alive requests:    495064
Total transferred:      1043777896 bytes
HTML transferred:       897500000 bytes
Requests per second:    14108.91 [#/sec] (mean)
Time per request:       1.418 [ms] (mean)
Time per request:       0.071 [ms] (mean, across all concurrent requests)
Transfer rate:          28762.81 [Kbytes/sec] received
For the record: I did my best configuring all the servers in the very same way. In all the cases I removed unnecessary rules that could have slowed down the server (checks for htpasswd files and so on). And all the binaries came from the Debian repository, except for Cherokee 0.8.1 that hasn't been packaged yet.
Anyway, this benchmark has been just a quick test. It is not certainly representing the result that these servers would have handling real traffic though. So, in the following days I will try to do a new a more accurate benchmark with static and dynamic content, compression, redirections, etc. I'm pretty sure the results will be even better.

Cherokee + Apache + Lighttpd Benchmark

This benchmark was performed by Brian Rosner with Cherokee 0.6.0 beta2.

Software

  • cherokee 0.6.0 beta2
  • apache 2.0.59
  • lighttpd 1.4.16

Hardware

  • 733 MHz PIII
  • 256 MB RAM
  • 80GB 7200RPM IDE HD
  • Debian GNU/Linux 4.0

Background

I installed a fresh installation of Debian on the server hardware. Right after you login you will need to get sudo to perform root commands from your account:
su
apt-get install sudo
Then add yourself to the /etc/sudoers file by running visudo and adding yourself in the user section. I just followed the root entry as this does not need to be a very secure server since it will not be running publicly. Now make sure you get back to your account and do:
sudo apt-get install gcc make automake autoconf libtool
mkdir src ; cd src
sudo mkdir /usr/local/cherokee
sudo mkdir /usr/local/lighttpd
The installed version of gcc is 4.1.2

Cherokee Setup Details

The following is what I executed to build Cherokee:
wget http://www.cherokee-project.com/download/0.6/0.6.0/cherokee-0.6.0b863.tar.gz
tar zxvf cherokee-0.6.0b863.tar.gz
cd cherokee-0.6.0b863
./configure --prefix=/usr/local/cherokee/0.6.0b863
make
sudo make install
Here is the configuration for cherokee:
server!port = 80
server!timeout = 60
server!keepalive = 1
server!keepalive_max_requests = 500
server!pid_file = /var/run/cherokee.pid
server!server_tokens = full
server!encoder!gzip!allow = html,html,txt
server!panic_action = /usr/local/cherokee/0.6.0b863/bin/cherokee-panic
server!mime_files = /usr/local/cherokee/0.6.0b863/etc/cherokee/mime.types

vserver!default!document_root = /usr/local/cherokee/0.6.0b863/var/www
vserver!default!directory_index = index.html

vserver!default!directory!/!handler = common
vserver!default!directory!/!handler!iocache = 1
vserver!default!directory!/!priority = 1
To run the web server I used:
cd /usr/local/cherokee/0.6.0b863
sudo sbin/cherokee -C etc/cherokee/cherokee.conf

Apache Setup Details

The following is what I executed to build Apache:
wget http://apache.oregonstate.edu/httpd/httpd-2.0.59.tar.gz
tar zxvf httpd-2.0.59.tar.gz
cd httpd-2.0.59
./configure --prefix=/usr/local/apache/2.0.59
make
sudo make install
I used the supplied highperformance.conf configuration file. I started the server with:
cd /usr/local/apache/2.0.59
sudo bin/httpd -k start -f conf/highperformance.conf
The server ran using prefork.

Lighttpd Setup Details

The following is what I executed to build lighttpd:
wget http://www.lighttpd.net/download/lighttpd-1.4.16.tar.gz
tar zxvf lighttpd-1.4.16.tar.gz
cd lighttpd-1.4.16
./configure --prefix=/usr/local/lighttpd/1.4.16
make
sudo make install
The configuration I used looked like this:
server.modules = (
    "mod_access",
    "mod_accesslog"

)

server.document-root = "/var/www"

mimetype.assign = (
    ".html" => "text/html",
    ".txt" => "text/plain"

)
I started the server with:
cd /usr/local/lighttpd/1.4.16
sudo sbin/lighttpd -f sbin/lighttpd.conf

Benchmark

I will perform several different benchmarks on each webserver. This is to help gauge what type of performance each server can handle in the different conditions. Each test will have SSL turned on and turned off.

small static file test

  • filesize: 99 bytes
  • command: ab -c 2 -t 2 -k http://localhost/index0.html

large static file test

  • filesize: 1.5MB
  • command: ab -c 2 -t 2 -k http://localhost/static.txt

Results

I have included cherokee with both iocaching on and off. The out of the box setting is that iocache is turned on.

small static file test w/ keepalive

  • cherokee 0.6.0b863 w/ iocache - 7816 reqs./sec.
  • cherokee 0.6.0b863 w/o iocache - 5761 reqs./sec.
  • lighttpd 1.4.16 - 4884 reqs./sec.
  • apache 2.0.59 - 2924 reqs./sec.

small static file test w/o keepalive

  • cherokee 0.6.0b863 w/ iocache - 2182 reqs./sec.
  • cherokee 0.6.0b863 w/o iocache - 1874 reqs./sec.
  • lighttpd 1.4.16 - 2255 reqs./sec.
  • apache 2.0.59 - 1250 reqs./sec.

large static file test w/ keepalive

  • cherokee 0.6.0b863 w/ iocache - 108 reqs./sec.
  • cherokee 0.6.0b863 w/o iocache - 107 reqs./sec.
  • lighttpd 1.4.16 - 106 reqs./sec.
  • apache 2.0.59 - 94 reqs./sec.

large static file test w/o keepalive

  • cherokee 0.6.0b863 w/ iocache - 88 reqs./sec.
  • cherokee 0.6.0b863 w/o iocache - 88 reqs./sec.
  • lighttpd 1.4.16 - 92 reqs./sec.
  • apache 2.0.59 - 118 reqs./sec.
Courtesy : http://www.cherokee-project.com/benchmarks.html