Architecture diagram
[aquarium] / doc / arch / arch.tex
1 \paragraph{Architectural decisions} As most in similar systems, Aquarium's architectural design is driven by two 
2 requirements: scaling and fault tolerance. As described in the requirements
3 section above, the concurrency and responsiveness requirements are steep;
4
5 Driven by our former experience with 3 tier systems, we designed the first
6 Aquarium prototype around a traditional relational database backend, with an
7 object relational mapping ({\sc orm}) based middle layer that run the business
8 processes (billing, message processing etc) and a fully-fledged {\sc rest}
9 frontend. While the simplicity of {\sc orm} systems provided a great start,
10 it quickly became obvious that such a design was too rigid for an open 
11 ended system. Complications arised from the fact that it was too difficult
12 to describe versioned tree-based structures, such as the configuration {\sc dsl} 
13 (see Section~\ref{sec:dsl}) and to make sure that resource events were
14 described in an abstract way that would include all future system expansions.
15 Moreover, the single query that places Aquarium in the system's critical 
16 path (number of remaining credits) must be answered within a few (less than
17 10) milliseconds for a large number of concurrent requests, which means that
18 it must be somehow cached and automatically updated when new chargable 
19 resources are invoked by the user.
20
21 For the reasons outlined above, we chose to base Aquarium on event sourcing. 
22 Event sourcing assumes that all changes to application
23 state are stored as a sequence of events, in an immutable log. With such a log
24 at hand, a system can easily rebuild the current state by replaying the events
25 in order. The event sourcing design pattern has some very interesting
26 properties, which made it particularity suitable for basing Aquarium's
27 architecture on it:
28
29 \begin{itemize}
30
31     \item Multiple models can be used in order to process the events, 
32         concurrently. This means that Aquarium can provide a limited
33         data view to its {\sc rest api} and a more detailed one to a
34         helpdesk frontend.
35
36     \item It is possible to perform queries on past system states by stopping
37         the event replay at a certain point of interest. This would prove very
38         possible for a future debugging interface.
39
40     \item In a carefully implemented event sourcing system, application crashes 
41         are not destructive, as long as event replay is fast enough and no
42         state is inserted to the application without being recorded to the event
43         log first.
44
45     \item After event log replay, new events only cause updates in the system's
46         in-memory state, which can be done very fast.
47
48 \end{itemize}
49
50 \begin{figure}
51     \begin{center}
52     \includegraphics[scale=1.5]{arch.pdf}
53     \end{center}
54 \caption{Functional components in Aquarium's architecture} 
55 \label{fig:arch}
56 \end{figure}
57
58 With event sourcing as the basis for Aquarium, we then proceeded to design the
59 system's runtime data model. What we wanted to model was the current state of
60 resource usage for each user, along with the user's wallet. One possibility we
61 wanted to explore on that front was copy on write updates; even for trivial
62 updates, the system would have to copy the affected data graphs to new
63 versions, instead of modifying the system in place. For that, we briefly
64 explored the use of software transactional memory, but found it restrictive for
65 our pursposes. What we chose instead was to contain each user's runtime state
66 in an actor. Using this design, shared state was eliminated; the use of the
67 actor model guarantee, that only one thread can execute within the context of
68 an actor renders the protection (with copy on write or other mechanism) of the
69 actor's state superflous. The actor model also fitted the event sourcing basis
70 very well, since each message in the log could pass through various processing
71 stages and reach the appropriate actor immutably.
72
73 \paragraph{Components} An overview of the Aquarium architecture is presented in
74 Figure~\ref{fig:arch}.  The system is modeled as a collection of logically and
75 functionally isolated components, which communicate by message passing. Withing
76 each component, a number of actors take care of concurrently processing
77 incoming messages through a load balancer component which is the gateway to
78 requests targeted to the component. Each component is also monitored by its own
79 supervisor; should an actor fail, the supervisor will automatically restart it.
80 The architecture allows certain application paths to fail individually while
81 the system is still responsive, while also enabling future distribution of
82 multiple components on clusters of machines.
83
84 The system receives input mainly from two sources: a queue for resource and
85 user events and a {\sc rest api} for billing state queries. The queue component
86 reads messages from a configurable number of queues and persists them in the
87 application's immutable log store. Both input components then forward incoming
88 messages to a network of dispatcher handlers which do not do any processing by
89 themselves, but know where the user actors lay. As described earlier, actual
90 processing of billing events is done within the user actors. Finally, a
91 separate network of actors take care of scheduling periodic tasks, such as
92 refiling of user credits; it does so by issuing events in the appropriate
93 queue.
94
95 \paragraph{Implementation}
96
97 Aquarium is being developed as a standalone service, based on the Akka library
98 for handling actor related functionality. Akka has all basic components for
99 implementing the architecture as described, which allowed us to focus on
100 implementing the business logic, leaving the details of actor registries,
101 dispatchers and message passing to Akka's runtime. Akka also provided
102 actor-based components for communicating with the message queue and, through a
103 third party component (Spray)\footnote{\url{http://github.com/spray}} for
104 handling {\sc rest} requests.  We chose the {\sc amqp} protocol and its
105 Rabbit{\sc mq} implementation for implementing the request queue, specifically
106 because recent versions include support for active/active cluster
107 configurations. The persistence layer is currently implemented by Mongo{\sc
108 db}, for its replication and sharding support.  However, this is not a hard
109 requirement, as Aquarium features an abstraction layer for all database queries
110 (currently 10 methods), which can then be implemented by any persistence
111 system, relational or not.