Commit a016ed04 authored by Vincent Pelletier's avatar Vincent Pelletier

CMFActivity: Optimise validation queries.

See SQLBase._getExecutableMessageSet for operation principle.
Removes the notion of order_validation_text: activity validation is no
longer evaluated per-activity , but per-dependency for multiple activities
at a time. In this context, order_validation_text does not make sense as
it flattens all dependency types for a given activity.
Rework activity-dependency-to-SQL methods: use a dict rather
dynamically-generated method names.
Based on initial work by Julien Muchembled.
parent f09e1a36
master DateTime.equalTo_fix TMP-2to3 UserPropertySheet_backward_compatibility addToDate_implicit_localtime add_boolean_type arnau arnau-RD-ERP5ify-portal_workflow-BAK arnau-RD-ERP5ify-portal_workflow-WIP arnau-RD-ERP5ify-portal_workflow-WITHOUT-MIGRATION arnau-RD-ERP5ify-portal_workflow-WORKFLOWS-NOT-MIGRATED arnau-RD-py3-master-TM arnau-RD-py3-master-TM-BAK arnau-RD-py3-master-WIP arnau-RD-py3-master-WIP-BAK arnau-RD-py3-master-WIP-bt5-and-tests arnau-RD-zope4py3 arnau-my2to3 arnau-zope4py2 aurel-zope4py2 cache-control-304-response drop-ZServer e2e-erp5 erp5-component erp5-vifib erp5-vifib-no-Products.DCWorkflowGraph erp5-vifib-py3 erp5_drone_simulator feat/coding_style_test feat/dedup_roles_in_pickles feat/erp5pt feat/fsum feat/improve_rounding_tool feat/lxml-html-snapshot feat/mariadb-10.11 feat/mariadb-10.11bis feat/mariadb-10.11ter feat/mariadb-10.5 feat/mariadb-10.6 feat/mariadb-11.4bis feat/mariadb-11.4bis-old feat/mariadb-11.4ter feat/notification-message-ignore-missing feat/python_language_support feat/reindexlastobjects_log feat/round_half_up feat/selenium-unexpected-success feat/slapos_agent_distributor feat/subject_set_query fix/GHSA-g5vw-3h65-2q3v fix/TALES_hide_error fix/accounting-fec-no-line fix/erp5_site_global_id fix/mariadb-1927 fix/measure-optional-variation fix/monaco-altClick fix/officejs_support_request_rss_secu fix/state_var fix/testnode_proctitle fix/workflow_info fix/workflow_method_security fix_isIndexable fix_web_illustration for_testrunner_1 for_testrunner_2 for_testrunner_3 graphic_gadget_js limit_accelerated_http_cache_manager mr1362 oauth-login-minor-improvement override_cache_control_header_by_caching_policy_manager poc/json-forms-study revert-192c2000 rfc/activate_default roque_quick support_legacy_sftp_server support_relative_url_in_hyperlink_field test_cmfactivity_isolation_level test_dynamic_methods tomo_testnode_slap_request translatable_path_master without_legacy_workflow workaround_mroonga_14 zope2 zope2zope4py2 zope4py3 zope4py3-BEFORE-CLEANUP zope4py3-master-rebase erp5.util-0.4.77 erp5.util-0.4.76 erp5.util-0.4.75 erp5.util-0.4.74 erp5.util-0.4.73 erp5.util-0.4.72 erp5.util-0.4.71 erp5-vifib-20240326 erp5-vifib-20230331 erp5-vifib-20230201 erp5-vifib-20220526 erp5-vifib-20220302 erp5-vifib-20210707 erp5-vifib-20200129
1 merge request!1334CMFActivity: Reduce distribution query count
Pipeline #13170 failed with stage
......@@ -89,74 +89,9 @@ class Queue(object):
def distribute(self, activity_tool, node_count):
raise NotImplementedError
def getExecutableMessageList(self, activity_tool, message, message_dict,
validation_text_dict, now_date=None):
"""Get messages which have no dependent message, and store them in the dictionary.
If the passed message itself is executable, simply store only that message.
Otherwise, try to find at least one message executable from dependent messages.
This may result in no new message, if all dependent messages are already present
in the dictionary, if all dependent messages are in different activities, or if
the message has a circular dependency.
The validation text dictionary is used only to cache the results of validations,
in order to reduce the number of SQL queries.
"""
if message.uid in message_dict:
# Nothing to do. But detect a circular dependency.
if message_dict[message.uid] is None:
LOG('CMFActivity', ERROR,
'message uid %r has a circular dependency' % (message.uid,))
return
cached_result = validation_text_dict.get(message.order_validation_text)
if cached_result is None:
message_list = activity_tool.getDependentMessageList(message, self)
if message_list:
# The result is not empty, so this message is not executable.
validation_text_dict[message.order_validation_text] = 0
if now_date is None:
now_date = DateTime()
for activity, m in message_list:
# Note that the messages may contain ones which are already assigned or not
# executable yet.
if activity is self and m.processing_node == -1 and m.date <= now_date:
# Call recursively. Set None as a marker to detect a circular dependency.
message_dict[message.uid] = None
try:
self.getExecutableMessageList(activity_tool, m, message_dict,
validation_text_dict, now_date=now_date)
finally:
del message_dict[message.uid]
else:
validation_text_dict[message.order_validation_text] = 1
message_dict[message.uid] = message
elif cached_result:
message_dict[message.uid] = message
def flush(self, activity_tool, object, **kw):
pass
def getOrderValidationText(self, message):
# Return an identifier of validators related to ordering.
order_validation_item_list = [
(key, value)
for key, value in sorted(
message.activity_kw.iteritems(), key=lambda x: x[0],
)
if value is not None and
getattr(self, "_validate_" + key, None) is not None
]
if order_validation_item_list:
return sha1(repr(order_validation_item_list)).hexdigest()
# When no order validation argument is specified, skip the computation
# of the checksum for speed. Here, 'none' is used, because this never be
# identical to SHA1 hexdigest (which is always 40 characters), and 'none'
# is true in Python. This is important, because dtml-if assumes that an empty
# string is false, so we must use a non-empty string for this.
return 'none'
def getMessageList(self, activity_tool, processing_node=None,**kw):
return []
......
This diff is collapsed.
......@@ -106,11 +106,10 @@ CREATE TABLE %s (
values_list = []
max_payload = self._insert_max_payload
sep_len = len(self._insert_separator)
hasDependency = self._hasDependency
for m in message_list:
if m.is_registered:
active_process_uid = m.active_process_uid
order_validation_text = m.order_validation_text = \
self.getOrderValidationText(m)
date = m.activity_kw.get('at_date')
row = ','.join((
'@uid+%s' % i,
......@@ -118,7 +117,7 @@ CREATE TABLE %s (
'NULL' if active_process_uid is None else str(active_process_uid),
"UTC_TIMESTAMP(6)" if date is None else quote(render_datetime(date)),
quote(m.method_id),
'0' if order_validation_text == 'none' else '-1',
'-1' if hasDependency(m) else '0',
str(m.activity_kw.get('priority', 1)),
quote(m.getGroupId()),
quote(m.activity_kw.get('tag', '')),
......
......@@ -1811,31 +1811,9 @@ class ActivityTool (BaseTool):
REQUEST['RESPONSE'].redirect( 'manage_main' )
return obj
security.declarePrivate('getDependentMessageList')
def getDependentMessageList(self, message, validating_queue=None):
activity_kw = message.activity_kw
db = self.getSQLConnection()
quote = db.string_literal
queries = []
for activity in activity_dict.itervalues():
q = activity.getValidationSQL(
quote, activity_kw, activity is validating_queue)
if q:
queries.append(q)
if queries:
message_list = []
for line in Results(db.query("(%s)" % ") UNION ALL (".join(queries))):
activity = activity_dict[line.activity]
m = Message.load(line.message,
line=line,
uid=line.uid,
date=line.date,
processing_node=line.processing_node)
if not hasattr(m, 'order_validation_text'): # BBB
m.order_validation_text = activity.getOrderValidationText(m)
message_list.append((activity, m))
return message_list
return ()
security.declarePrivate('getSQLQueueTableNameSet')
def getSQLQueueTableNameSet(self):
return [x.sql_table for x in activity_dict.itervalues()]
# Required for tests (time shift)
def timeShift(self, delay):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment