Skip to content

Minimize Calls of getRefObj/getLinkObj#459

Open
drfho wants to merge 24 commits intomainfrom
fix_redundant_link_processing
Open

Minimize Calls of getRefObj/getLinkObj#459
drfho wants to merge 24 commits intomainfrom
fix_redundant_link_processing

Conversation

@drfho
Copy link
Copy Markdown
Contributor

@drfho drfho commented Feb 7, 2026

Ref:

  1. https://github.com/idasm-unibe-ch/unibe-cms/issues/1124
  2. getObjProperty(): avoid unnecessary validation of empty values #452
  3. Linkelement: Long loading times for pages with many links #440

The function getLinkObj is called very often to process a link with in zmslinkelement.py. To minimize these calls (which hits the catalog) a cache for getRefObj() in the request buffer is introduced.

Tracing the calls back it reveales that getRefObj() in zmslinkelement.py:195 will be called 18 times within ZMSLinkElement (from getObjProperty, getType, isActive, isVisible, initObjChildren, getPageExt, renderShort, etc.), and each time it resolved the link from scratch via getLinkObj().

The fix adds request-buffering to getRefObj() using the same fetchReqBuff/storeReqBuff pattern already used by getInternalLinkDict. The cache key is 'getRefObj.%s' % self.get_uid(), so each ZMSLinkElement resolves its reference object once per HTTP request instead of 12-18 times.

image

@drfho drfho requested a review from zmsdev February 7, 2026 17:42
@drfho
Copy link
Copy Markdown
Contributor Author

drfho commented Feb 7, 2026

Hint: the debug code (to be removed) shows how the calls are reduced:

# DEBUG: logging/counting getLinkObj calls
request.set('getLinkObj_counter', request.get('getLinkObj_counter', 0) + 1)
standard.writeStdout(self, '[getLinkObj] url=%s, ob=%s, ref_params=%s, counter=%d'%(
url,
ob.absolute_url() if ob is not None else None,
ref_params,
request.get('getLinkObj_counter', 0)
)
)

image

request.get('getLinkObj_counter', 0)
ob.id if ob is not None else None,
ref_params,
'\n\t'.join(traceback_stack)
Copy link
Copy Markdown
Contributor Author

@drfho drfho Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zmsdev: the traceback stack now shows the last 3 function calls ending with the call of getLinkObj(). The example shows 8 Linkobjects that induce each about 8 function calls for creating the rendering. 51 of 66 functions calls are delivered from the new request-buffer.

Image

@drfho
Copy link
Copy Markdown
Contributor Author

drfho commented Feb 9, 2026

Test Custom-Linkcontainer using getLinkObj() for rendering

@zmsdev: a typical user-test scenario reveals that a linkcontainer of e.g. 8 custom-linkelements[*] calls 52x getLinkObj() without the buffering-optimisation in the function getRefObj() (this PR) including 9x of an aquired link-element (indirect functions calls from ouside the current context and thus without any rendering, see following picture). After introducing the buffering the getLinkObj()-calls are reduced to 33 (including 1x for aquired link-element, see no. 11 in traceback.log).
So, besides the repetitive calls of getLinkObj/getRefObj() the number of aquired link-elements may create an unexpected redundancy additionally.
At this state the optimisation reduces direct-calls from 43 to 32 (about 25%)

aq_links

[*] custom-linkcontainer

image

Test Standard-ZMSLinkContainer

If we test the rendering of 8 standard ZMSLinkElements aggregated by the standard ZMSLinkContainer, we will get 16+1 calls of getLinkObj, means 2x each Link plus 1x for aquired. Without optimisation the standard_html creates 74+1 calls (80% less redundancy).

image
[ZMSLinkElement:getLinkObj] uid:00bcad5a-bce7-4b83-948d-fc5bead41d67, Target-ID=e5 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:00bcad5a-bce7-4b83-948d-fc5bead41d67, Target-ID=e5 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:00bcad5a-bce7-4b83-948d-fc5bead41d67, Target-ID=e5 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:00bcad5a-bce7-4b83-948d-fc5bead41d67, Target-ID=e5 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:00bcad5a-bce7-4b83-948d-fc5bead41d67, Target-ID=e5 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:00bcad5a-bce7-4b83-948d-fc5bead41d67, Target-ID=e5 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:00bcad5a-bce7-4b83-948d-fc5bead41d67, Target-ID=e5 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:00bcad5a-bce7-4b83-948d-fc5bead41d67, Target-ID=e5 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:00bcad5a-bce7-4b83-948d-fc5bead41d67, Target-ID=e5 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:0ba6aba3-0217-4d94-8d2f-df21bcc766b6, Target-ID=e12 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:0ba6aba3-0217-4d94-8d2f-df21bcc766b6, Target-ID=e12 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:0ba6aba3-0217-4d94-8d2f-df21bcc766b6, Target-ID=e12 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:0ba6aba3-0217-4d94-8d2f-df21bcc766b6, Target-ID=e12 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:0ba6aba3-0217-4d94-8d2f-df21bcc766b6, Target-ID=e12 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:0ba6aba3-0217-4d94-8d2f-df21bcc766b6, Target-ID=e12 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:0ba6aba3-0217-4d94-8d2f-df21bcc766b6, Target-ID=e12 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:0ba6aba3-0217-4d94-8d2f-df21bcc766b6, Target-ID=e12 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:0ba6aba3-0217-4d94-8d2f-df21bcc766b6, Target-ID=e12 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:0dd5fb6f-bfb4-4307-96f8-6add09df52a6, Target-ID=e60 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:0dd5fb6f-bfb4-4307-96f8-6add09df52a6, Target-ID=e60 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:0dd5fb6f-bfb4-4307-96f8-6add09df52a6, Target-ID=e60 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:0dd5fb6f-bfb4-4307-96f8-6add09df52a6, Target-ID=e60 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:0dd5fb6f-bfb4-4307-96f8-6add09df52a6, Target-ID=e60 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:0dd5fb6f-bfb4-4307-96f8-6add09df52a6, Target-ID=e60 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:0dd5fb6f-bfb4-4307-96f8-6add09df52a6, Target-ID=e60 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:0dd5fb6f-bfb4-4307-96f8-6add09df52a6, Target-ID=e60 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:0dd5fb6f-bfb4-4307-96f8-6add09df52a6, Target-ID=e60 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:0dd5fb6f-bfb4-4307-96f8-6add09df52a6, Target-ID=e60 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:0dd5fb6f-bfb4-4307-96f8-6add09df52a6, Target-ID=e60 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:44b58809-bfe4-4759-b86b-c30ff5ae6ae9, Target-ID=e57 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:44b58809-bfe4-4759-b86b-c30ff5ae6ae9, Target-ID=e57 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:44b58809-bfe4-4759-b86b-c30ff5ae6ae9, Target-ID=e57 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:44b58809-bfe4-4759-b86b-c30ff5ae6ae9, Target-ID=e57 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:44b58809-bfe4-4759-b86b-c30ff5ae6ae9, Target-ID=e57 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:44b58809-bfe4-4759-b86b-c30ff5ae6ae9, Target-ID=e57 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:44b58809-bfe4-4759-b86b-c30ff5ae6ae9, Target-ID=e57 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:44b58809-bfe4-4759-b86b-c30ff5ae6ae9, Target-ID=e57 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:44b58809-bfe4-4759-b86b-c30ff5ae6ae9, Target-ID=e57 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:4c17e3a2-fb35-4b5e-99cc-67bfe44347e4, Target-ID=e65 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:4c17e3a2-fb35-4b5e-99cc-67bfe44347e4, Target-ID=e65 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:4c17e3a2-fb35-4b5e-99cc-67bfe44347e4, Target-ID=e65 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:4c17e3a2-fb35-4b5e-99cc-67bfe44347e4, Target-ID=e65 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:4c17e3a2-fb35-4b5e-99cc-67bfe44347e4, Target-ID=e65 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:4c17e3a2-fb35-4b5e-99cc-67bfe44347e4, Target-ID=e65 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:4c17e3a2-fb35-4b5e-99cc-67bfe44347e4, Target-ID=e65 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:4c17e3a2-fb35-4b5e-99cc-67bfe44347e4, Target-ID=e65 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:4c17e3a2-fb35-4b5e-99cc-67bfe44347e4, Target-ID=e65 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:664eedeb-3fff-470e-91ea-d1e5036e3834, Target-ID=e138 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:664eedeb-3fff-470e-91ea-d1e5036e3834, Target-ID=e138 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:664eedeb-3fff-470e-91ea-d1e5036e3834, Target-ID=e138 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:664eedeb-3fff-470e-91ea-d1e5036e3834, Target-ID=e138 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:664eedeb-3fff-470e-91ea-d1e5036e3834, Target-ID=e138 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:664eedeb-3fff-470e-91ea-d1e5036e3834, Target-ID=e138 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:664eedeb-3fff-470e-91ea-d1e5036e3834, Target-ID=e138 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:664eedeb-3fff-470e-91ea-d1e5036e3834, Target-ID=e138 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:664eedeb-3fff-470e-91ea-d1e5036e3834, Target-ID=e138 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:93bb8004-aad3-41cb-aea6-b72d986c3e8f, Target-ID=e22 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:93bb8004-aad3-41cb-aea6-b72d986c3e8f, Target-ID=e22 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:93bb8004-aad3-41cb-aea6-b72d986c3e8f, Target-ID=e22 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:93bb8004-aad3-41cb-aea6-b72d986c3e8f, Target-ID=e22 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:93bb8004-aad3-41cb-aea6-b72d986c3e8f, Target-ID=e22 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:93bb8004-aad3-41cb-aea6-b72d986c3e8f, Target-ID=e22 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:93bb8004-aad3-41cb-aea6-b72d986c3e8f, Target-ID=e22 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:93bb8004-aad3-41cb-aea6-b72d986c3e8f, Target-ID=e22 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:93bb8004-aad3-41cb-aea6-b72d986c3e8f, Target-ID=e22 (ZMSFolder), ref_params={}
[ZMSLinkElement:getLinkObj] uid:9c704923-ca1d-4c3d-9045-07d4adddb706, Target-ID=e63 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:9c704923-ca1d-4c3d-9045-07d4adddb706, Target-ID=e63 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:9c704923-ca1d-4c3d-9045-07d4adddb706, Target-ID=e63 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:9c704923-ca1d-4c3d-9045-07d4adddb706, Target-ID=e63 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:9c704923-ca1d-4c3d-9045-07d4adddb706, Target-ID=e63 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:9c704923-ca1d-4c3d-9045-07d4adddb706, Target-ID=e63 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:9c704923-ca1d-4c3d-9045-07d4adddb706, Target-ID=e63 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:9c704923-ca1d-4c3d-9045-07d4adddb706, Target-ID=e63 (ZMSDocument), ref_params={}
[ZMSLinkElement:getLinkObj] uid:9c704923-ca1d-4c3d-9045-07d4adddb706, Target-ID=e63 (ZMSDocument), ref_params={}

[ZMSLinkElement:getLinkObj] uid:bdafd639-6fc2-45e2-a96f-bcb66ddf7da7, Target-ID=e132 (ZMSFlexbox ACQUIRED), ref_params={}

drfho added a commit that referenced this pull request Feb 11, 2026
drfho added a commit that referenced this pull request Feb 18, 2026
@drfho
Copy link
Copy Markdown
Contributor Author

drfho commented Mar 27, 2026

Alternatives to Request-Buffering with ZMS

To save Zope session data that is accessible across all users (a "shared" session or global buffer), there several options depending on the required persistence and performance.

Since we want store link data dictionaries to speed up resolution (moving beyond the request-bound ReqBuff), there are the following approaches:

1. RAMCacheManager (Recommended for High Performance)

Zope's built-in RAMCacheManager is designed exactly for this. It stores data in memory across all threads and users. It is much faster than the ZODB because it avoids database commits and conflict errors.

  • Access: You can find it in the ZMI (usually at the Zope root or inside your ZMS instance).
  • Usage in Python:
    cache_manager = context.get_cache_manager('my_ram_cache')
    cache = cache_manager.ZCacheManager_getCache()
    
    # Store data
    cache.ZCache_set(context, link_dict, view_name='link_resolution', keywords={'uid': uid})
    
    # Fetch data
    value = cache.ZCache_get(context, view_name='link_resolution', keywords={'uid': uid})

2. Products.mcdutils (Memcached - for Distributed Clusters)

If Products.mcdutils (Memcache) is used and the Zope setup involves multiple physical servers (a cluster), RAMCacheManager won't work because it's local to one process.

  • Suggestion: Use the MemCacheZCacheManager (seen in zcache.py) which provides the same API as RAMCacheManager but stores data in a shared memcached daemon.

3. ZMS-Specific Persistence (ZMSIndex)

If the "link data" involves just mapping UIDs to paths, ZMS already has a central facility for this: the ZMSIndex.

zmsindex = context.getZMSIndex()
catalog = zmsindex.get_catalog()
brains = catalog(get_uid=my_uid)

But ZMSIndex has a rigid minimal schema and cannot store further data about visibility etc. by default.

4. Volatile Attributes _v_ (Thread-Shared, Non-Persistent)

If you just want a simple in-memory cache on a singleton object (like the ZMS root) that doesn't need to survive a restart:

  • Logic: Attributes starting with _v_ are not saved to the database. They stay in memory as long as the object stays in the ZODB cache.
    if not hasattr(zms_root, '_v_link_cache'):
        zms_root._v_link_cache = {}
    zms_root._v_link_cache[uid] = link_data

Comparison for your use case:

Method Shared? Persistent? Best for...
ReqBuff No No Single Request performance
RAMCache Yes No (restarts) High-speed link resolution lookup
Memcache Yes No (restarts) Multi-server/clustered Zope instances
ZMSIndex Yes Yes Standard ZMS object/path lookups

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants