The text below is a cross-post to this list from the a recent
chandlerdb blog post
http://blogs.osafoundation.org/chandlerdb/2006/01/using_collectio.html
as per Ted's request.
Andi..
---------------------------------------------------------------------------
January 24, 2006
Using collection indexes to find items
During last week's sprints I was asked to see how easy it was to
import mail
into Chandler from a local mailbox. Thanks to Python's mailbox and email
packages, the mailbox parsing was trivial. Similarly, Chandler's domain
model can represent email items and has a number of APIs that make
creating
such items from a raw email string very easy.
Unexpectedly, importing mail messages was very slow however, and was
getting
worse as more messages were added to the repository. The first cause of
slowness was easily resolved by turning off 'bz2' compression of all the
text LOBs being created.
The second cause of slowness was more surprising though. It had to do
with
looking up existing email address items from the email addresses
occurring
in the headers of the messages being imported. The EmailAddress item
class
defined here has a class method called getEmailAddress() that will
linearly
iterate all EmailAddresses instances every time such an item is
sought. The
mailbox I was trying to import contains about 6,500 mails with 800 unique
email addresses. Linearly searching them won't scale.
In the early days of the repository we had planned on having a query
system
and Ted even implemented a query language not unlike one you'd
encounter in
an SQL database. Last year, while working on Chandler 0.6, it occurred
to us
that a formal query language in a Python-based system was somewhat
redundant
and that we should be using computed collections instead of queries
altogether to accomplish the same goals . Hence abstract sets and
their item
wrappers were implemented.
By combining abstract sets, collections and indexes - something I had
added
to bi-directional reference collections during the Chandler 0.5
release - we
realized we could get very decent item look-up performance as well as
cache
the computed aspect of abstract sets making membership tests and
iteration
also considerably faster.
This technique can be illustrated with the example below taken from the
EmailAddress index I added during last week's sprints.
* First, a collection needs to be setup. This can be as simple as a
bi-directional reference collection, an abstract collection of all
items of a given kind or a more complicated computed collection
combining or filtering other collections. In the EmailAddress
example,
I just created a KindCollection instance based on the EmailAddress
kind. In the app parcel I added yet another collection called
emailAddressCollection.
emailAddressCollection = \
KindCollection.update(parcel, 'emailAddressCollection',
kind=pim.mail.EmailAddress.getKind(view),
recursive=True)
* Then, a sorted index needs be added to the collection. I wanted
to be
able to find existing email addresses such that no two email
address
items in the collection have the same lowercase internet email
address
string. For this purpose, I added the _compareAddr() method to the
EmailAddress class in the mail parcel:
def _compareAddr(self, other):
return cmp(self.emailAddress.lower(),
other.emailAddress.lower())
and the index creation call after the code creating the collection:
emailAddressCollection.rep.addIndex('emailAddress', 'compare',
compare='_compareAddr')
which creates a 'compare' index called 'emailAddress', an index
calling a method called '_compareAddr' on the items it is
comparing.
* And finally to use this collection and the index to find items I
added
a findEmailAddress() class method to the EmailAddress class:
@classmethod
def findEmailAddress(cls, view, emailAddress):
collection = schema.ns("osaf.app",
view).emailAddressCollection.rep
emailAddress = emailAddress.lower()
def compareAddr(key):
return cmp(emailAddress,
view[key].emailAddress.lower())
uuid = collection.findInIndex('emailAddress', 'exact',
compareAddr)
if uuid is None:
return None
return view[uuid]
The findInIndex() call looks for an exact match as per the
compareAddr
local function which compares lowercase versions of internet
address
strings.
The technique above replaces the linear search with a binary search
yielding
a very noticeable performance improvement in the overall importing of
email
messages into the repository.
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
Open Source Applications Foundation "Dev" mailing list
http://lists.osafoundation.org/mailman/listinfo/dev