Question
· Feb 8, 2023

Why are "Exclusive_e->Delock" needed when saving persistent objects in a transaction ? (and can they be avoided)

I use the following code (which is a simplified version of what happen on a server) :

tstart
for I=1:1:N {
  set test = ##class(Test.Test).%New()
  set test.ID = I
  do test.%Save() //create a "Exclusive_e->Delock" lock on ^Test.TestD(..)
}
hang 5
tcommit //locks are removed here

Test.Test is a persistent class that inherit from %Persistent :

Class Test.Test Extends %Persistent
{
   Property ID As %Integer [ Required ];
   Index IDKEY On ID [ IdKey ];
   //...      
}

I think delocks are needed because of the transaction, to maintain data consistency, but why exactly are they needed here ?

Is there some options that can be used to avoid the delocks ? (even if data consistency is lost).

The following code (that does not use persistent classes) will also create nodes in a global, but without creating any locks :

tstart
for I=1:1:N {
 set ^TEST(I) = $lb("", "123")
}
hang 5
tcommit 

EDIT :
What I tried so far (found in page suggested by Vic Sun)  :

1) adding this parameter in persistent class definition :

Parameter DEFAULTCONCURRENCY = 0;

=> does not seems to have any effect (delocks are still created after calling %Save() method)

This make sense because that method seems to be only used for methods like %Open() or %Delete().

2) Calling this method before transaction is created :

do $system.OBJ.SetConcurrencyMode(0)

=> works, but AFAIK this is disabling concurrency locking for the whole process (which is not what I want).

Product version: IRIS 2021.1
$ZV: IRIS for Windows (x86-64) 2021.1 (Build 215U) Wed Jun 9 2021 09:39:22 EDT
Discussion (6)1
Log in or sign up to continue

Thanks. I will take a look at it. I have some issues on a server where too many "delocks" are created because a lot of persistent objects are created in a long term transaction. I would like to avoid refactoring all the code to bypass persistent objects and use globals directly (which will require a lot of work).

The global delock means the global will stay locked until the top most transaction completes (tcommit or trollback), this is to protect against another process updating the global (assuming it uses locks) whilst your load is running.

You can effectively achieve the same with your own lock instead of the top level tstart

lock +^TEST 
for I=1:1:N { 
    set ^TEST(I) = $lb("", "123") 
    
} 
hang 5 
lock -^TEST

Depending on your requirements, a timeout and lock failure handling might be needed

It's possible to change (and thus disable) concurrency of a persistent object by calling this private method (eg: in constructor) :

Method %OnNew() As %Status
{
    do ..%ConcurrencySet(0) // <--- here
    quit $$$OK
}

Disclaimer : this is an undocumented method. It might be removed from IRIS implementation in the future, use it at your own risk.

The parameter of that method accept same values as what is returned by $system.OBJ.GetConcurrencyMode().
Once is concurrency is disabled, updates from other processes to related global can occurs even within transactions (data consistency is lost). A possible workaround is to create a single lock on the global :

do ##class(Test.Test).%LockExtent()   //same as lock +^Test.TestD
//do something here 
//...
do ##class(Test.Test).%UnlockExtent() //same as lock -^Test.TestD

This prevent having many locks created during long duration transactions that create many persistent objects (but on the other side, it locks the whole global, not individual nodes).