Table of Contents

Merge - incremental recalculation

@since 15.15.x

This page describes the incremental recalculation of MERGE controlled values introduced by the rework of provisioning merge cleanup. It complements the Merge page , which still describes the conceptual model of controlled values; this page focuses on the how of the recalculation pipeline and its operational concerns.

The legacy evictControlledValuesCache flag and the synchronized full recalculation are kept as a transitional fallback only. The default for new attributes is now false and the production cleanup runs incrementally via dirty states.
Controlled values are stored as Java Serializable in a bytea column on SysAttributeControlledValue. All equality checks - active vs. historic matching, deduplication and the value-based filter - compare the serialized bytes. This is reliable for simple primitive types like String, Boolean and numeric types. For complex objects (Map, List of mixed entries, custom DTOs) the serialized form is not guaranteed to be stable across runs, JVM upgrades or even calls in the same process - two semantically equal values may serialize to different bytes and silently appear as separate records (active+historic split, missing dedup, broken value lookup). Always return a simple constant value from a merge transform script.

Motivation

Before 15.15.x the recalculation worked like this:

In a real installation with ~14 000 roles this regularly blocked all provisioning for tens of minutes after a small bulk role change (e.g. import of 5 roles), because the lock and the SERIALIZABLE transaction serialized the whole tenant.

The new design replaces "full recompute on a global lock" with "process only what changed, lock-free":

Architecture

Key artifacts

Dirty state lifecycle

RoleSystemAttributeDirtyStateProcessor (CzechIdM event processor for SysRoleSystemAttribute CREATE/UPDATE) enqueues a dirty state when:

If a previous dirty state already exists for the same role-system-attribute, it is reused (deduplicated by owner). Repeated saves therefore never accumulate states.

DELETE does not enqueue a dirty state. The deletion cleanup is synchronous in RoleSystemAttributeDeleteProcessor (removes link records and runs orphan check on each released controlled value).

Drain

The drain (DefaultSysRoleSystemAttributeControlledValueService.processIncremental(UUID mappingId)) pages pending dirty states with a single filter (ownerType, superOwnerId=mappingId, code=DIRTY_STATE, state=BLOCKED) and processes them until the queue is empty:

Active vs. historic invariant

A controlled value on a parent mapping is either active or historic, never both. The drain enforces this in two places:

This is the same semantic as the legacy setControlledValues reconcile, only enforced incrementally per role-system-attribute change instead of via a global recompute.

Lazy drain during provisioning

DefaultSysSystemAttributeMappingService.getCachedControlledAndHistoricAttributeValues calls processIncremental(attributeMapping.getId()) before reading controlled values from the DB. The first provisioning after a role change therefore picks up the pending dirty states automatically; no separate task or admin action is needed.

The legacy evict block is still present in the same method as a transitional fallback for data with evictControlledValuesCache=true. Because the default is now false and nothing sets it back to true, the legacy block stays dormant for new installations.

One-time initialization

After upgrade to 15.15.x the sys_role_sys_attr_contr_val table is empty and has to be filled from existing role-system-attributes. This is done by AttributeControlledValuesInitTaskExecutor, started automatically by AccInitMergeControlledValueProcessor on application startup.

The task is executed synchronously via LongRunningTaskManager.executeSync - the application is fully ready only after init completes. This guarantees that the first provisioning call after upgrade sees a consistent state and does not race with the init.

The task iterates every SysRoleSystemAttribute, evaluates the transform script and either:

Configuration

In the application profile (application.properties):

# One-time initialization of the merge controlled values table (sys_role_sys_attr_contr_val).
# Runs synchronously at application startup, blocks the start until finished
# (necessary so any first provisioning sees a complete state).
# The init long running task fills the SysRoleSystemAttributeControlledValue records for all existing role-system-attributes
# and sets this property to 'false' once it finishes successfully.
# - true: init will run on the next start (default for a fresh deploy)
# - false: init was already executed (set automatically after the first successful run)
# Set this back to 'true' manually only if the table was wiped or you need a full re-init.
# @since 15.15.x
idm.sec.acc.provisioning.controlledValues.init.enabled=true
For ~10 000 role-system-attributes the initialization takes around 20 minutes on a typical machine. It runs in a single transaction, so a failure rolls back the partial state and the task is simply re-run on the next start.
Until the init LRT finishes, no provisioning happens (synchronous startup). For very large installations plan the upgrade window accordingly. After successful init the property flips to false and the application starts normally on every next restart.

Manual recalculation

Admins can still trigger AttributeControlledValuesRecalculationTaskExecutor from Scheduler → Long running tasks. Form parameters:

The parameter name only-evicted is kept for backward compatibility. With the deprecation of the evict flag it now reads as "incremental mode" rather than "only attributes with evict=true".

Common situations

First time: Manual initialization on appliance

On the appliance environment the default synchronous startup initialization may be impractical for very large installations - the application is unavailable until the init LRT finishes (see One-time initialization above). For these cases the init can be disabled at startup and triggered later as a regular scheduled task. This trades a long single downtime for a shorter downtime plus a controlled operational window in which the new mechanism is not fully active.

Steps

  1. Create an override properties file on the appliance, for example:
    /data/volumes/czechidm/application.properties.d/application-skip.properties
  2. Disable the automatic init in that file:
    idm.sec.acc.provisioning.controlledValues.init.enabled=false
  3. Restart the IdM service:
    systemctl restart iam-czechidm
  4. Wait until IdM is fully up.
  5. In Scheduler → Long running tasks, plan and execute the AttributeControlledValuesInitTaskExecutor task manually.

Risks

This procedure leaves a window of inconsistent state between the application startup and the manual completion of the init task. During this window the new incremental mechanism does not have a complete picture of which role-system-attribute contributes which controlled value, so any change to a merge role-system-attribute may behave incorrectly. Treat merge role-system-attribute administration (save / disable / delete) as frozen until the manual init finishes.

The concrete risks are:

After AttributeControlledValuesInitTaskExecutor finishes successfully, persist the disabled flag through the IdM GUI (Nastavení → Konfigurace aplikace) so the override file can be safely removed - if the override file were deleted on its own, the next restart would re-trigger the synchronous init. Set the property via GUI:
idm.sec.acc.provisioning.controlledValues.init.enabled=false

and then delete the original override file from /data/volumes/czechidm/application.properties.d/.