Synopsis
Today, almost any ecommerce website uses a load-balancer or an application delivery controller in front of it, in order to improve its availability and reliability.
In today’s article, I’ll explain how we can take advantage of ADCs’ layer 7 features to improve an ecommerce website performance and give the best experience to end-user in order to increase the revenue.
The points on which we can work are:
- Network optimization
- Traffic regulation
- Overusage protection
- User “tagging” based on cart content
- User “tagging” based purchase phase
- Blackout prevention
- SEO optimization
- Partner slowness protection
Note: the list is not exhaustive and the given example will be very simple. My purpose is not to create a very complicated configuration but give the reader clues on how he can take advantage of our product.
Note2: I won’t discuss about static content, there is already one article with a lot of details about it on this blog.
As Usual, the configuration example below applies on our ALOHA ADC appliance, but should work as well on HAProxy 1.5.
Network optimization
Client-side network latency have a negative impact on websites: the slowest the user connectivity is, the longest the connection will remain opened on the web server, the time for the client to download the object. This could last much longer if the client and server uses HTTP Keepalives.
Basically, this is what happens with basic layer 4 load-balancers like LVS or some other appliance vendors, when the TCP connection is established between the client and the server directly.
Since HAProxy works as a HTTP reverse-proxy, it breaks the TCP connection and enables TCP buffering between both connections. It means HAProxy reads the response at the speed of the server and delivers it at the speed of the client.
Slow clients with high latency will have no impact anymore on application servers because HAProxy “hides” it by its own latency to the server.
An other good point is that you can enable HTTP Keepalives on the client side and disable it on the server side: it allows a client to re-use a connection to download several objects, with no impact on server resources.
TCP buffering does not require any configuration, while enabling client side HTTP keep-alive is achieved by the line option http-server-close.
And The configuration is pretty simple:
# default options defaults option http-server-close mode http log 10.0.0.1 local2 option httplog timeout connect 5s timeout client 20s timeout server 15s timeout check 1s timeout http-keep-alive 1s timeout http-request 10s # slowloris protection default-server inter 3s fall 2 rise 2 slowstart 60s # main frontend frontend ft_web bind 10.0.0.3:80 default_backend bk_appsrv # application server farm backend bk_appsrv balance roundrobin # app servers must say if everything is fine on their side and # they are ready to process traffic option httpchk GET /appcheck http-check expect rstring [oO][kK] cookie SERVERID insert indirect nocache server s1 10.0.1.101:80 cookie s1 check server s2 10.0.1.102:80 cookie s2 check
Traffic Regulation
Any server has a maximum capacity. The more it handles requests, the slower it will be to process each request. And if it has too many requests to process, it can even crash and won’t obviously be able to answer to anybody!
HAProxy can regulate request streams to servers in order to prevent them from crashing or even slowing down. Note that when well set up, it can allow you to use your server at their maximum capacity without never being in trouble.
Basically, HAProxy is able to manage request queues.
You can configure traffic regulation with fullconn and maxconn parameters in the backend and with minconn and maxconn parameters on the server line description.
Let’s update our server line description above with a simple maxconn parameter:
server s1 10.0.1.101:80 cookie s1 check maxconn 250 server s2 10.0.1.102:80 cookie s2 check maxconn 250
Note: there would be many many things to say about queueing and the HAProxy parameter cited above, but this is not the purpose of the current article.
Over usage protection
By over usage, I mean that you want to be able to handle an unexpected flow of users and be able to classify users in 2 categories:
- Those who have already been identified by the website and are using it
- Those who have just arrived and wants to use it
The difference between both type of users can be done through the ecommerce CMS cookie: identified users owns a Cookie while brand new users doesn’t.
If you know your server farm has the capacity to manage 10000 users, then you don’t want to allow more than this number until you expand the farm.
Here is the configuration to protect against over-usage (The application Cookie is “MYCOOKIE”.):
# default options defaults option http-server-close mode http log 10.0.0.2 local2 option httplog timeout connect 5s timeout client 20s timeout server 15s timeout check 1s timeout http-keep-alive 1s timeout http-request 10s # slowloris protection default-server inter 3s fall 2 rise 2 slowstart 60s # main frontend frontend ft_web bind 10.0.0.3:80 # update the number below to the number of people you want to allow acl maxcapacity table_cnt(bk_appsrv) ge 10000 acl knownuser hdr_sub(Cookie) MYCOOK # route any unknown user to the sorry page if we reached the maximum number # of allowed users and the request does not have a cookie use_backend bk_sorrypage if maxcapacity !knownuser default_backend bk_appsrv # appsrv backend for dynamic content backend bk_appsrv balance roundrobin # define a stick-table with at most 10K entries # cookie value would be cleared from the table if not used for 10 mn stick-table type string len 32 size 10K expire 10m nopurge stick store-response set-cookie(MYCOOK) # Reset the TTL in the stick table each time a request comes in stick store-request cookie(MYCOOK) # app servers must say if everything is fine on their side and # they are ready to process traffic option httpchk GET /appcheck http-check expect rstring [oO][kK] cookie SERVERID insert indirect nocache server s1 10.0.1.101:80 cookie s1 check maxconn 250 server s2 10.0.1.102:80 cookie s2 check maxconn 250 # sorry page management backend bk_sorrypage balance roundrobin server s1 10.0.1.103:80 check maxconn 1000 server s2 10.0.1.104:80 check maxconn 1000
User tagging based on cart content
When your architecture has enough capacity, you don’t need to classify users. But imagine if your platform runs out of capacity, you want to be able to reserve resources for users who have no article in the cart, that way the website looks very fast, hopefully these users will buy some articles.
Just configure your ecommerce application to setup a cookie with some information about the cart: either the number of article, the whole value, etc…
In the example below, we’ll consider the application creates a cookie named CART and the number of articles as a value.
Based on the information provided by this cookie, we’ll take routing decision and choose different farms with different capacity.
# default options defaults option http-server-close mode http log 10.0.0.2 local2 option httplog timeout connect 5s timeout client 20s timeout server 15s timeout check 1s timeout http-keep-alive 1s timeout http-request 10s # slowloris protection default-server inter 3s fall 2 rise 2 slowstart 60s # main frontend frontend ft_web bind 10.0.0.3:80 # update the number below to the number of people you want to allow acl maxcapacity table_cnt(bk_appsrv) ge 10000 acl knownuser hdr_sub(Cookie) MYCOOK acl empty_cart hdr_sub(Cookie) CART=0 # route any unknown user to the sorry page if we reached the maximum number # of allowed users and the request does not own a cookie use_backend bk_sorrypage if maxcapacity !knownuser # Once the user have something in the cart, move it to a farm with less resources # only when there are too many users on the website use_backend bk_appsrv if maxcapacity !empty_cart default_backend bk_appsrv_empty_cart # Default farm when everything goes well backend bk_appsrv_empty_cart balance roundrobin # create the entry in the table when the server generates the cookie stick store-response set-cookie(MYCOOK) table bk_appsrv # Reset the TTL in the stick table each time a request comes in stick store-request cookie(MYCOOK) table bk_appsrv # app servers must say if everything is fine on their side # and they can process requests option httpchk GET /appcheck http-check expect rstring [oO][kK] cookie SERVERID insert indirect nocache server s1 10.0.1.101:80 cookie s1 check maxconn 200 server s2 10.0.1.102:80 cookie s2 check maxconn 200 # Reserve resources for the few users which have something in their cart backend bk_appsrv balance roundrobin # define a stick-table with at most 10K entries # cookie would be cleared from the table if not used for 10 mn stick-table type string len 32 size 10K expire 10m nopurge # create the entry in the table when the server generates the cookie stick store-response set-cookie(MYCOOK) # Reset the TTL in the stick table each time a request comes in stick store-request cookie(MYCOOK) cookie SERVERID insert indirect nocache server s1 10.0.1.101:80 cookie s1 track bk_appsrv_empty_cart/s1 maxconn 50 server s2 10.0.1.102:80 cookie s2 track bk_appsrv_empty_cart/s2 maxconn 50 backend bk_sorrypage balance roundrobin server s1 10.0.1.103:80 check maxconn 1000 server s2 10.0.1.104:80 check maxconn 1000
User tagging based on purchase phase
The synopsis of this chapter is the same as the precedent chapter: behing able to classify users and ability to reserve resources.
But this time, we’ll identify users based on the phase they are. Basically, we’ll consider two phases:
- browsing phase, when people add articles in the cart
- purchasing phase, when people have finished filling up the cart and start providing billing, delivery and payment information
In order to classify users, we’ll use the URL path. It starts by /purchase/ when the user is in the purchasing phase. Any other URLs are considered as browsing.
Based on the information provided by requested URL, we’ll take routing decision and choose different farms with different capacity.
# defaults options defaults option http-server-close mode http log 10.0.0.2 local2 option httplog timeout connect 5s timeout client 20s timeout server 15s timeout check 1s timeout http-keep-alive 1s timeout http-request 10s # slowloris protection default-server inter 3s fall 2 rise 2 slowstart 60s # main frontend frontend ft_web bind 10.0.0.3:80 # update the number below to the number of people you want to allow acl maxcapacity table_cnt(bk_appsrv) ge 10000 acl knownuser hdr_sub(Cookie) MYCOOK acl purchase_phase path_beg /purchase/ # route any unknown user to the sorry page if we reached the maximum number # of allowed users and the request does not own a cookie use_backend bk_sorrypage if maxcapacity !knownuser # Once the user is in the purchase phase, move it to a farm with less resources # only when there are too many users on the website use_backend bk_appsrv if maxcapacity purchase_phase default_backend bk_appsrv_browse # Default farm when everything goes well backend bk_appsrv_browse balance roundrobin # create the entry in the table when the server generates the cookie stick store-response set-cookie(MYCOOK) table bk_appsrv # Reset the TTL in the stick table each time a request comes in stick store-request cookie(MYCOOK) table bk_appsrv # app servers must say if everything is fine on their side # and they can process requests option httpchk GET /appcheck http-check expect rstring [oO][kK] cookie SERVERID insert indirect nocache server s1 10.0.1.101:80 cookie s1 check maxconn 200 server s2 10.0.1.102:80 cookie s2 check maxconn 200 # Reserve resources for the few users in the purchase phase backend bk_appsrv balance roundrobin # define a stick-table with at most 10K entries # cookie would be cleared from the table if not used for 10 mn stick-table type string len 32 size 10K expire 10m nopurge # create the entry in the table when the server generates the cookie stick store-response set-cookie(MYCOOK) # Reset the TTL in the stick table each time a request comes in stick store-request cookie(MYCOOK) cookie SERVERID insert indirect nocache server s1 10.0.1.101:80 cookie s1 track bk_appsrv_browse/s1 maxconn 50 server s2 10.0.1.102:80 cookie s2 track bk_appsrv_browse/s2 maxconn 50 backend bk_sorrypage balance roundrobin server s1 10.0.1.103:80 check maxconn 1000 server s2 10.0.1.104:80 check maxconn 1000
Blackout prevention
A website blackout is the worst thing that could happen: something has crashed and the application does not work anymore, or none of the servers are reachable.
When such thing occurs, it is common to get 503 errors or a blank page after 30 seconds.
In both cases, end users have a negative feeling about the website. At least an excuse page with an estimated recovery date would be appreciated. HAProxy allows to communicate to end user even if none of the servers are available.
The configuration below shows how to do it:
# defaults options defaults option http-server-close mode http log 10.0.0.2 local2 option httplog timeout connect 5s timeout client 20s timeout server 15s timeout check 1s timeout http-keep-alive 1s timeout http-request 10s # slowloris protection default-server inter 3s fall 2 rise 2 slowstart 60s # main frontend frontend ft_web bind 10.0.0.3:80 # update the number below to the number of people you want to allow acl maxcapacity table_cnt(bk_appsrv) ge 10000 acl knownuser hdr_sub(Cookie) MYCOOK acl purchase_phase path_beg /purchase/ acl no_appsrv nbsrv(bk_appsrv_browse) eq 0 acl no_sorrysrv nbsrv(bk_sorrypage) eq 0 # worst case management use_backend bk_worst_case_management if no_appsrv no_sorrysrv # use sorry servers if available use_backend bk_sorrypage if no_appsrv !no_sorrysrv # route any unknown user to the sorry page if we reached the maximum number # of allowed users and the request does not own a cookie use_backend bk_sorrypage if maxcapacity !knownuser # Once the user is in the purchase phase, move it to a farm with less resources # only when there are too many users on the website use_backend bk_appsrv if maxcapacity purchase_phase default_backend bk_appsrv_browse # Default farm when everything goes well backend bk_appsrv_browse balance roundrobin # create the entry in the table when the server generates the cookie stick store-response set-cookie(MYCOOK) table bk_appsrv # Reset the TTL in the stick table each time a request comes in stick store-request cookie(MYCOOK) table bk_appsrv # app servers must say if everything is fine on their side # and they can process requests option httpchk GET /appcheck http-check expect rstring [oO][kK] cookie SERVERID insert indirect nocache server s1 10.0.1.101:80 cookie s1 check maxconn 200 server s2 10.0.1.102:80 cookie s2 check maxconn 200 # Reserve resources for the few users in the purchase phase backend bk_appsrv balance roundrobin # define a stick-table with at most 10K entries # cookie would be cleared from the table if not used for 10 mn stick-table type string len 32 size 10K expire 10m nopurge # create the entry in the table when the server generates the cookie stick store-response set-cookie(MYCOOK) # Reset the TTL in the stick table each time a request comes in stick store-request cookie(MYCOOK) cookie SERVERID insert indirect nocache server s1 10.0.1.101:80 cookie s1 track bk_appsrv_browse/s1 maxconn 50 server s2 10.0.1.102:80 cookie s2 track bk_appsrv_browse/s2 maxconn 50 backend bk_sorrypage balance roundrobin server s1 10.0.1.103:80 check maxconn 1000 server s2 10.0.1.104:80 check maxconn 1000 backend bk_worst_case_management errorfile 503 /etc/haproxy/errors/503.txt
And the content of the file /etc/haproxy/errors/503.txt could look like:
HTTP/1.0 200 OK Cache-Control: no-cache Connection: close Content-Type: text/html Content-Length: 246 <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Maintenance</title> </head> <body> <h1>Maintenance</h1> We're sorry, ecommerce.com is currently under maintenance and will come back soon. </body> </html>
SEO optimization
Most search engines takes now into account pages response time.
The configuration below redirects search engine bots to a dedicated server and if it’s not available, then it is forwarded to the default farm. The bot is identified by its User-Agent header.
# defaults options defaults option http-server-close mode http log 10.0.0.2 local2 option httplog timeout connect 5s timeout client 20s timeout server 15s timeout check 1s timeout http-keep-alive 1s timeout http-request 10s # slowloris protection default-server inter 3s fall 2 rise 2 slowstart 60s # main frontend frontend ft_web bind 10.0.0.3:80 # update the number below to the number of people you want to allow acl maxcapacity table_cnt(bk_appsrv) ge 10000 acl knownuser hdr_sub(Cookie) MYCOOK acl purchase_phase path_beg /purchase/ acl bot hdr_sub(User-Agent) -i googlebot bingbot slurp acl no_appsrv nbsrv(bk_appsrv_browse) eq 0 acl no_sorrysrv nbsrv(bk_sorrypage) eq 0 acl no_seosrv nbsrv(bk_seo) eq 0 # worst caperformancese management use_backend bk_worst_case_management if no_appsrv no_sorrysrv # use sorry servers if available use_backend bk_sorrypage if no_appsrv !no_sorrysrv # redirect bots use_backend bk_seo if bot !no_seosrv use_backend bk_appsrv if bot no_seosrv # route any unknown user to the sorry page if we reached the maximum number # of allowed users and the request does not own a cookie use_backend bk_sorrypage if maxcapacity !knownuser # Once the user is in the purchase phase, move it to a farm with less resources # only when there are too many users on the website use_backend bk_appsrv if maxcapacity purchase_phase default_backend bk_appsrv_browse # Default farm when everything goes well backend bk_appsrv_browse balance roundrobin # create the entry in the table when the server generates the cookie stick store-response set-cookie(MYCOOK) table bk_appsrv # Reset the TTL in the stick table each time a request comes in stick store-request cookie(MYCOOK) table bk_appsrv # app servers must say if everything is fine on their side # and they can process requests option httpchk GET /appcheck http-check expect rstring [oO][kK] cookie SERVERID insert indirect nocache server s1 10.0.1.101:80 cookie s1 check maxconn 200 server s2 10.0.1.102:80 cookie s2 check maxconn 200 # Reserve resources for the few users in the purchase phase backend bk_appsrv balance roundrobin # define a stick-table with at most 10K entries # cookie would be cleared from the table if not used for 10 mn stick-table type string len 32 size 10K expire 10m nopurge # create the entry in the table when the server generates the cookie stick store-response set-cookie(MYCOOK) # Reset the TTL in the stick table each time a request comes in stick store-request cookie(MYCOOK) cookie SERVERID insert indirect nocache server s1 10.0.1.101:80 cookie s1 track bk_appsrv_browse/s1 maxconn 50 server s2 10.0.1.102:80 cookie s2 track bk_appsrv_browse/s2 maxconn 50 # Reserve resources search engines bot backend bk_seo option httpchk GET /appcheck http-check expect rstring [oO][kK] server s3 10.0.1.103:80 check backend bk_sorrypage balance roundrobin server s1 10.0.1.103:80 check maxconn 1000 server s2 10.0.1.104:80 check maxconn 1000 backend bk_worst_case_management errorfile 503 /etc/haproxy/errors/503.txt
Partner slowness protection
Some ecommerce website relies on partners for some product or services. Unfortunately, if the partner’s webservice application slows down, then our own application will slow down. Even worst, we may see sessions pilling up and server crashes due to lack of resources…
In order to prevent this, just configure your appserver to pass through HAProxy to reach your partners’ webservices. HAProxy can shut a session if a partner is too slow to answer. If the partner complain you don’t send them enough deals, just tell him to improve his platform, maybe using a ADC like HAProxy / ALOHA Load-Balancer 😉
frontend ft_partner1 bind 10.0.0.3:8001 use_backend bk_partner1 backend bk_partner1 # the partner has 2 seconds to answer each requests timeout server 2s # you can add a maxconn here if you're not supposed to open # too many connections on the partner application server partner1 1.2.3.4:80 check