This document summarizes what has been done from September 2010 to February 2011,
related to portal type classes, zodb property sheets, and accessor generation.
It should help getting a rough understanding of what portal type class is and
how to use it.
Table of Contents
You cannot create instances of documents directly:
This use should be banned, and will break. Instead, you should use
You can experiment to understand the difference: the first use creates a Document
instance, the second used creates a portal type class. We only want portal type
All objects you create must have a portal type object defined in Types Tool.
No exception. Tools should have a portal type as well.
We're getting rid of Base._aq_dynamic, and accessors are now generated directly
from Property Sheet definitions, and put into Accessor Holder: one Accessor
Holder for each existing Property Sheet item.
Accessor Holder are classes, and you can see them in the method resolution order
of your ERP5 objects. For instance, for a person, person.__class__.mro() is:
Each accessor_holder class corresponds to accessors that come directly from a
Property Sheet. Note as well accessor_holder.BaseAccessorHolder which contains
common methods such as related category getters and portal type group getters.
_aq_reset is gone as well
The effective tradeoff of this change is the following: we trade dynamic lazy
generation for static generation plus a few mro()-deep lookups. Check for
example the Base._edit code, where we have to lookup in a class mro() to fetch
the list of restricted methods. This kind of places where we have to walk one's
mro() are costly places. On the other hand, it's EASY to optimize them. With
lazy aq_dynamic, environment was constantly changing, and we had no guarantees
that everything was generated. But with portal type classes/accessor holders,
nothing ever changes once the class has been generated once: at the end of
loadClass() (ERP5.dynamic.lazy_class) nothing will ever happen to the class
anymore, data is "static". So it means that all deep computations we do can
be safely cached on the class object for later. Back to our _edit example,
the list of method ids that are restricted can be, and should probably be
computed once and stored on the portal type class
A new performance test can now be written. On a tiny instance, that only has
for property_sheet in portal.portal_property_sheets.contentValues():
And time this loop. The impact of accessor generation is now easy to measure
and improve, instead of being a giant octopus with tentacles that unfold at
every dynamic call. Once you've looped over this list, you're mostly done,
and the rest of the code only gathers useful accessor holders and puts them
on the right classes. Cherry picking with workflow twists, as you still need
to wrap accessors as WorkflowMethod on the portal type class. We may start
with a relatively higher cost, but that's easier to improve, easier to profile,
easier to optimize.
If then, you want to assess the cost of Workflow method generation, you can do
for portal_type_id in portal.portal_types.objectIds():
And once again, time it.
Generally speaking, we generate things less blindly, and after cleanups the
memory usage should drop to a lower figure than with aq_dynamic.
The globals in Utils are evil, and cache too much. I suppose that removing them
or emptying them WILL save a lot of memory. Similarly, I'm questioning the
validity and use of the workflow_method_registry attributes on portal
type/property holder/accessor holder classes
There was something relatively bad in the way we were using _aq_reset. Scenario:
This edition triggers two workflow triggers, one for the class change, and one
for the base category change. Each trigger used to cause an _aq_reset call.
Generally speaking, if during one transaction we had N property changes on M
different objects, we would trigger N*M times _aq_reset. That begs the question:
is it absolutely compulsory to reset accessors immediately after one's action?
If we think about it, 100% of the actions that can trigger accessor regeneration
are user-triggered. Meaning that transactions will be short-lived, and that in
case of a success, a commit() will happen under a short time. So can we delay
the reset at commit time? Yes, it seemed so. It has a few nice properties:
Why did I care so much about the number of resets? With new accessor generation,
we do a bit more during generation; and especially the generation of basic
properties are very costly. So chaining several resets *is* costly, much more
than two aq_resets.