Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

experimental.catalogmoveopt

Plone add-on that optimizes catalog operations when content is moved or renamed, preserving Record IDs (RIDs) and reindexing only the indexes that actually change.

The problem

When an object is moved or renamed in stock Plone, Products.CMFCore fires a full catalog cycle: unindex at the old path, reindex everything at the new path. For large content trees this is expensive because every index is recomputed even though most of them (title, description, content body, …) have not changed at all. It also assigns a new RID to the object, which can invalidate in-flight catalog references.

How it works

The add-on monkey-patches Products.CMFCore at Zope startup (via an IProcessStarting subscriber) to replace the stock handleContentishEvent with an optimized version.

On a true object move (old parent ≠ new parent, i.e. cut-paste):

  1. IObjectWillBeMovedEvent — instead of calling unindexObject(), the object's current physical path is saved in the transaction-local registry keyed by its ZODB _p_oid. The catalog entry is left untouched.
  2. IObjectMovedEvent — the saved old path is retrieved, and CatalogTool.moveObject() (injected by this add-on) is called. It remaps old_path → same RID → new_path in the catalog's internal BTree structures, then calls reindexObject() with only the context-aware indexes.

The net result: the RID is preserved, only the path-dependent and security-dependent indexes are recomputed, and the full reindex of expensive text/metadata indexes is skipped entirely.

For renames (same parent, new id) the same path is followed — the object stays in the same container, only its path and id change.

For all other event types (add, copy, delete) the behaviour is identical to stock CMFCore.

Transaction-local path registry

The old path is stored via transaction.set_data() / transaction.data(), keyed by a stable module-level singleton object. This avoids the _v_ volatile attribute pattern, which is vulnerable to ZODB cache ghostification: for large subtrees, objects can be evicted from the ZODB cache between the WillBeMoved and Moved event phases, causing silent fallback to a full reindex. Transaction-attached data lives outside the ZODB object graph and is discarded automatically on commit or abort.

Context-aware indexes

Only indexes whose values change when an object moves need to be reindexed. This add-on ships with two built-in providers:

Provider name Indexes
cmf.location path, getId, id
cmf.security allowedRolesAndUsers

Third-party packages can contribute additional indexes by registering a named utility providing IContextAwareIndexProvider:

<!-- my.package/configure.zcml -->
<utility
    provides="experimental.catalogmoveopt.interfaces.IContextAwareIndexProvider"
    name="my.package.myindex"
    component=".providers.MyIndexProvider"
    />
# my.package/providers.py
from zope.interface import implementer
from experimental.catalogmoveopt.interfaces import IContextAwareIndexProvider

@implementer(IContextAwareIndexProvider)
class MyIndexProvider:
    def getIndexNames(self):
        return ("my_custom_index",)

If no providers are registered the optimization is disabled and the stock full-reindex path is used as a safe fallback.

Installation

Add experimental.catalogmoveopt to your Plone backend's dependencies:

# pyproject.toml
dependencies = [
    ...
    "experimental.catalogmoveopt",
]

No further configuration is required. The add-on uses z3c.autoinclude.plugin so its ZCML is loaded automatically when installed in a Plone site.

Compatibility

Plone Python
6.0 3.10, 3.11
6.1 3.10, 3.11, 3.12
6.2 3.10, 3.11, 3.12, 3.13

Development

git clone git@github.com:RedTurtle/experimental.catalogmoveopt.git
cd experimental.catalogmoveopt
make install
make test

Prior art and upstream discussion

This add-on exists as a monkey-patch package while the optimization makes its way into the Plone/CMFCore ecosystem proper. Key references:

  • 4teamwork/ftw.copymovepatches — the original proof-of-concept for Plone 4.3 that demonstrated the approach. A real-world benchmark reported an 80-second move of a folder with ~300 files dropping to ~8 seconds (~10× speedup).

  • plone/Products.CMFPlone#3834 — David Glick's draft experiment bringing the same optimization to Plone 6, using ftw.copymovepatches as the starting point. The linked comment explicitly requests that the fix land in CMFCore rather than as a monkey-patch in CMFPlone.

  • zopefoundation/Products.CMFCore#161 — the upstream CMFCore pull request (by the author of this package) that proposes adding CatalogTool.moveObject() and the IContextAwareIndexProvider interface directly to CMFCore. Once merged, this add-on will become unnecessary.

Contribute

License

The project is licensed under GPLv2.

Release files for experimental.catalogmoveopt 1.0.0a1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for experimental.catalogmoveopt 1.0.0a1
File Size Uploaded
experimental_catalogmoveopt-1.0.0a1.tar.gz 23.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for experimental.catalogmoveopt 1.0.0a1
File Interpreter ABI Platform
experimental_catalogmoveopt-1.0.0a1-py3-none-any.whl Python 3 none any Details

Total release size: 43.1 kB

Release files / experimental_catalogmoveopt-1.0.0a1.tar.gz

Download URL experimental_catalogmoveopt-1.0.0a1.tar.gz
Size 23.9 kB
Tags Source
SHA-256 checksum
How to use checksums
fd551247618b72f600a6c16dc770184e2c9ac46172b0b5fb2e17c1b886f67b88
BLAKE2b-256 checksum
How to use checksums
51963727cde315412b3487d1333e9949a8d4935505710307e21edb8d3cbfab94
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.10.16

Release files / experimental_catalogmoveopt-1.0.0a1-py3-none-any.whl

Download URL experimental_catalogmoveopt-1.0.0a1-py3-none-any.whl
Size 19.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
475f6a47838d28d433647b707fd0723f5fb88e836f6a0e84239f662aed742bbb
BLAKE2b-256 checksum
How to use checksums
7baabe0c7d09b45ed0a6e2de37ef1298c554d8f0f180df0814093321a2a390c5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.10.16

Release history Release notifications | RSS feed

This release

1.0.0a1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page