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 revenue.
Common eCommerce Website Issues
The points on which we can improve an eCommerce website are:
Network optimization
Traffic regulation
Overusage protection
User “tagging” based on the cart content
User “tagging” based purchase phase
Blackout prevention
SEO optimization
Partner slowness protection
The configuration example below applies to our ALOHA load balancer, but should work as well on HAProxy 1.5.
Network Optimization
Client-side network latency has a negative impact on websites: the slowest the user connectivity is, the longest the connection will remain open on the web server, and the time for the client to download the object. This could last much longer if the client and server use 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.
Another 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.
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 of an eCommerce Website
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 be able to answer 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 its maximum capacity without ever being in trouble.
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
Overload Protection
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 want to use it
The difference between both types of users can be done through the eCommerce CMS cookie: identified users owns a Cookie while brand new users don’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 articles, 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 decisions and choose different farms with different capacities.
# 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: being able to classify users and ability to reserve resources. But this time, we’ll identify users based on the phase they are in. Basically, we’ll consider two website purchase phases:
browsing phase, when people add articles to 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 browsing. Based on the information provided by the requested URL, we’ll take a routing decision and choose different farms with different capacities.
# 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
Website 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 a 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 (Page Response Time)
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.
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
Subscribe to our blog.
Get the latest release updates, tutorials, and deep-dives from HAProxy experts.