Although tools like Varnish can improve performance and scalability for static sites, when user-specific content is needed, a hit to the PHP/Ruby/Python/.Net backend is still required, causing scalability issues. We’ll look at a brand-new Nginx module which implements an ultra-fast and scalable solution to this problem, changing the way developers think about designing sites with user-specific content.
When dynamic becomes static - the next step in web caching techniques
1. When dynamic becomes static
(the next step in web caching techniques)
Wim Godden
Cu.be Solutions
@wimgtr
2. Who am I ?
Wim Godden (@wimgtr)
Founder of Cu.be Solutions (http://cu.be)
Open Source developer since 1997
Developer of OpenX, PHPCompatibility, PHPConsistent, ...
Speaker at Open Source conferences
3. Who are you ?
Developers ?
System/network engineers ?
Managers ?
31. Caching blocks with individual TTLs
Top header
(cache for 2h)
Latest news
Article content page
Page content
Navigation
(cache for 1h)
32. Caching blocks with individual cache duration
Top header
(cache for 2h)
Latest news (cache for 2m)
Article content page
Page content (cache for 30m)
Navigation
(cache for 1h)
34. ESI – how it works
GET /page GET /page
<html>
...
<esi:include src="/top"/>
<esi:include src="/nav"/>
<div id=”something”>
<esi:include src="/latest-news"/>
</div>
<esi:include src="/article/id/732"/>
...
</html>
35. ESI – how it works
GET /top
<div id=”top-part”>
<a href=”/login”>Login</a>
</div>
36. ESI – how it works
GET /page GET /page
<html>
...
<esi:include src="/top"/>
<esi:include src="/nav"/>
<div id=”something”>
<esi:include src="/latest-news"/>
</div>
<esi:include src="/article/id/732"/>
...
</html>
37. ESI – how it works
GET /page GET /page
<html>
...
<div id=”top-part”>
<a href=”/login”>Login</a>
</div>
<esi:include src="/nav"/>
<div id=”something”>
<esi:include src="/latest-news"/>
</div>
<esi:include src="/article/id/732"/>
...
</html>
38. Varnish - what can/can't be cached ?
Can :
Static pages
Images, js, css
Static parts of pages that don't change often (ESI)
Can't :
POST requests
Very large files (it's not a file server !)
Requests with Set-Cookie
User-specific content
39. ESI → no caching on user-specific content ?
Logged in as : Wim Godden
5 messages
cache for cache for 5min
1h
cache for 0s ?
47. SLIC on Nginx
<slic:include key="top" src="/top" session="true" />
<slic:include key="news" src="/news" />
<slic:include
key="menu"
src="/menu" />
Logged in as : Wim Godden
5 messages ???
48. New message is sent...
POST /send
DB
insert into...
set(...)
top (in SLIC session)
49. Advantages
No repeated GET hits to webserver anymore !
At login : POST → warm up the cache !
No repeated hits for user-specific content
Not even for non-specific content
51. First release : ESI
Part of the ESI 1.0 spec
Only relevant features implemented
Extension for dynamic session support
But : unavailable for copyright reasons
52. Rebuilt from scratch : SLIC
Control structures : if/else, switch/case, foreach
Variable handling
Strings : concatenation, substring, …
Exception handling, header manipulation, …
JSON support !
53. SLIC code samples
You are logged in as : <slic:session_var("person_name") />
56. Approaches – full block
<p id=”LoggedInAs”>
You are logged in as : Wim Godden
</p>
<p id=”MessageCount”>
You have 5 messages
</p>
Logged in as : Wim Godden
5 messages
<slic:include key="top" src="/top" session="true" />
top_432
57. Approaches – individual variables
Logged in as : Wim Godden
<p id=”LoggedInAs”>
You are logged in as : <slic:session_var("person_name") />
</p>
<p id=”MessageCount”>
You have <slic:session_var(“messages”) /> messages
</p>
5 messages
<slic:include key="top" src="/top" session="true" />
58. Approaches – JSON
Logged in as : Wim Godden
5 messages
<slic:include key="top" src="/top" session="true" />
<p id=”LoggedInAs”>
You are logged in as : <slic:session_var("userData").person_name />
</p>
<p id=”MessageCount”>
You have <slic:session_var(“userData”).message_count /> messages
</p>
59. Identifying the user
In Nginx configuration :
slic_session_cookie <name> → Defined by language (or configurable)
slic_session_identifier <string> → Defined by you
Example for PHP :
slic_session_cookie PHPSESSID
slic_session_identifier UserID
60. Identifying the user
Cookie :
PHPSESSID =
jpsidc1po35sq9q3og4f3hi6e2
Nginx + SLIC
4g3e2t UserID_jpsidc1po35sq9q3og4f3hi6e2
61. Retrieving user specific content
Nginx + SLIC
get userData_432
Cookie :
PHPSESSID =
jpsidc1po35sq9q3og4f3hi6e2
63. Figures
2nd customer :
No. of web servers : 72 → 8
No. of db servers : 15 → 4
Total : 87 → 12 (86% reduction !)
Last customer :
No. of total servers : +/- 1350
Expected reduction : 1350 → 380
Expected savings : €1.5 Million per year
65. Code changes
Required
Template conversion
Push-to-DB → Push-to-DB + Push-to-Cache
Choice :
If user is logged in → push updates to cache
If user is not logged in → warm up cache on login
66. Availability
Good news :
The concept is solid : ESI version stable at 4 customers
Open Source
Bad news :
First customer holds copyrights
Total rebuild
Q2 2015 : v0.1 (include, variable handling, …)
Final release : Q3 2015
Site : http://slic.cu.be
Code : Github
67. Some technical details
Written in Lua (with LuaJIT)
Each SLIC:include is a subrequest
Groups cache key requests together for multiget
Shares cache results across all subrequests
Memcached implemented
Redis and others in the pipeline
Not RFC compliant yet
Unit tested
Future (1.0 or beyond) :
Better cache abstraction
Template compilation
Translation functionality
...