Skip to content

Commit

Permalink
Edits in pushserver article
Browse files Browse the repository at this point in the history
  • Loading branch information
tarmack committed Feb 22, 2015
1 parent 12de3d3 commit 3031cc7
Showing 1 changed file with 89 additions and 80 deletions.
169 changes: 89 additions & 80 deletions content/articles/pushserver.rst
@@ -1,6 +1,6 @@
:title: pushserver
:date: 2015-02-09 13:01
:summary: Pub/sub server to broadcast real-time updates from the server to the clients
:summary: Pub/sub server to broadcast real-time updates from the server to clients
:category: Open source
:author: Anatoly Bubenkov
:slug: articles/pushserver
Expand All @@ -11,111 +11,120 @@
Introduction
------------

Paylogic constantly works on improving the user experience of our frontoffice. One of the important things when you
buy a ticket, is the availability counter. The availability counter shows the user how many tickets
(for a certain venue) are available for purchase.
Paylogic Frontoffice - is one of our products which is a web application to sell the tickets for the merchant events.
It is simple, powerful and flexible to satisfy our clients needs. Visually it's done as a wizard with dynamic set of
steps: tickets, overview, payment and so on.
At the moment the Paylogic frontoffice does not show ticket availability: if the ticket locking went
fine users are simply forwarded to the next step. If there's not enough tickets available, you're
informed about that. This leads to the need of the constant request-response football between the user and the backend
just to figure out the maximum number of tickets the user can buy.
`pushserver <https://github.com/paylogic/pushserver>`_ which enables backend services to send real-time events
to web clients.
This article describes the motivation, technology in brief and implementation details of the ``pushserver``.
Paylogic is constantly working to improve the user experience of it's ticketing platform. When buying a ticket, one of
the most important things to know is the amount of available tickets. The Paylogic Frontoffice is one of the products
Paylogic uses to allow event organizers to sell tickets for their events. It is a simple yet powerful and flexible web
application. The purchasing process is set up as a wizard with a dynamic set of steps: select tickets, order overview,
payment and so on. The availability counter shows the user how many tickets are still available for a certain event. At
the moment the Paylogic Frontoffice does not have a real-time indication of the ticket availability. The user selects
his tickets and pushes the "next" button. If all goes well the user is simply forwarded to the next step. If, however,
there are not enough tickets available to satisfy the users request, he is send back to the ticket selection step and
informed about this. This leads to a constant request-response football between the user and the backend just to figure
out the maximum number of tickets the user can buy. In comes the `pushserver <https://github.com/paylogic/pushserver>`_
which enables the backend services to send real-time events to the web clients. This article describes the motivation
and gives a brief overview of the technology and implementation details of a ``pushserver``.


User experience
---------------

Paylogic constantly improves the user experience of our products. Frontoffice
(the service which does the actual sale) is the most important among them. Basic flow of the ticket sale
contains such steps (simplified):
Paylogic is constantly improving the user experience of it's' products. The Frontoffice being the most important among
them. The basic flow of the ticket sale can be simplified as the following steps:

* Get the list of tickets (ticket types)
* Select ticket(s) quantities
* Get the list of available ticket types
* Select the requested ticket quantities
* Check out the basket

The second step becomes difficult for the user, if the number of tickets is limited (which is usually the case).
The user, to make a right choice, needs to know how many tickets are available.
But how does he know, and more importantly, when, that availability was changed?
The second step becomes difficult for the user if the number of available tickets is limited. To allow the user to make
the right choice he needs to know how many tickets are still available. The question becomes: how does the user get to
know this, and more importantly, when. The user might be pondering over the available ticket types for a few minutes. At
the same time another user might come in and take some tickets. How does the first user know that the number of
available tickets was changed?

This problem can be solved in several ways:

Update the availability when the choice is made, if there's not enough, there will be an user error with availability counter.
This is probably the simplest option to implement, but from the user perspective most unnatural and unintuitive, as
he doesn't expect there is only 1 ticket left, if the select box with the quantity allows him to select 10.
Update the availability when the choice is made.
If after clicking the next button it turns out there are not enough tickets available, an error will be send
to the user with the updated availability counter. This is probably the simplest option to implement. However, from
the user's perspective this process is unnatural and unintuitive. The user doesn't expect that there is only 1
ticket left if the select box allowed him to select
10.

Update the availability once per certain amount of time, e.g. every second.
This is more user-friendly. But if we'll keep the period very short to emulate real-time updates, then it will be
problematic for the server to handle, as it will look like real DDOS attack from many clients (during the peak
sales we have thousands and thousands of concurrent connections).
Update the availability on an interval, e.g. every 10 seconds.
This is more user-friendly but if we take a very short period to emulate real-time updates, then this can be
problematic for the server to handle. In essence it will look like DDOS attack, as during peak sales we have tens of
thousands of users using the Frontoffice all at once.

Update the availability at the time it changes.
If there's no change, why bother client's browser with the old information?
Also the server is not attacked anymore.
If there's no change, why bother the client's browser with information it already knows? This also allows the
servers to not get overloaded as it is no longer the browser but the pushserver who is deciding on the amount of
traffic.


But how to implement this?
--------------------------

So we need a way to send updates from the server to the client, without the need of explicit request for the new info
from the client side.
There's a nice `answer <http://stackoverflow.com/questions/11077857/what-are-long-polling-websockets-server-sent-events-sse-and-comet/12855533#12855533>`_
which describes the basics.
So we need a way to send updates from the server to the client without the need for the browser to explicitly request
the new information. There's a nice `answer
<http://stackoverflow.com/questions/11077857/what-are-long-polling-websockets-server-sent-events-sse-and-comet/12855533#12855533>`_
at Stack Overflow which describes the basic options for such a scheme.

So we have options:
There are a few options available:

`Long polling <http://en.wikipedia.org/wiki/Push_technology#Long_polling>`_
Browser support is 100%. That's probably the strongest side of this technique. But reconnection and state restore is
completely the problem of the implementation, there's no helpers provided by the standard as there's no such:
it's just normal HTTP, which ends in some long period, and for the browser it's like one big document.
Browser support is 100%. That's probably the only strong point of this technique. The problem of constantly
reconnecting and state recovery is completely in the hands of the implementation. There are no helpers provided by
any standard as there's no such thing. It really is just a normal HTTP request which takes a long time and to the
browser it's just one big document.

`Websockets <http://en.wikipedia.org/wiki/WebSocket>`_
`Browser support <http://caniuse.com/websockets>`_ is not 100% but is very good, but is it what's needed? It is, but
it gives us much more that enough: it's two-way communication channel. But we don't really need it for our problem.
Most if not all updates are coming ``from`` the server. Updates from the client are normal requests (XMLHttpRequest)
if there is a need. Also websockets are not HTTP but TCP (HTTP is used only during the handshake)
so it requires much more hassle from the OPIT side, as they will need to set up the balancing on the TCP level.
`Browser support <http://caniuse.com/websockets>`_ is not 100% but is very good. Is it what's needed for our
problem? It could fit very well but it gives us much more than we need. Websockets provide a two-way communication
channel but we don't really need that for our problem. All updates are coming ``from`` the server. Updates from the
client are done using normal requests (XMLHttpRequest or regular POST). Additionally websockets are not HTTP but raw
TCP, HTTP is only used for the handshake. This means it requires much more hassle from the OPIT side as they will
need to set up a raw TCP service next to the current web service.

`Server sent events (SSE) <http://en.wikipedia.org/wiki/Server-sent_events>`_
Browser support is `not that bad <http://caniuse.com/eventsource>`_, and increasing constantly. It's one-way
communication channel, but with very useful features provided by the browser: state restore on connection failure (
browser sends the last event ID it received since disconnection), automatic reconnection and a very easy javascript
interface.
Browser support is `not that bad <http://caniuse.com/eventsource>`_, and increasing constantly. Server sent events
provide a one-way communication channel. However, this channel has some very useful features provided by the
browser:
* Automatic reconnect in case of a connection problem
* State restoration on reconnect (the browser sends the last event ID it received before the disconnect)
* A very easy javascript interface

Those options provide possibilities we need but they are not equal, so SSE seems to be the most optimal.
As we use Python a lot, we've looked around for some existing solutions which implement SSE server-side, we've found
`flask sse <https://github.com/DazWorrall/flask-sse>`_, which seemed what we need.
Those options all provide the possibilities we need but they are not equal. SSE seems to be the most optimal for our use
case. As we use Python a lot, we've looked around for some existing solutions which implement SSE server-side. We've
found `flask sse <https://github.com/DazWorrall/flask-sse>`_, which seems to be exactly what we need.

Flask-sse uses redis `pubsub <http://redis.io/topics/pubsub>`_ as a backend for broadcasting the events from the server.
Using `redis-cluster <http://redis.io/topics/cluster-spec>`_ which is currently in beta, but
`will be released soon <https://twitter.com/antirez/status/478425814040854528>`_ (and actually already used in
production as reported by Salvatore Sanfilippo) allows us to have high available, scalable web service.
Flask-sse uses Redis `pubsub <http://redis.io/topics/pubsub>`_ as a backend for broadcasting the events from the server.
It uses `redis-cluster <http://redis.io/topics/cluster-spec>`_ which is currently in beta, but `will be released soon
<http://antirez.com/news/79>`_ (and is actually already used in production as reported by Salvatore Sanfilippo).
Flask-sse on top of redis-cluster allows us to have a high available, scalable web service.


Use-case
--------

Possible use-case can be drawn as following:
A possible use-case can be drawn as follows:


.. image:: |filename|/images/pushserver/diagram-user-perspective.png
:align: center
:alt: pushserver use-case from the user perspective

Actor changes some data on the backend via normal request to the backend. All users receive the broadcasted message
from the backend via long-lasting channel for server-sent events.
The actor changes some data on the backend via a normal request to the backend. All users receive the broadcasted
message from the backend via long-lasting channel for server-sent events.

And the deployment can look like:
The deployment may look like this:

.. image:: |filename|/images/pushserver/diagram-deployment.png
:align: center
:alt: pushserver use-case possible deployment schema


Sending events to a push server from your app
---------------------------------------------
Sending events to a push server
-------------------------------

::

Expand All @@ -124,11 +133,11 @@ Sending events to a push server from your app
send_event('myevent', {"message": "Hello!"}, channel='mychannel')


Client side
-----------
Receiving events in the browser
-------------------------------

On the client side you just need a javascipt handler function which will be called when a new message is pushed
from the server.
On the client side you just need a javascipt handler function which will be called when a new message is pushed from the
server.

::

Expand All @@ -137,26 +146,25 @@ from the server.
alert(event.data);
};

Server-Sent Events are `supported <http://caniuse.com/#feat=eventsource>`_ by recent Firefox,
Chrome and Safari browsers.
Internet Explorer does not yet support Server-Sent Events, so there are two recommended Polyfills
to support older browsers:
Server-Sent Events are `supported <http://caniuse.com/#feat=eventsource>`_ by recent Firefox, Chrome and Safari
browsers. Internet Explorer does not yet support Server-Sent Events but there are two recommended Polyfills to support
IE and older browsers:

* `EventSource.js <https://github.com/remy/polyfills/blob/master/EventSource.js>`_
* `jquery.eventsource <https://github.com/rwldrn/jquery.eventsource>`_

Mobile browsers have limited support, so test carefully if it works for your target set of browsers.
Mobile browsers have limited support, so test carefully whether it works for your target set of browsers.


Live demo
---------

Here is a small demo video of the potential of this technique: we use pushserver to update the ticket availability in
our frontoffice application, which is where users can buy the tickets. The left and right windows are operated by
different users and are completely independent. When ticket availability is changed by the action from the left
window's operator, the right window changes instantly, without the polling involved (you can see the network bar).
our Frontoffice application. The left and right windows are operated by different users and are completely independent.
When the ticket availability is changed by an action from the left window's operator, the right window changes instantly
without any polling involved (you can see the network bar).

Simple frontoffice where you can only select the quantity of the ticket(s):
A simple Frontoffice where you can only select the quantity of tickets:

.. html::

Expand All @@ -165,7 +173,7 @@ Simple frontoffice where you can only select the quantity of the ticket(s):
controls src='/videos/pushserver-in-action.mov'
poster='/images/pushserver/pushserver-in-action.png' />

More advanced example, where you can pick a seat:
A More advanced example where you can pick a seat:

.. html::

Expand All @@ -177,8 +185,9 @@ More advanced example, where you can pick a seat:
Future considerations for Paylogic
----------------------------------

We are considering to create a special stream API, where API users can listen to the events to get them instantly
instead of polling the state from time to time. This is especially important for the cases like result of the
payment processing, availability change, etc.
For now, it's more like an experiment for us, but it's a promising technology and we're eager to hear some feedback from
the developers who use our API if it will be useful for them to have the stream API.
We are considering creating a special stream API where API users can subscribe to events and get them instantly instead
of needing to poll the state from time to time. This is especially important for things like collecting the result of
payment processing, ticket availability changes, etc.

For now, Server sent events are more like an experiment for us, but it's a promising technology. We're eager to hear
some feedback from developers who use our API on whether it will be useful for them to have a stream API.

0 comments on commit 3031cc7

Please sign in to comment.