🚀 Welcome to MDriven Learn –  MDriven is now on Discord!  Don’t miss the latest Release Notes.
CacheInvalidation
This page was created by Hans.karlsen on 2019-03-18. Last edited by Wikiadmin on 2026-07-29.

You can configure a server-side ViewModel cache that refreshes derived or persisted information when the model data it reads changes; this page is for MDrivenServer administrators and model developers who need that refresh to be selective and reliable.

What cache invalidation does

MDrivenServer cache invalidation keeps a server-side cache module current without recalculating every possible root object on a timer.

A cache module is a server-side ViewModel that MDrivenServer executes for a root object. While it runs, MDrivenServer records the model members that the ViewModel reads in a dependency list called a manifest. When a later save changes one of those recorded members, MDrivenServer marks the affected cache module as invalidated. A later update loop refreshes the invalidated module.

This approach is useful when a value is derived from many related objects and must be persisted or otherwise prepared for efficient use. For example, a Box has many Thing objects, each with a weight. A cache ViewModel rooted in Box can run an action that writes a persisted complex name or total based on the box name and the weights of its things. Changing a thing's weight invalidates the box cache because the action read that weight.

Cache invalidation is asynchronous. A save records changes first; MDrivenServer detects invalidation and performs the refresh in its server-side loops. Do not design a UI flow that requires a cache value to be refreshed in the same instant as the save.

Configure a cache-enabled server-side ViewModel

Create a dedicated ViewModel for the cached work. Keep its purpose narrow: it should read the dependencies and run the action that updates the cached or persisted result.

  1. In MDriven Designer, create a ViewModel rooted in the class that represents the cache root. For the example, root it in Box.
  2. Add an action to the ViewModel. The action performs the work that must be recalculated, such as setting the persisted complex name from the box and its things.
  3. Right-click the ViewModel and choose the server-side execution criteria editor.
  4. Make the ViewModel available for server-side execution.
  5. Select Cache and auto update. This subscribes to the data used by the ViewModel and persists that subscription in the database.
  6. Upload the model and update the server-side jobs so MDrivenServer receives the new or changed server-side ViewModel definition.
  7. Ensure that the cache invalidation tables exist and that cache invalidation is enabled in MDrivenServer.
  8. Make a change to data read by the ViewModel, such as changing a Thing.Weight, and allow the server-side update loop to run. Verify that the persisted result is recalculated.

The normal server-side execution criteria can select objects to run, but cache and auto update has a different role: it tracks dependencies for cache refresh. Keep these concerns clear when designing server-side jobs.

How invalidation and refresh work

The following sequence describes the persisted cache-invalidation workflow.

  1. Save: A normal save writes member-level changes to MDrivenCache_RecentlyUpdated. Newly created and deleted objects are recorded with created and deleted flags. For a changed embedded single-ended link, the other end is also recorded as dirty.
  2. Short transaction handling: MDrivenServer moves observed rows from MDrivenCache_RecentlyUpdated to MDrivenCache_RecentlyUpdatedWorkPart. This minimizes the period during which the recently-updated table is held in a transaction.
  3. Invalidation loop: MDrivenServer compares changed data with manifest dependencies. It invalidates direct matches on object identity, class, and member. It also considers objects that may now enter a set, changes matched through allInstances using created and deleted, and ViewModel metadata checksum changes.
  4. Update loop: MDrivenServer selects invalidated cache modules that are old enough to refresh, orders them by priority, instantiates the named ViewModel for its root object, and executes its actions. The run creates the dependency manifest used for subsequent invalidation.

The update loop also enables cache actions to save persisted calculated fields and create persisted derived objects.

Design the manifest deliberately

The manifest is the basis for automatic invalidation. It contains the model usage observed while the cache ViewModel executes. Read every value that should cause the cache to refresh.

Do not use a cache-written member as the trigger

MDrivenServer deliberately excludes changes made during the cache refresh from the manifest. This prevents a cache module from invalidating itself in a loop.

For example, do not use the same TimeRefreshed attribute both as a UI refresh signal and as the timestamp written by the cache action:

-- Avoid this pattern
-- UI sets TimeRefreshed to null
-- Cache action sets TimeRefreshed to DateTime.Now

Because the cache action writes TimeRefreshed, changes to that attribute are not a dependency that triggers the cache. Instead, separate the request from the result:

Member Written by Read by cache ViewModel Purpose
TimeWhenUserWantsThisRefreshed UI or other external process Yes Explicit refresh request
TimeRefreshed Cache action Not as the refresh trigger Timestamp or result of the cache refresh

For example, have the UI set TimeWhenUserWantsThisRefreshed. The cache ViewModel can read it with an expression such as self.TimeWhenUserWantsThisRefreshed.notnull, then write TimeRefreshed when it completes. The request attribute becomes a manifest dependency because the cache reads it but does not change it.

Avoid default-superclass members for precise triggers

Members on the model's default superclass are ignored for cache invalidation. These members are intentionally broad and abstract, so using them as narrow invalidation triggers would be expensive and imprecise.

Do not rely on a default-superclass member such as a general change timestamp to refresh one specific cache module. Add or use a concrete member on the relevant domain class when you need a surgical trigger. See Documentation:Working with Code and Persistence Mapping for related model and persistence configuration.

Control what enters the manifest

A cache ViewModel can expose the following specially named attributes.

Attribute name Type and meaning Example
CacheIgnoreObjects... You can define multiple attributes whose names start with CacheIgnoreObjects. Each returns an object list. Objects returned by these attributes are not added to the manifest. Return objects that must not create cache dependencies.
CacheIgnoreClasses A set of strings containing class names to exclude from the manifest and therefore from invalidation. Set{User.asstring,Singleton.asstring,SysSingleton.asstring,AcoSuper.asstring}
Priority An integer stored on the cache head and used to order refresh work. Lower values are higher priority. Use 1 for an urgent cache; the normal update default is 1000.

Use ignore options with care. Ignoring an object or class means later changes to it cannot invalidate the cache through that manifest dependency.

Administration and diagnosis

MDrivenServer provides a CacheInvalidation maintenance view. Use it to inspect the latest or oldest invalidated and updated cache modules, list the manifest rows for a selected module, and delete a module when administration requires it.

When a cache does not refresh, investigate in this order:

  1. Confirm that the ViewModel is enabled for server-side execution and has Cache and auto update selected.
  2. Confirm that the ViewModel action reads the data whose changes should trigger refresh.
  3. Inspect the cache module's manifest rows. If the expected class and member are absent, the cache run did not read that dependency, or the member was excluded because it was written during the run or belongs to the default superclass.
  4. Check whether unapplied change records remain in MDrivenCache_RecentlyUpdated.
  5. Inspect invalidated modules and refresh timing in the CacheInvalidation maintenance view.
  6. Confirm that the updated model and server-side jobs have been deployed to MDrivenServer.

The following SQL statements help a database administrator see whether saved changes are waiting to be processed:

SELECT COUNT(*)
FROM MDrivenCache_RecentlyUpdated;

SELECT COUNT(*), classname, membername
FROM MDrivenCache_RecentlyUpdated
GROUP BY classname, membername
ORDER BY classname, membername;

Run database queries only with appropriate operational access and change control.

Reset persisted manifest data

If you must clear cache invalidation manifest data and start over, delete the manifest heads and rows:

DELETE FROM MDrivenCache_ManifestHead;
DELETE FROM MDrivenCache_ManifestRow;

Deleting manifest heads causes relevant root objects to recreate cache modules that are marked for invalidation. This is a database-level maintenance operation; perform it only with a backup and an understanding of the refresh load that recreating the cache can produce.

Large transaction volumes: change row identifiers to BIGINT

On databases with very high transaction volume, the identity columns in cache tables can reach the approximately two-billion-row limit of a 32-bit integer. The affected tables are:

  • MDrivenCache_ManifestRow
  • MDrivenCache_RecentlyUpdated
  • MDrivenCache_RecentlyUpdatedWorkPart

Before changing these identifiers, update MDrivenServer to a version that handles the rowid value as Int64 when moving rows from MDrivenCache_RecentlyUpdated to MDrivenCache_RecentlyUpdatedWorkPart.

The following statements show the intended column definitions. Existing index dependencies can cause the drop operation to fail; identify and remove dependent indexes before rerunning the relevant statement, then recreate the indexes according to your database's requirements.

ALTER TABLE MDrivenCache_ManifestRow DROP COLUMN rowid;
ALTER TABLE MDrivenCache_ManifestRow
ADD rowid BIGINT PRIMARY KEY IDENTITY(1,1);

ALTER TABLE MDrivenCache_RecentlyUpdated DROP COLUMN rowid;
ALTER TABLE MDrivenCache_RecentlyUpdated
ADD rowid BIGINT PRIMARY KEY IDENTITY(1,1);

ALTER TABLE MDrivenCache_RecentlyUpdatedWorkPart DROP COLUMN rowid;
ALTER TABLE MDrivenCache_RecentlyUpdatedWorkPart
ADD rowid BIGINT PRIMARY KEY;

MDrivenCache_RecentlyUpdatedWorkPart.rowid is not an identity column.

For an acute short-term outage caused by the 32-bit identity limit, recreating an Int32 row identifier can reduce the current maximum to the actual rows still present, which may restore operation temporarily. Treat this only as a short-term recovery action; plan the BIGINT migration and MDrivenServer update for sustained high-volume systems.

See also

Cache-table rowid limit

Cache-table rowid limit

Some MDrivenCache tables with many transactions can reach the approximately two-billion-value limit of an Int32 identity column. The documentation identifies these tables as the primary tables concerned:

  • MDrivenCache_ManifestRow
  • MDrivenCache_RecentlyUpdated
  • MDrivenCache_RecentlyUpdatedWorkPart

To address this limit, recreate the rowid columns as BIGINT. The documented changes are:

-- This can fail because of index dependencies. Drop those indexes and rerun.
ALTER TABLE MDrivenCache_ManifestRow DROP COLUMN rowid
ALTER TABLE MDrivenCache_ManifestRow ADD rowid BIGINT PRIMARY KEY IDENTITY(1,1)

-- This can fail because of index dependencies. Drop those indexes and rerun.
ALTER TABLE MDrivenCache_RecentlyUpdated DROP COLUMN rowid
ALTER TABLE MDrivenCache_RecentlyUpdated ADD rowid BIGINT PRIMARY KEY IDENTITY(1,1)

-- This can fail because of index dependencies. Drop those indexes and rerun.
ALTER TABLE MDrivenCache_RecentlyUpdatedWorkPart DROP COLUMN rowid
ALTER TABLE MDrivenCache_RecentlyUpdatedWorkPart ADD rowid BIGINT PRIMARY KEY

MDrivenCache_RecentlyUpdatedWorkPart.rowid is not an identity column.

MDrivenServer must be updated when moving from MDrivenCache_RecentlyUpdated to MDrivenCache_RecentlyUpdatedWorkPart after this change, because it uses the rowid key. Newer versions handle this value as Int64 rather than Int32.

Short-term response

The documentation states that a system can halt because of the approximately two-billion-value limit. As an acute short-term fix, drop and reintroduce the affected rowid as an Int32 identity column. Recreating the identity reduces its maximum value to the values currently in use, which may be well below the Int32 limit.

See also