Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Z
ZODB
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
nexedi
ZODB
Commits
b180e3e3
Commit
b180e3e3
authored
Aug 26, 2016
by
Jim Fulton
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
added schema migration and made various fixes
parent
e6ed5830
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
92 additions
and
45 deletions
+92
-45
documentation/guide/writing-persistent-objects.rst
documentation/guide/writing-persistent-objects.rst
+92
-45
No files found.
documentation/guide/writing-persistent-objects.rst
View file @
b180e3e3
...
@@ -3,7 +3,7 @@ Writing persistent objects
...
@@ -3,7 +3,7 @@ Writing persistent objects
==========================
==========================
In the :ref:`Tutorial <tutorial-label>`, we discussed the basics of
In the :ref:`Tutorial <tutorial-label>`, we discussed the basics of
implementing persis
et
nt objects by subclassing
implementing persis
te
nt objects by subclassing
``persistent.Persistent``. This is probably enough for 80% of
``persistent.Persistent``. This is probably enough for 80% of
persistent-object classes you write, but there are some other aspects
persistent-object classes you write, but there are some other aspects
of writing persistent classes you should be aware of.
of writing persistent classes you should be aware of.
...
@@ -14,7 +14,7 @@ Access and modification
...
@@ -14,7 +14,7 @@ Access and modification
Two of the main jobs of the ``Persistent`` base class is to detect
Two of the main jobs of the ``Persistent`` base class is to detect
when an object has been accessed and when it has been modified. When
when an object has been accessed and when it has been modified. When
an object is accessed, it's state may need to be loaded from the
an object is accessed, it's state may need to be loaded from the
database. When an object
u
s modified, the modification needs to be
database. When an object
i
s modified, the modification needs to be
saved if a transaction is committed.
saved if a transaction is committed.
``Persistent`` detects object accesses by hooking into object
``Persistent`` detects object accesses by hooking into object
...
@@ -24,7 +24,7 @@ maybe other ways of modifying state that we need to make provision for.
...
@@ -24,7 +24,7 @@ maybe other ways of modifying state that we need to make provision for.
Rules of persistence
Rules of persistence
====================
====================
When implementing persistet objects, be aware that an object's
When implementing persiste
n
t objects, be aware that an object's
attributes should be :
attributes should be :
- immutable (such as strings or integers),
- immutable (such as strings or integers),
...
@@ -34,7 +34,8 @@ attributes should be :
...
@@ -34,7 +34,8 @@ attributes should be :
- You need to take special precautions.
- You need to take special precautions.
If you modify a non-persistent mutable value of a persistent-object
If you modify a non-persistent mutable value of a persistent-object
attribute, you need to mark the persistent object as changed yourself::
attribute, you need to mark the persistent object as changed yourself
by setting ``_p_changed`` to True::
import persistent
import persistent
...
@@ -73,14 +74,14 @@ we didn't set an attribute on the book, it's not marked as changed, so
...
@@ -73,14 +74,14 @@ we didn't set an attribute on the book, it's not marked as changed, so
we set ``_p_changed`` ourselves.
we set ``_p_changed`` ourselves.
Using standard Python lists, dicts, or sets is a common thing to do,
Using standard Python lists, dicts, or sets is a common thing to do,
so this pattern of
call
ing ``_p_changed`` is common.
so this pattern of
sett
ing ``_p_changed`` is common.
Let's look at some alternatives.
Let's look at some alternatives.
Using tuples for small
collection
s instead of lists
Using tuples for small
sequence
s instead of lists
------------------------------------------------
---
------------------------------------------------
If objects contain
collection
s that are small or that don't change
If objects contain
sequence
s that are small or that don't change
often, you can use tuples instead of lists::
often, you can use tuples instead of lists::
import persistent
import persistent
...
@@ -153,7 +154,7 @@ Note that in this example, when we added an author, the book itself
...
@@ -153,7 +154,7 @@ Note that in this example, when we added an author, the book itself
didn't change, but the ``authors`` attribute value did. Because
didn't change, but the ``authors`` attribute value did. Because
``authors`` is a persistent object, it's stored in a separate database
``authors`` is a persistent object, it's stored in a separate database
record from the book record and is managed by ZODB independent of the
record from the book record and is managed by ZODB independent of the
manage
em
nt of the book.
manage
me
nt of the book.
In addition to ``PersistentList`` and ``PersistentMapping``, general
In addition to ``PersistentList`` and ``PersistentMapping``, general
persistent data structures are provided by the ``BTrees`` package,
persistent data structures are provided by the ``BTrees`` package,
...
@@ -164,7 +165,7 @@ objects, because their data are spread over many subobjects.
...
@@ -164,7 +165,7 @@ objects, because their data are spread over many subobjects.
It's generally better to use ``BTree`` objects than
It's generally better to use ``BTree`` objects than
``PersistentMapping`` objects, because they're scalable and because
``PersistentMapping`` objects, because they're scalable and because
the handle :ref:`conflicts <conflicts-label>` better. ``TreeSet``
the
y
handle :ref:`conflicts <conflicts-label>` better. ``TreeSet``
objects are the only ZODB-provided persistent set implementation.
objects are the only ZODB-provided persistent set implementation.
``BTree`` and ``TreeSets`` come in a number of families provided via
``BTree`` and ``TreeSets`` come in a number of families provided via
different modules and differ in their internal implementations:
different modules and differ in their internal implementations:
...
@@ -229,9 +230,11 @@ Special attributes
...
@@ -229,9 +230,11 @@ Special attributes
There are some attributes that are treated specially.
There are some attributes that are treated specially.
Attributes with names starting with ``_p_`` are reserved for use by
Attributes with names starting with ``_p_`` are reserved for use by
the persistence machiner and by ZODB. These include:
the persistence machinery and by ZODB. These include (but aren't
limited to):
_p_changed The ``_p_changed`` attribute has the value ``None`` if the
_p_changed
The ``_p_changed`` attribute has the value ``None`` if the
object is a :ref:`ghost <ghost-label>`, True if it's changed, an
object is a :ref:`ghost <ghost-label>`, True if it's changed, an
False if it's not a ghost and not changed.
False if it's not a ghost and not changed.
...
@@ -241,7 +244,7 @@ _p_oid
...
@@ -241,7 +244,7 @@ _p_oid
_p_serial
_p_serial
The object's revision identifier also know as the object serial
The object's revision identifier also know as the object serial
number, also known as the object transaction id. It's a timestamp
number, also known as the object transaction id. It's a timestamp
and if not set as the value 0 encoded as string of 8 zero bytes.
and if not set
h
as the value 0 encoded as string of 8 zero bytes.
_p_jar
_p_jar
The database connection the object was accessed through. This is
The database connection the object was accessed through. This is
...
@@ -249,7 +252,7 @@ _p_jar
...
@@ -249,7 +252,7 @@ _p_jar
object's database connection.
object's database connection.
Attributes with names starting with ``_v_`` are treated as volatile.
Attributes with names starting with ``_v_`` are treated as volatile.
They aren't saved to the database. They
a
re useful for caching data
They aren't saved to the database. They
'
re useful for caching data
that can be computed from saved data and shouldn't be saved. They
that can be computed from saved data and shouldn't be saved. They
should be treated as though they can disappear between transactions.
should be treated as though they can disappear between transactions.
Setting a volatile attribute doesn't cause an object to be considered
Setting a volatile attribute doesn't cause an object to be considered
...
@@ -257,7 +260,7 @@ to be modified.
...
@@ -257,7 +260,7 @@ to be modified.
An object's ``__dict__`` attribute is treated specially in that
An object's ``__dict__`` attribute is treated specially in that
getting it doesn't cause an object's state to be loaded. It may have
getting it doesn't cause an object's state to be loaded. It may have
the value ``None`` rather than a di
s
ctionary for :ref:`ghosts
the value ``None`` rather than a dictionary for :ref:`ghosts
<ghost-label>`.
<ghost-label>`.
...
@@ -265,20 +268,20 @@ Object storage and management
...
@@ -265,20 +268,20 @@ Object storage and management
=============================
=============================
Every persistent object is stored in its own database record. Some
Every persistent object is stored in its own database record. Some
storages maintain multile object revisions, in which case each
storages maintain multi
p
le object revisions, in which case each
persistent object is stored in its own set of records. Data for
persistent object is stored in its own set of records. Data for
different persistent objects are stored separately.
different persistent objects are stored separately.
The database manages each object separately, according to a
lifecycl
e
The database manages each object separately, according to a
:ref:`lif
e
described in the next section
.
cycle <object-life-cycle-label>`
.
This is important when considering how to distribute data ac
c
ross your
This is important when considering how to distribute data across your
objects. If you use lots of
little
persistent objects, then more
objects. If you use lots of
small
persistent objects, then more
objects may need to be loaded or saved and you may incur more memory
objects may need to be loaded or saved and you may incur more memory
overhead. O
TOH, of objects are too big, you may load or save more data
overhead. O
n the other hand, if objects are too big, you may load or
than would otherwise be needed.
save more data
than would otherwise be needed.
.. _schema-migration-label
.. _schema-migration-label
:
Schema migration
Schema migration
================
================
...
@@ -292,8 +295,8 @@ still be loaded into an object with the new schema.
...
@@ -292,8 +295,8 @@ still be loaded into an object with the new schema.
Adding attributes
Adding attributes
-----------------
-----------------
Perhaps the commonest schema change is to add
information. This can
Perhaps the commonest schema change is to add
attributes. This is
often be accomplished
by adding a default value in a class
usually accomplished easily
by adding a default value in a class
definition::
definition::
class Book(persistent.Persistent):
class Book(persistent.Persistent):
...
@@ -325,13 +328,51 @@ can keep a ``library`` module that contains::
...
@@ -325,13 +328,51 @@ can keep a ``library`` module that contains::
from catalog import Publication as Book # XXX deprecated name
from catalog import Publication as Book # XXX deprecated name
A downside of this approach is that it clutters code and may even
A downside of this approach is that it clutters code and may even
cause
i
s to keep modules solely to hold aliases. (`zope.deferredimport
cause
u
s to keep modules solely to hold aliases. (`zope.deferredimport
<http://zopedeferredimport.readthedocs.io/en/latest/narrative.html>`_
<http://zopedeferredimport.readthedocs.io/en/latest/narrative.html>`_
can help with this by making these aliases a little more eff
e
cient and
can help with this by making these aliases a little more eff
i
cient and
by generating deprecation warnings.)
by generating deprecation warnings.)
Object lifecycle states and special attributes (advanced)
Migration scripts
=========================================================
-----------------
If the simple approaches above aren't enough, then migration scripts
can be used. How these scripts are written is usually application
dependent, as the application usually determines where objects of a
given type reside in the database. (There are also some low-level
interfaces for iterating over all of the objects of a database, but
these are usually impractical for large databases.)
An improvement to running migration scripts manually is to use a
generational framework like `zope.generations
<https://pypi.python.org/pypi/zope.generations>`_. With a generational
framework, each migration is assigned a migration number and the
number is recorded in the database as each migration is run. This is
useful because remembering what migrations are needed is automated.
Upgrading multiple clients without down time
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Production applications typically have multiple clients for
availability and load balancing. This means an active application may
be committing transactions using multiple software and schema
versions. In this situation, you may need to plan schema migrations
in multiple steps:
#. Upgrade software on all clients to a version that works with the old and new
version of the schema and that writes data using the old schema.
#. Upgrade software on all clients to a version that works with the old and new
version of the schema and that writes data using the new schema.
#. Migrate objects written with the old schema to the new schema.
#. Remove support for the old schema from the software.
.. _object-life-cycle-label:
Object life cycle states and special attributes (advanced)
==========================================================
Persistent objects typically transition through a collection of
Persistent objects typically transition through a collection of
states. Most of the time, you don't need to think too much about this.
states. Most of the time, you don't need to think too much about this.
...
@@ -346,7 +387,7 @@ Added
...
@@ -346,7 +387,7 @@ Added
Note that most objects are added implicitly by being set as
Note that most objects are added implicitly by being set as
subobjects (attribute values or items) of objects already in the
subobjects (attribute values or items) of objects already in the
database
) of objects already in the database
.
database.
Saved
Saved
When an object is added and saved through a transaction commit, the
When an object is added and saved through a transaction commit, the
...
@@ -354,7 +395,14 @@ Saved
...
@@ -354,7 +395,14 @@ Saved
Changed
Changed
When a saved object is updated, it enters the *changed* state to
When a saved object is updated, it enters the *changed* state to
indicate that there are changes that need to be committed.
indicate that there are changes that need to be committed. It
remains in this state until either:
- The current transaction committed, and the object transitions to
the saved state, or
- The current transitions is aborted, and the object transitions to
the ghost state.
.. _ghost-label:
.. _ghost-label:
...
@@ -364,12 +412,12 @@ Ghost
...
@@ -364,12 +412,12 @@ Ghost
and it will enter the saved state. A saved object can become a
and it will enter the saved state. A saved object can become a
ghost if it hasn't been accessed in a while and the database
ghost if it hasn't been accessed in a while and the database
releases its state to make room for other objects. A changed
releases its state to make room for other objects. A changed
object can become a ghost if the transaction it's modified in is
object can
also
become a ghost if the transaction it's modified in is
aborted.
aborted.
An object that's loaded from the database is loaded as a
An object that's loaded from the database is loaded as a
ghost. This typically happens when the object is a subobjet of
ghost. This typically happens when the object is a subobje
c
t of
another object whos state is loaded.
another object who
'
s state is loaded.
We can interrogate and control an object's state, although somewhat
We can interrogate and control an object's state, although somewhat
indirectly. To do this, we'll look at some special persistent-object
indirectly. To do this, we'll look at some special persistent-object
...
@@ -394,14 +442,13 @@ If we add it to a database::
...
@@ -394,14 +442,13 @@ If we add it to a database::
(False, True, True)
(False, True, True)
We know it's added because it has an oid, but its serial (object
We know it's added because it has an oid, but its serial (object
revision timestamp), ``_p_serial``, is the special zero value it's
revision timestamp), ``_p_serial``, is the special zero value
, and
it's
value for ``_p_changed`` is False.
value for ``_p_changed`` is False.
If we commit the transaction that added it::
If we commit the transaction that added it::
>>> import transaction
>>> import transaction
>>> transaction.commit()
>>> transaction.commit()
>>> from ZODB.utils import z64
>>> book._p_changed, bool(book._p_oid), book._p_serial == z64
>>> book._p_changed, bool(book._p_oid), book._p_serial == z64
(False, True, False)
(False, True, False)
...
@@ -435,30 +482,30 @@ Note that accessing ``_p_`` attributes didn't cause the object's state
...
@@ -435,30 +482,30 @@ Note that accessing ``_p_`` attributes didn't cause the object's state
to be loaded.
to be loaded.
We've already seen how modifying ``_p_changed`` can cause an object to
We've already seen how modifying ``_p_changed`` can cause an object to
be maked as modified. We can also use it to make an object into a
be ma
r
ked as modified. We can also use it to make an object into a
ghost:
ghost:
>>> book._p_changed = None
>>> book._p_changed = None
>>> book._p_changed, bool(book._p_oid)
>>> book._p_changed, bool(book._p_oid)
(None, True)
(None, True)
Other things you can do, but shouldn't
Other things you can do, but shouldn't
(advanced)
======================================
======================================
===========
The first rule here is don't be clever!!! It's
soooo
tempting to be
The first rule here is don't be clever!!! It's
very
tempting to be
clever, but it's almost never worth it.
clever, but it's almost never worth it.
Overriding ``__getstate__`` and ``__setstate__``
Overriding ``__getstate__`` and ``__setstate__``
------------------------------------------------
------------------------------------------------
When an object is saved in a database, it's ``__getstate__`` method is
When an object is saved in a database, it's ``__getstate__`` method is
called without arguments
. The default implementation simply returns a
called without arguments
to get the object's state. The default
copy of an object's instance dictionary. (It's a little mor
e
implementation simply returns a copy of an object's instanc
e
complicated for objects with slots.)
dictionary. (It's a little more
complicated for objects with slots.)
An object's state is loaded by loading the state from the database and
An object's state is loaded by loading the state from the database and
passing it to the object's ``__setstate__`` method. The default
passing it to the object's ``__setstate__`` method. The default
implementation expects a di
sc
ionary, which it used to populate the
implementation expects a di
ct
ionary, which it used to populate the
object's instance dictionary.
object's instance dictionary.
Early on, we thought that overriding these methods would be useful for
Early on, we thought that overriding these methods would be useful for
...
@@ -468,7 +515,7 @@ the result was to make object implementations brittle and/or complex
...
@@ -468,7 +515,7 @@ the result was to make object implementations brittle and/or complex
and the benefit usually wasn't worth it.
and the benefit usually wasn't worth it.
Overriding ``__getattr__``, ``__getattribute__``, or ``__setattribute__``
Overriding ``__getattr__``, ``__getattribute__``, or ``__setattribute__``
=========================================================================
-------------------------------------------------------------------------
This is something extremely clever people might attempt, but it's
This is something extremely clever people might attempt, but it's
probably never worth the bother. It's possible, but it requires such
probably never worth the bother. It's possible, but it requires such
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment