Question Norman W. Freeman · 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) :

tstartfor 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 5tcommit//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 :

tstartfor I=1:1:N {
 set^TEST(I) = $lb("", "123")
}
hang5tcommit 

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

Comments

Vic Sun · Feb 8, 2023

Hi Norman,

I'm not sure I follow exactly what you are trying to do but I think the object concurrency options can provide what you are looking for. Yes - you can avoid locks even when using objects.

0
Norman W. Freeman  Feb 8, 2023 to Vic Sun

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).

0
Vic Sun  Feb 8, 2023 to Norman W. Freeman

Would it be possible to shrink your transaction? I think that would be preferable. If you aren't concerned about data consistency then what is the role of the transaction?

0
Norman W. Freeman  Feb 8, 2023 to Vic Sun

Transaction is created during import of data, so unless there is some serious optimization it's difficult to shrink time. Data consistency is important but it's might be OK to accept some tradeoffs. 

0
Matt Gage · Feb 8, 2023

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 +^TESTfor I=1:1:N { 
    set^TEST(I) = $lb("", "123") 
    
} 
hang5lock -^TEST

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

0
Norman W. Freeman · Feb 9, 2023

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) // <--- herequit$$$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).

0