1 of 79

Content personalization

and paywalls

with

WordPress and Varnish

Stanislav Khromov�WordCamp Norrköping 2015

2 of 79

About me

  • WordPress developer at
  • Worked at publishing�houses for the past 5 years
  • @khromov

(part of Schibsted Media Group)

3 of 79

Let’s talk about WordPress

4 of 79

What makes WordPress good?

  • Excellent community
  • Thriving ecosystem
  • Intuitive admin backend
  • Huge market share

5 of 79

...and what doesn’t?

  • Difficult to scale right
  • It’s slow

6 of 79

...and what doesn’t?

  • Difficult to scale right
  • It’s slow

7 of 79

8 of 79

What makes WordPress slow?

  • 70-98% spent in PHP depending on setup
  • CPU = bottleneck

9 of 79

The problem

Scaling at high traffic levels

(>1 million daily pageviews)

10 of 79

The truth about traffic

Daily traffic�1 000 000 = 11.6 requests/s�5 000 000 = 57.9 requests/s�10 000 000 = 116 requests/s�

But mean traffic values aren’t completely accurate.

We have to be prepared for traffic spikes.

11 of 79

A 4-day example

12 of 79

Traffic patterns

13 of 79

Traffic patterns

14 of 79

Traffic patterns

Sleep!

At work!

Going home...

The “After-lunch slump”

Late-night browsing

15 of 79

The truth about traffic

  • Mean traffic values aren’t accurate
  • We have to be prepared for traffic spikes!

16 of 79

A more realistic example

“Expected” peaks can easily be 2x of mean traffic�1 000 000 = 23.2 requests/s�5 000 000 = 115.8 requests/s�10 000 000 = 232 requests/s

17 of 79

Requests to WordPress vs assets

  • Not all requests go to WordPress
  • Have 10 CSS and 10 JS files on your page? That’s +20 requests / page load
  • Static file resources are not as heavy to serve as WP, but still use up valuable worker threads on the backend. (Can be helped by CDN.)

18 of 79

Full-page caching

  • Full-page caching is the most common way to improve performance
  • Huge performance increases. Instead of going to WordPress every time we can save the HTML output.
  • An abundance of solutions available

19 of 79

On the web server...

20 of 79

On the web server...

Regular user

21 of 79

On the web server...

Cache plugin

22 of 79

Anonymous visitor

Anonymous visitor

Cache plugin

23 of 79

Logged in visitor

Logged in

visitor

Cache plugin

(bypassed)

24 of 79

As a separate process:

25 of 79

As a separate process:

26 of 79

As a separate process:

Varnish Cache

rules

27 of 79

As a separate process:

Varnish Cache

rules

Anonymous visitor

28 of 79

As a separate process:

Logged in

visitor

Varnish Cache

rules bypassed due to cookies

29 of 79

Varnish basics

  • Varnish Query Language
  • vcl_recv handles initial request
    • if it should go to backend, a “pass” is issued
  • vcl_fetch handles the response from the WordPress backend

30 of 79

Understanding the Varnish Request / Response cycle

31 of 79

Understanding the Varnish Request / Response cycle

32 of 79

Varnish <3 WordPress

33 of 79

Problems with cache

  • Page cache is simple, and efficient. (95% of requests can be cached)
  • But it’s “dumb”
  • Not possible to vary dynamically by user without going to backend for each user
  • How do we solve this?

34 of 79

Edge Side Includes

to the rescue!

35 of 79

What are Edge Side Includes?

  • W3C Specification since 2001
  • Implemented in various
  • Let’s you load a second URL as part of the current page load.
  • Similar to a PHP require() that also works with remote requests

36 of 79

What are Edge Side Includes?

37 of 79

What are Edge Side Includes?

38 of 79

A simple example

  • Let’s show visitors a lucky number on each page load:

39 of 79

Breaking it down

40 of 79

What we need

  • Enable ESI support in Varnish
  • Whitelist our ESI URL from caching
  • Create a WordPress Widget
  • Create a simple ESI endpoint that shows a random “lucky” number

41 of 79

Enable ESI support in Varnish

  • Add a line to the Varnish configuration file and restart it.

42 of 79

Whitelist our ESI from caching

  • Inside vcl_recv, we want to pass a specific URL pattern:

43 of 79

Create a WordPress Widget

  • Let’s make a simple widget to display our ESI.

44 of 79

Create a WordPress Widget

The interesting part - outputting the widget:

45 of 79

Create a simple ESI

A simple PHP file: /esi/lucky-number.php

46 of 79

47 of 79

Edge Side Includes are handled in vcl_fetch by parsing the returned HTML from the backend

48 of 79

Edge Site Includes pass the cache

Varnish Cache

rules

Anonymous visitor

Edge Side Include

49 of 79

Independent caching policies for ESI and backend request

WordPress

backend

ESI call

Always pass

Cache

50 of 79

51 of 79

Do you wanna build paywall?

52 of 79

What do we want?

  • Allow users (subscribers) to log in
  • Allow users to read articles only if they are logged in
  • Use existing WordPress login system
  • Problem: How do you use WordPress login system without actually booting WordPress?

53 of 79

Breaking it down

54 of 79

Breaking it down

55 of 79

What we need

  • Set is_subscriber cookie for subscribers after they log in.
  • Throw away cookies on frontend for subscribers so it can be cached while they are “signed in”.
  • Send user cookie to ESI
  • Create ESI to show login / logout box
  • Create WordPress endpoint that our ESI can fetch the login / logout box from

56 of 79

Independent caching policies for ESI and backend request

WordPress

backend

ESI call

Always pass

Cache

57 of 79

Varnish Cache

rules

Logged in

Subscriber

Edge Side Include: Login form

Cache

inside ESI

ESI Authenticates user based on is_subscriber cookie

is_subscriber

Cookie

is_subscriber

(via Header)

Non-dynamic part

gets cached here

Get login or

logout form from backend

58 of 79

Throw away cookies

Problem: All cookies are gone, how are we going to pass them to the ESI?

Solution: Rewrite cookie to HTTP header

59 of 79

Send user cookie to ESI

Solution: Rewrite cookie to HTTP header

60 of 79

Create ESI to show login / logout box

Would be nice if WordPress could generate that for use while still using an ESI...

61 of 79

Authenticating the ESI

We can authenticate the request by looking at the headers

62 of 79

Contacting the backend

We can grab the login form from the backend URL

63 of 79

Backend response

  • Make WordPress trigger a JSON response on ?wordcampers_internal_api=1 and return the login / logout form
  • Make Varnish pass this URL

64 of 79

65 of 79

Grabbing the post content

  • Same procedure
  • Create ESI for fetching content from backend
  • Create backend API for serving content if user is authorized
  • Code examples provided!

66 of 79

Varnish Cache

rules

Subscriber

Edge Side Include: Content

Edge Side Include: Login form

67 of 79

Case Study - Aftonbladet Plus locking

  • Third party signon system handles login
  • ESI serves up content

68 of 79

What we’ve learned

  • Scaling for high traffic is a challenge
  • Caching is a viable alternative to scale WordPress
  • Caching doesn’t have to be static
  • Varnish configuration and Edge Side Includes can be very powerful and valuable

69 of 79

Sources & credits

70 of 79

May the traffic be with you!

71 of 79

72 of 79

73 of 79

74 of 79

ESI vs AJAX

  • Similar creation
  • ESI cached on backend, AJAX cached at the whim of the browser
  • Communication with ESI can be via request headers, cookies or query string.
  • Communication with AJAX is same, but can also perform logic based on browser data (localstorage, events) after it has loaded.

75 of 79

The problem:

Scaling at high traffic levels

76 of 79

What do we want?

  • Allow users to sign in while still not loading WordPress
  • Allow users to be authenticated outside of wordpress (possible with 3:rd party as well)

77 of 79

A typical high-traffic setup: Hardware

Database cluster

Load-balancing & caching proxy

Web servers

NoSQL storage

HTTP

MySQL

Other

78 of 79

A typical high-traffic setup: Software

MySQL

Varnish

nginx /

php-fpm

Memcached

HTTP

MySQL

Other

79 of 79

WordPress generation time

  • Generation time can vary from 200 - 3000 ms depending on application.
  • What is the bottleneck?