Concurrency matters was Re: [InterSystems-MV] Deeper and deeper into no-code coding

20 views
Skip to first unread message

Dawn Wolthuis

unread,
Jan 6, 2012, 11:09:46 AM1/6/12
to intersy...@googlegroups.com
I'll admit that I have worked the "locking" issue much more from a practical standpoint than a theoretical one. We decided not to use locking (aka "pessimistic locking") and to work with "optimistic locking" aka no locking instead. Our top two priorities are

1) Consistency with some risk. We do not use database-level transactions above the level of a single record. So, if you need to roll-back a record based on a subsequent failure, you need to that "by hand." We design our schema and applications accordingly (e.g. don't delete a parent before the children in the app). I realize that statements like the last one raise the ire of some DBMS purists.

2) The user experience of having no locked records ever.

We use the automated record versioning from Cache'.  Most of our writes to date have been using Zen MVC. The %Save() compares the version of the object (record) in memory that you want to write to the persisted record and does not perform the write if these versions are different. It notifies us of the concurrency error instead.

If we get a concurrency error, we do nothing clever. We tell the user that rats, the data has changed since they looked at it, we show them the new data (which might not look different, depending on what changed in the record and what they are seeing) and they have to make their changes again and save again. If the user is a process, then we can either re-read the record and try again or log and skip it, depending on the circumstances.

We have run tests to confirm that both the persistence and the user experience are what we are looking for. So, nothing really clever here and some would consider the approach to be peasantry, but it gives a high degree of accuracy, is straight-forward, and seems tidy and maintainable.  Cheers!  --dawn

On Fri, Jan 6, 2012 at 9:21 AM, Ed Clark <edc...@aol.com> wrote:
It might be interesting to have some conversations on the list about how locking works on various systems, and on cache. I know that there are settings for concurrency in cache that affect both SQL and operations done by %Persistent.cls, but I'm not familiar enough with it to say if it can immediately emulate something like mv WRITEU lock retention.

Just throwing out some points:

The oldest locking mechanism in mv is the one used by the mvbasic LOCK and UNLOCK statements. If you want to control concurrency you set a lock:
 LOCK 45
before you access something (read an item, open a file) and then release it when you are done:
 UNLOCK 45
This is entirely optional and cooperative. Everything on your system has to agree to take the lock before doing something and release it after, and nothing prevents a rogue user from ignoring the locks. It's somewhat limited because you could only take locks on the 64 flags 0-64.

The low-level locking mechanism in cache is similar (see documentation for the object script lock command). It has a lot more options (incremental locks; shared locks) and allows you to lock "resources", but remains cooperative.

In Cache, the mvbasic LOCK and UNLOCK statements are implemented using the cache locking mechanism, so you can lock any number or string, not just 0-64. more flexible but still cooperative.

The multivalue world added record-based locking at some point, via READU/WRITEU. Some vendors also added shared locks via READL and file locks. These are higher-level than the LOCK locks, but are still cooperative (except on one platform where a write will hang if someone else hods a lock).

Cache mvbasic supports these high-level locks and uses the cache low-level lock mechanism to implement them, so locks can be used cooperatively between object script and mvbasic applications. For example:
 OPEN "VOC" TO VOCFP ELSE STOP 201,"VOC"
 READU ITEM FROM VOCFP,'BASIC' ELSE STOP 201,"BASIC"
takes out the same lock that the object script:
 LOCK ^MV.VOC("BASIC")
would.

Afaik, object script doesn't have an equivalent high-level mechanism. But cache does support concurrency in classes and sql. The %OpenId method had a "concurrency" parameter, and the documentation points to information about the concurrency levels. I don't know how these concurrency levels compare to what mv does with readu locks. I think locks are held during transactions though. Anyone want to comment?

On Jan 5, 2012, at 9:52 PM, Bill Farrell wrote:

> Thanks, Jason.  I think we got most of this covered.  I had a long conversation with Lee and that tidied up all but one of my questions (which I forgot to ask).
>
> After learning a little bit more about the %Save mechanism and how Cache treats locks, a bare-naked %Persistent or %MV.Adaptor class extent is nowhere near adequate.  Understand, that in MVBooster, there are *no* fatal errors.  Errors are reported, but what you do with them at the app level is your business.  The reason behind this is that MVBooster wasn't originally coded with applications in mind, although it has proven to be a great framework upon which to hang app code.  The original code base (carried forward) was built with two main goals in mind:  keeping TCP/IP communications and phantoms alive.  One simply cannot count on Cache (nor any MV system, either) alone to manage record locking and data integrity.  With phantom processes that handle TCP/IP communications, one can't do a READ/READU/READL bare.  If a lock is encountered, a phantom cannot "hang" forever because some idiot went to lunch halfway through a screen leaving records locked.  My framework allows a number of reactions: wait a limited amount of time, return immediately if the record can't be read right away, etc.  Phantoms and TCP/IP protocals cannot wait forever;  something's gonna break.  Rather than cause hangs or aborts *EVER*, errors are quietly returned in standard thread-global error variables.
>
> Any file class that extends MVFileAbstraction inherits careful Read(), ReadU(), ReadL(), ReadN() methods (and ReadV methods as well).
>
> Same goes the other way.  There are Write()/WriteU() (and value counterparts).  There are often good business reasons NOT to release a lock on a given WRITE.  That's what the Pick WRITEU was invented for.  I haven't yet heard %Save having the ability to emulate WRITEU.  While I wouldn't use that in a communications scenario, I have used it (albeit rarely) in applications.  The fact that it exists in the MV world was reason enough for me to include it in MVBooster.
>
> Everything I've read and heard today convinces me that neither Open nor %Save alone is going to accomplish the task at hand.  Lee helped me talk through the new SetProperty method until I spotted a mechanism (my duh, the error reporting mechanism already in MVBooster, handily) to catch errors thrown by any Set() method.  I'm rockin' now.
>
> Thanks again and best regards,
> Bill
>
>
>
> On Thu, 2012-01-05 at 17:08 -0700, Jason Warner wrote:
>> Bill,
>>
>> I've been following this conversation and some interesting things have come out of it. I will provide some comments from our system where we are pushing more towards object only and moving away from MV files.Now, the relevant remaining questions I have are narrowing down pretty fast:
>>> 1.  Who calls %OnBeforeSave?
>>
>> %Save and %OnBeforeSave are inherited from %Persistent. When you call %Save it goes through various steps before even getting to the point of writing to disk. One thing to mention is that if you need to massage the data in any way, %OnBeforeSave is the wrong place to do it. %OnAddToSaveSet is where you need to do things like that. From what I understand %Save calls %OnAddToSaveSet of the current object and all children first. Then, %OnBeforeSave then the actual save happens then %OnAfterSave is called. If any of the these return an error status, the entire transaction is rolled back so nothing winds up on disk.
>>
>>> 2.  Where is %Save getting its data from?  If I use my abstracted method someObject->Write( optionalKey ) I know that exactly one dynamic array will be written to exactly one file.  That's the effect I want, no more and no less.  If %Save follows Storage, then I've already got it made and needn't worry with the question any further.
>>
>> %Save gets its data from the properties on the object itself. In and object that inherits from %MV.Adaptor, you can also add additional attributes to the properties that tells how and where it is projected to disk. There is an attribute that you can add to a property that tells the compiler that this property will never be stored to disk (I can't remember what it is). There are other attributes that you can add that specify that a property can only be a certain length or even that it must follow a certain pattern. All of these attributes are checked when %Save is called and a status is returned detailing any violations.
>>
>>> 3.  If I use my nifty little SetProperty() method Ed got me started on to find and set a property value (that is, an attribute in the dynamic array held in thisObject->Record), who's checking the data type.  If the property's Set() method I call (by inference) rejects either the content or type thus returning an error %Status, where does that status go after the $Xecute?  Can I catch it and return it to the mainline that called the custom SetProperty() method?
>>
>> This is an interesting point I ran into when working with %Time variables from MV. If you set a property to time() then the value stored is actually a string. It is treated like a string until it is required to be anything but a string. The information I got from InterSystems at the time is that one of the ways that Cache gets better performance is to not cast anything until it needs to be what it needs to be. Since time() from the MV shell returns a string it stays a string until it has to be a %Time variable.
>>
>> One thing you can do is override the setters and getters for a property on an object to do the checking on the data if it needs to happen before your attributes are evaluated during the %Save routine. One thing to be aware of, is that in older versions of Cache, you can't access i%PropertyName (the internal storage variable for your property).
>>
>> I understand wanting to validate data in a green screen environment at the time it is entered since you control the user's interaction with your application at all times. However, if you are planning to move to the web in the future, you may want to avoid throwing errors when setting properties in favor of validating the data at save. You can't guarantee what order a user will enter data on the web and like the example mentioned earlier, they may know their zip and enter it, but have to look up their address and add it later.
>>
>>> 4.  If I set a property as [Required], where does that get caught and would I even need to set a property [Required] if I'm doing sanity checking in %OnBeforeSave()?InterSystems Corporation
>>>
>> [Required] is checked in the %Save function. I don't remember if this happens before or after the %OnBeforeSave, but I do know it happens after the %OnAddToSaveSet. If it was me, I would use [Required] on the property instead of checking in the %OnBeforeSave. It seems to show my intent much more clearly in the code and I feel like %OnBeforeSave is useful for checking more complex data. For example, we have a requirement not to store the unique identifiers for Canadian dealers in our data due to Canadian law. However, in the US, we do want and collect Tax IDs and the like. We have to do these checks in the %OnBeforeSave.
>>
>> As Michael Cohen brought up, the code for SQL and MV triggers are run from different entry points from the %Save data. If you decide to go the %Save route, you should probably do away with your MV triggers and move that code into your %AddToSaveSet, %OnBeforeSave or %OnAfterSave. If you try to keep MV triggers and %Save logic it can get very complicated very fast.
>>
>> Jason
>>
>>
>> --
>> You received this message because you are subscribed to the Google Groups "InterSystems: MV Community" group.
>> To post to this group, send email to Cac...@googlegroups.com
>> To unsubscribe from this group, send email to CacheMV-u...@googlegroups.com
>> For more options, visit this group at http://groups.google.com/group/CacheMV?hl=en
>
> --
> You received this message because you are subscribed to the Google Groups "InterSystems: MV Community" group.
> To post to this group, send email to Cac...@googlegroups.com
> To unsubscribe from this group, send email to CacheMV-u...@googlegroups.com
> For more options, visit this group at http://groups.google.com/group/CacheMV?hl=en

--
You received this message because you are subscribed to the Google Groups "InterSystems:  MV Community" group.
To post to this group, send email to Cac...@googlegroups.com
To unsubscribe from this group, send email to CacheMV-u...@googlegroups.com
For more options, visit this group at http://groups.google.com/group/CacheMV?hl=en



--
Dawn M. Wolthuis

Take and give some delight today

Bill Farrell

unread,
Jan 6, 2012, 11:25:19 AM1/6/12
to InterSy...@googlegroups.com
I'm grinning, Dawn, because I feel ya.  I designed my locking somewhat pessimistically figuring that Murphy will stick his big foot in it somewhere.  Rather than avoiding problems, I take them head on by using a READ statement that uses every possible phrase (LOCKED THEN...ON ERROR THEN... etc).  When things don't work out, I make sure the exact error is bubbled up to the app.  There are un-Pick-like options for Read() that allow attempts to gain locks plus an option to return immediately with a warning if you can't have a record because some fool went to lunch (AND pointing out which fool... and I have been that fool) but don't kill a process.  "Report, don't die".

Every site and software suite has its own needs.  While MVB may be overkill for most day-to-day apps, the error reporting, debugging and tracing facilities, and "don't die" theory are invaluable for communications and critical background tasks.  The beauty of MV is being able to choose the size that fits :)

B

Dawn Wolthuis

unread,
Jan 6, 2012, 2:06:46 PM1/6/12
to intersy...@googlegroups.com
I re-read what I wrote and thought it sounded a bit like I didn't care enough to do locking or multi-record transactions. I do not want to leave the impression that it is some combination of indifference (no way) and ignorance (always ;-) that prompts us to have some peasantry in our design -- we specifically designed it the way we have it based on our experiments, discussions with ISC, and requirements. There are trade-offs to every design for this, of course. [For example, now you hear the NoSQL crowd talking about "eventual consistency." ]

There are definitely applications where the best bang for the buck is to use record locking, particularly green-screen-only apps. When you are going to have a web interface for any database applications, however, then I think you will pull your hair out until you ditch the locking and go with concurrency checking like that which Cache' does for you when you use versions. Unless you want to trap your application into specific interfaces, most new business applications will be better served by "optimistic locking" (aka no locking) at this point, I think.

Do you have a use case in an application that would not fare well with the approach we are taking? I do recognize there are some trade-offs made, but it an approach that has served us well to date. Application developers have been able to do what they want to do even though they are not using typical mv writes and are using %Save() or from MVC a savePlus() method instead. It works!

I'll admit that when I read what you are writing about this, I do not understand everything at first glance. You are definitely working at a level below where I want to work. I want to use the expertise of the database vendor and build on that, letting them know if the features do not meet our needs. So I am happy to use %Save() even though originally I wanted to use the typical mvbasic reads and writes. With %Save() we get automated versioning (which is way cool, if you ask me) along with autoincrement keys where specified (also very nice, no need to save next key values through our code and in our parm/config files -- they are part of the DBMS) plus everything else we would expect.

So, I am not yet groovin' with why %Save() does not do the job for you, but I also do not know your requirements and have not figured out everything you are saying here.  In any case, best wishes. I hope your approach serves you well. So far, we are happy with the no-locking approach we have taken.  --dawn

Bill Farrell

unread,
Jan 6, 2012, 5:41:35 PM1/6/12
to intersy...@googlegroups.com
Hi Dawn,

I certainly didn't take your reply to mean you didn't care, or didn't want to deal with locking.  (Maybe we're more in tune than we knew!)  I've had the same experience with web applications.  Trying to figure out a scheme that would satisfy all ends of locking cost what little hair I ever had.  (You've seen my pic, now you know what happened!)  What my framework provides me is the capability of choosing whether to set an update, intent, or no lock at all at any given instant.  I also added the capability to give up with an error message (warning) if a record is already locked without waiting for it to come free.

Web apps might benefit from the "sorry, someone else named Curly has the record you want; work on something else, Moe", but given your audience even that much might be too much.  I deal mostly in the nuts-and-bolts world fiddling with data transport and background processes.  I'd say the majority of time there, even, I'll choose the "don't wait if you don't have to" option.  I've cleaned up endless data after incautilous phantoms or protocol layers that break.  My intent is to have every possible option available at any given instant so I can choose the right tool for every particular instance the way a surgeon might choose one scalpel over another.  Or BFH (big...hammer).

I've also ported tons of code between UV and VB.  Having, mmm, a "lingua franca" style of coding makes ports go one heckofalot easier.  I see myself writing far, far less VB thanks to Cache's much more powerful tool set.  Still, if you peek under the covers of any of my code from the early 90s onward to today, you'll see the same intensely neat, very structured code whether you're looking at my VB or Pick code.  Some of that's from having to move stuff from system to system to system.  Some of it's because I've been sentenced to read my own code cold LOL

(More often than not, my VB does things in a MV sort-of way; particularly the way I look at and deal with MV data.  Once a Pickie...)

Writing and maintaining user-facing apps is usually a fair bit different to where I usually live.  I'll admit I detest doing apps, preferring the warm, dark cocoon of deep system internals.  Anything that speeds up development in any venue as far as I'm concerned is a Good Thing(tm).  No matter at which level I'm coding, I've got a time-tested set of tools to take a load off both development and testing.  This is even more important when I'm dealing with background processes.  Phantoms are notoriously hard to diagnose when things suddenly take a turn.  I have the option of turning on step-by-step tracing or code-entry tracing just by flipping a variable or two.  This is a handy capability when you can't shut a phantom down even for a few minutes.  It has been my experience that people NEVER clean out &PH& or &COMO&.  If there is a problem, yeahright, TRY and find it in there.  I have a standard SYSLOG into which daily logs (if any logging is turned on) go.  Those are automatically rotated on a daily basis and elderly logs (past the MVB configuration retention-days attribute) get quietly deleted.  If that's not enough, there's a fully-fledged email class (doesn't need a smart host, does MX resolution and deliver on its own).  If the MVB configuration has email addresses, a phantom can email any number of people with almost exactly what went wrong.  There is a "safe" subroutine calling method so if a background process calls Somebody Else's program (of course, errors never occur in our own :-) ), it won't kill the phantom if that Somebody left a variable unassigned.  The fact will be logged and additionally emails will be sent.

What's ta' know?  No-touch maintenance.  All pretty-much automagic.  It's working for me.  Anyone else's mileage will probably vary due to the differences in the kind of code we have to write on a day-to-day basis.

The upshot is, it doesn't matter to me whether I'm creating a mission-critical background task or a BP ditty, I've got one rapid-code, self-diagnosing platform that makes my life a lot easier.  (Ever try to diagnose a phantom remotely with users yelling they're not getting results and you have to start by figuring out IF there's a &PH& file or a &COMO&, then out of the 10,000 logs in either one, which is the one belonging to the current phantom you're diagnosing... AND when you find it, there's not a single clue in it?  Hate that!)  I also like having instant gratification when I run a user-facing app and hollers "Hey Stooput" at me, refusing to bonz data when I make a fat-finger.

Of course I type perfectly.  And I have a bridge for sale somewhere, heh...

There's one heckofalot to my framework.  I've used it for years and I trust it.  I have completely consistent code inside and outside that framework.  Using just everything in it may not be  right for everyone but it works well for me, keeps me and keeps mission-critical background tasks from mysterious death or from destroying data.  Even if you don't want to use the whole thing, there are gadgets in the Library class that might keep someone else from having to figure out how to write because I wrote it at some point already and just left it in the library.

Again, a perusal of the documatic would give a lot of insight into why I do things the way I do.  As I told Lee on the phone last night, it makes for good potty reading when all the magazines have gone stale, heh.

You have a great weekend!
B

Dawn Wolthuis

unread,
Jan 6, 2012, 5:55:12 PM1/6/12
to intersy...@googlegroups.com
I have no doubt I will learn a lot when I look at your libraries. I will have to be in the right place to do that (and I am not referring to your last statement).  --dawn

Bill Farrell

unread,
Jan 6, 2012, 5:59:31 PM1/6/12
to InterSy...@googlegroups.com
BWAH!

Yeah, I have to motivate myself for technical reading when I'd rather be immersing in some total escape Eddings.

B
Reply all
Reply to author
Forward
0 new messages