Skip to content

Commit

Permalink
Extends documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Denis Krienbühl committed Feb 9, 2015
1 parent 7bb8fb8 commit 93db11b
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 87 deletions.
9 changes: 3 additions & 6 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@ API Documentation
Context / Registry
------------------

.. autoclass:: libres.context.registry.Registry
.. automodule:: libres.context.registry
:members:

.. autoclass:: libres.context.context.Context
.. automodule:: libres.context.context
:members:

.. autoclass:: libres.context.context.ContextServicesMixin
:members:

.. autoclass:: libres.context.context.StoppableService
.. automodule:: libres.context.session
:members:

Database Access
Expand Down
2 changes: 1 addition & 1 deletion docs/concepts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ reservation conflicts with another reservation. They accomplish that by
using the allocation together with the start time of the reservation as a
primary key on the database.

Reserved slots are unique inside an allocation, making sure that when two
Reserved slots are unique inside an allocation, making sure that when two
reservations are made at the same time, only one will succeed.

Reservations
Expand Down
39 changes: 39 additions & 0 deletions docs/faq.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
FAQ
===

Why is *Database X* not an option? / Why does Postgresql < 9.1 not work?
------------------------------------------------------------------------

seantis.reservation relies on a Postgresql feature introduced in 9.1
called "Serialized Transactions". Serialized transactions are
transactions that, run on multiuser systems, are guaranteed to behave
like they are run on a singleuser system.

In other words, serialized transactions make it much easier to ensure
that the data stays sane even when multiple write transactions are run
concurrently.

Other databases, like Oracle, also support this feature and it would be
possible to support those databases as well. Patches welcome.

Note that MySQL has serialized transactions with InnoDB, but the
documentation does not make any clear guarantees and there is a debate
going on:

http://stackoverflow.com/questions/6269471/does-mysql-innodb-implement-true-serializable-isolation

For more information see :ref:`serialized-transactions`.

Why did you choose SQL anyway? Why not *insert your favorite NoSQL DB here*?
----------------------------------------------------------------------------

- If a reservation is granted to you, noone else must get the same
grant. Primary keys and transactions are a natural fit to ensure
that.

- Our data model is heavily structured and needs to be validated
against a schema.

- All clients must have the same data at all time. Not just eventually.

- Complicated queries must be easy to develop as reporting matters.
2 changes: 2 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ Content
:maxdepth: 2

concepts
under_the_hood
api
faq

License
=======
Expand Down
48 changes: 48 additions & 0 deletions docs/under_the_hood.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
Under the Hood
==============

.. _serialized-transactions:

Serialized Transactions
-----------------------

Working with dateranges in Libres often entails making sure that ranges don't
overlap. This often means that multiple records have to be considered before
a daterange can be changed.

One way to do this is to lock the relevant records and tables. Thus stopping
concurrent transactions around the same daterange from leaving the
database in an invalid state.

Libres uses serialized transactions instead, a feature of *proper*
databases like Postgres. Serialized transactions always behave like they were
single user transactions. If two transactions arrive at the very same time,
only one transaction is accepted, while the other is stopped.

`See the nice documentation on the topic in the postgres manual.
<http://www.postgresql.org/docs/current/static/transaction-iso.html>`_.

Since serialized transactions are slower than normal transactions, Libres only
employs them for write operations. Read operations are left in the default
transaction level.

As a user you don't really have to care about this, though you might encounter
one of these errors::

psycopg2.extensions.TransactionRollbackError
libres.modules.errors.DirtyReadOnlySession
libres.modules.errors.ModifiedReadOnlySession

A `TransactionRollbackError` occurs if the transaction you sent was denied
because another serial transaction was let through instead.

A `DirtyReadOnlySession` error occurs if you wrote something to the database
without comitting it.

A `ModifiedReadOnlySession` error occurs if you tried to write something to
the database without using the serial transaction.

See :class:`~libres.context.session.SessionStore`,
:class:`~libres.context.session.SessionProvider`,
:class:`~libres.context.session.Serializable` and
:class:`~libres.context.session.serialized` for implementation details.
81 changes: 1 addition & 80 deletions libres/context/session.py
Original file line number Diff line number Diff line change
@@ -1,83 +1,3 @@
"""
The session module provides the sqlalchemy session for all of
seantis.reservation
Read the following doc for the why and how:
About transactions in seantis.reservation
=========================================
All sqlalchemy transactions are bound to the transactions of Zope. This is
provided by zope.sqlalchemy which ensures that both sql and zope transaction
always go hand in hand. Namely, when the zope transaction is commited, the
sqlalchemy transaction is commited (or rolled back) at the same time.
Transaction isolation
=====================
A feature of postgres (and indeed other databases) is the ability to use
varying degrees of transaction isolation. Simply put, some transactions are
less isolated than others.
The better the isolation, the worse the performance.
See the nice documentation on the topic in the postgres manual:
http://www.postgresql.org/docs/current/static/transaction-iso.html
Required Levels
===============
There are two levels we need for reservations. One is the Read Commited
Isolation Level for fast database reads when browsing the calendar.
The other is the Serializable Isolation Level for database writes. This level
ensures that no two transactions are processed at the same time. Bad news for
concurrency, but very good news for integrity.
Implementation
==============
It would be nice to be able to use fast isolation when running uncritical
codepaths and good isolation during critical paths. Unfortunately sqlalchemy
(psycopg2 to be precise) does not offer any such way.
This is why the session module exists.
What it does is provide a global utility (think singleton). Which stores
a read session and a write session for each thread. The read session uses
read commited mode, the write session serializable mode.
Usage
=====
A function which should, within it's scope, force all database requests to go
through the isolated transaction, can do so by using the @serializable
decorator or the serializable_call function. These two functions will switch
the current thread to the serializable isolation session and back for whatever
they wrap.
Since all functions get their session from the global session utility this
means that the transaction isolation can be globally switched.
It also means that one must be careful when using this feature. Mixing of
these two sessions may lead to one session not seeing what the other did.
As long as the session is not mixed within a single request though, everything
should be fine.
Note that there are hooks in place which enforce correct usage. A read session
cannot be used anymore once a serial session was used to change the database.
Testing
=======
All testing is done in test_session.py using postgres. As only postgres 9.1+
supports true serialized isolation it is a requirement to use this database.
Other databases like Oracle support this as well, but for other databases to be
supported they need to be tested first!
"""
import re
import threading
import functools
Expand All @@ -96,6 +16,7 @@


class SessionStore(object):
""" Holds the read-commited and serializable session. """

def __init__(self, dsn, engine_config, session_config):
self.readonly = self.create_session(
Expand Down

0 comments on commit 93db11b

Please sign in to comment.