projects Beyond the LAMP stack By Thijs Feryn PHP
Hi, I’m Thijs
I’m @ThijsFeryn on Twitter
I’m an Evangelist At
I’m a at board member
LAMP Stack?
CPU memory I/O PHP consumes lots of
FasterPHP
Differentapproach
Offloading
•Webserver •Database •User process
Caching
Optimizedatabase Optimizeruntime Avoid Avoid
Don’t recompute data that hasn’t changed
Offloadthewebserver
Varnish
Reverse proxy User Varnish Server Proxy in	the	data center
✓ Varnish Configuration Language ✓ Edge Side Include support ✓ Gzip compression/decompression ✓ Cache purging ✓ HTTP streaming ✓ Grace mode ✓ Configure backends ✓ Backend loadbalancing ✓ ACL protection ✓ VMODs in C Varnish
Extend the default behavior in VCL
✓Strip tracking cookies (Google Analytics, …) ✓Sanitize URL ✓URL whitelist/blacklist ✓PURGE ACLs ✓Edge Side Include rules ✓Alway cache static files ✓Extend hash keys ✓Override TTL ✓Define grace mode What to extend?
vcl 4.0; import std; acl purge { "127.0.0.1" } backend default { .host = "176.62.160.59"; .port = "80"; .connect_timeout = 600s; .first_byte_timeout = 600s; .between_bytes_timeout = 600s; } sub vcl_recv { if (req.http.Authorization) { return (pass); } if (req.http.Accept-Encoding) { if (req.http.Accept-Encoding ~ "gzip") { set req.http.Accept-Encoding = "gzip"; } else if (req.http.Accept-Encoding ~ "deflate") { set req.http.Accept-Encoding = "deflate"; } else { unset req.http.Accept-Encoding; } } if (req.method == "PURGE") { if (!client.ip ~ purge) {
Minimal VCL Let the application handle it
Cachinginyourarchitecture
RespectHTTP
Cache-control: public, max- age=3600, s-maxage=7200 Cache-control: no-cache, no- store VS
Cookies?
State
Non- cacheable content
Edge Side Includes
<!DOCTYPE html> <html> <head> <title>The Demo</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <link rel="stylesheet" href="/css/bootstrap.min.css"> <link rel="stylesheet" href="/css/bootstrap-theme.min.css"> <script src=“/js/jquery-2.1.4.min.js"></script> <script src="/js/bootstrap.min.js"></script> </head> <body> <nav class="navbar navbar-inverse navbar-fixed-top"> <esi:include src=“http://mysite.dev/nav" /> </nav> <div class="jumbotron"> <esi:include src="http://mysite.dev/jumbotron" /> </div> <div class="container"> {% block content %}{% endblock %} <hr> <footer> <esi:include src="http://mysite.dev/footer" /> </footer> </div> </body> </html> ESI tags
Or just use AJAX Async Graceful degradition
✓Cache pages and static assets ✓Fastest way ✓Hit rate may vary ✓Chop your content up in pieces ✓Use ESI or AJAX ✓Gateway to your application Where does Varnish fit in?
You can also host your static files on a separate set of Nginx servers
ContentDeliveryNetwork
CDNs are nothing but a bunch of reverse caching proxies
Put the content where your user is
Offloadthedatabase
Data is stored for flexibility, not for performance SQL (joins) allow different compositions of the same data
Makethedataretrievalfaster
✓ Key-value store ✓ Fast ✓ Lightweight ✓ Data stored in RAM ✓ ~Memcached ✓ Data types ✓ Data persistance ✓ Replication ✓ Clustering Redis
Redis $ redis-cli 127.0.0.1:6379> ping PONG 127.0.0.1:6379> set mykey somevalue OK 127.0.0.1:6379> get mykey "somevalue
✓ Strings ✓ Hashes ✓ Lists ✓ Sets ✓ Sorted sets ✓ Geo ✓ … Redis data types
Redis $ redis-cli 127.0.0.1:6379> hset customer_1234 id 1234 (integer) 1 127.0.0.1:6379> hset customer_1234 items_in_cart 2 (integer) 1 127.0.0.1:6379> hmset customer_1234 firstname Thijs lastname Feryn OK 127.0.0.1:6379> hgetall customer_1234 1) "id" 2) "1234" 3) "items_in_cart" 4) "2" 5) "firstname" 6) "Thijs" 7) "lastname" 8) "Feryn" 127.0.0.1:6379>
$ redis-cli 127.0.0.1:6379> lpush products_for_customer_1234 5 (integer) 1 127.0.0.1:6379> lpush products_for_customer_1234 345 (integer) 2 127.0.0.1:6379> lpush products_for_customer_1234 78 12 345 (integer) 5 127.0.0.1:6379> llen products_for_customer_1234 (integer) 5 127.0.0.1:6379> lindex products_for_customer_1234 1 "12" 127.0.0.1:6379> lindex products_for_customer_1234 2 "78" 127.0.0.1:6379> rpop products_for_customer_1234 "5" 127.0.0.1:6379> rpop products_for_customer_1234 "345" 127.0.0.1:6379> rpop products_for_customer_1234 "78" 127.0.0.1:6379> rpop products_for_customer_1234 "12" 127.0.0.1:6379> rpop products_for_customer_1234 "345" 127.0.0.1:6379> rpop products_for_customer_1234 (nil) 127.0.0.1:6379> Redis
Clustering
✓ Database/API cache ✓ PHP session storage ✓ Message queue (lists) ✓ NoSQL database ✓ Real-time data retrieval Where does Redis fit in?
Basically: Real-time & volatile data
BUT: MySQL can still remain “source of truth” database
✓Full-text search engine ✓Analytics engine ✓NoSQL database ✓Lucene based ✓Built-in clustering, replication, sharding ✓RESTful interface ✓Schemaless ElasticSearch
POST /my-index {"acknowledged":true} POST/my-index/my-type { "key" : "value", "date" : "2015-05-10", "counter" : 1, "tags" : ["tag1","tag2","tag3"] } { "_index": "my-index", "_type": "my-type", "_id": "AU089olr9oI99a_rK9fi", "_version": 1, "created": true } Confirmation
GET/my-index/my-type/AU089olr9oI99a_rK9fi?pretty { "_index": "my-index", "_type": "my-type", "_id": "AU089olr9oI99a_rK9fi", "_version": 1, "found": true, "_source": { "key": "value", "date": "2015-05-10", "counter": 1, "tags": [ "tag1", "tag2", "tag3" ] } } Retrieve document by id Document & meta data
Analyzed vs non-analyzed Full-text vs exact value Filter vs Query
Search
POST /products/product/_search?pretty { "query": { "match": { "name.raw": "Linen Blazer" } } } POST /products/product/_search?pretty { "query": { "filtered": { "query": { "match_all": {} }, "filter": { "term": { "name": "Linen Blazer" } } } } } Matches 2 products Matches 1 product
Aggregations
Group by on steroids
POST /products/product/_search?pretty { "fields": ["category","price","name"], "query": { "match": { "name.raw": "blazer" } }, "aggs": { "avg_price": { "avg": { "field": "price" } }, "min_price" : { "min": { "field": "price" } }, "max_price" : { "max": { "field": "price" } }, "number_of_products_per_category" : { "terms": { "field": "category", "size": 10 } } } } Multi-group by & query
"aggregations": { "min_price": { "value": 455 }, "number_of_products_per_category": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "key": "Blazers", "doc_count": 2 }, { "key": "Default Category", "doc_count": 2 }, { "key": "Men", "doc_count": 2 } ] }, "max_price": { "value": 490 }, "avg_price": { "value": 472.5 } } Aggregation output
Clustering
✓ Full-text search engine with drill-down search ✓ Human language & text analysis ✓ NoSQL database ✓ Big data analytics tool using Kibana Where does ElasticSearch fit in?
Offloadtheuserprocess
Workerscripts
✓ Uses PHP-CLI ✓ Runs continuously ✓ Process forking ✓ Pthreads ✓ Run worker scripts in parallel ✓ Managed by supervisord Worker scripts
✓ Sync MySQL & Redis/ ElasticSearch ✓ Resize images ✓ Async logging & metrics ✓ Other tasks that shouldn’t block the user process Worker scripts
Messagequeues
✓ Pub/sub ✓ Speaks AMQP protocol ✓ Supported by Pivotal ✓ Channels/Exchanges/ Queues ✓ Built-in clustering ✓ Reliable messaging RabbitMQ
✓ Take load away from user process ✓ Free up resources on frontend servers ✓ Elaborate messaging strategies ✓ Async event-based actions Where do RabbitMQ/workers fit in?
But with most of these tools we still go through the PHP runtime …
SOA Microservices AJAX Websockets
✓ Javascript runtime ✓ Async ✓ Event-driven ✓ Non-blocking I/O ✓ Callbacks ✓ Lightweight ✓ NPM packages ✓ Backend-code in Javascript NodeJS
const http = require('http'); const hostname = '127.0.0.1'; const port = 1337; http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('Hello Worldn'); }).listen(port, hostname, () => { console.log(`Server running at http://${hostname}:${port}/`); });
Gateway to ElasticSearch, RabbitMQ & Redis
✓ Compiled language for the web ✓ Invented by Google ✓ Strictly typed ✓ Feels like your average interpreted language ✓ Async features ✓ Built for systems programming ✓ REALLY fast ✓ Not object oriented Go(lang)
package main import ( "fmt" "net/http" "goji.io" "goji.io/pat" "golang.org/x/net/context" ) func hello(ctx context.Context, w http.ResponseWriter, r *http.Request) { name := pat.Param(ctx, "name") fmt.Fprintf(w, "Hello, %s!", name) } func main() { mux := goji.NewMux() mux.HandleFuncC(pat.Get("/hello/:name"), hello) http.ListenAndServe("localhost:8000", mux) }
Really fast workers Really fast APIs Could replace PHP workers Could replace NodeJS
Theendgame
✓ Cache pages (Varnish) ✓ Assemble content via ESI or AJAX ✓ Static assets on Nginx or CDN ✓ Business logic in lightweight API calls (NodeJS, Go) ✓ Key-value stores for volatile & real-time data in Redis ✓ ElasticSearch as a NoSQL database ✓ RabbitMQ for async communication ✓ Worker processes read from message queue End game
Use the right tool for the job
Use the tools you like
PHP projects beyond the LAMP stack

PHP projects beyond the LAMP stack