Sergey, it's great that you are writing articles for newbies, nevertheless you don't explicitly mark it. Just a quick note on your samples: ZWRITE command never returns <UNDEFINED> in IRIS, so to check the global existence one should use something like

 if '$data(^A) { write "Global ^A is UNDEFINED",! }

I'm sure that you are aware of it; just thinking of novices that should not be confused.

Stephen, agree with you, those %% can be nasty, while it's possible to avoid them in this very case:

..\bin\cache -s. -U"%SYS" <Routine/ClassMethod call>

making the command syntax very similar to Linux one. In most other cases %% are inevitable; take a look at a small brief from real CMD script: 

 call :CheckNameSpace %%%%SYS
 if %sc% equ 0 goto :help
 ...
:CheckNameSpace
 set sc=1
 If "%1" == "" goto :NameSpaceNotSet
 ...

After re-reading excellent articles referenced above, it seemed that:
1) Too low QoS value can be incompatible with VM Stun time.
2) Too high value can be inappropriate as well for some other reasons. E.g., it can postpone a failover when it's of real need when Primary crashed or isolated.
So, why not stop bothering about QoS value, and just Set No Failover during snapshot phase? Documentation describes how to do it manually, while it should be possible programmatically as well.

You may want to use some kind of print spooling to avoid the situations of monopolization such a device as printer. Take a look at CUPS; there is a brief notes how to use lp or lpr commands in Caché/IRIS: Using Pipes to Communicate with Processes.

On Windows we just used OS printer name for opening the device in Caché, and it was enough to spool the jobs to printer queue; no other tricks were needed.

To get more reliable figures on performance, we usually take the following approach (# 1):

 set nTop=1000000 // number of test repetitions
 for each test_j {
   init_counters
   for i=1:1:nTop {
      run_test_j
   }
   save_counters_for_test_j(nTop)
 }

because it introduces less extra payload and provides more precise measuring than alternative approach (#2):

 set nTop=1000000
 for i=1:1:nTop {
  for each test_j {
    init_counters
    run_test_j
    save_counters_for_test_j
  }
 }
 for each test_j {
   aggregate_counters_for_test_j(nTop)
 }

Jeffrey,

I'd try to define namespace global mapping, something like this:

[Map.YOURNSP]
...
Global_Ens*=YOURNSPDB,,YOURNSP-LOCKS
...

where

YOURNSP - your namespace
YOURNSPDB - database where ^Ens* globals are stored (mirrored one)
YOURNSP-LOCKS - database ^Ens* globals locks will be associated with (local one, RO or RW, it doesn't matter).

The similar definitions should exist for each global groups that can be locked.

Disclaimer: I didn't try this trick myself with mirrored DBs. It might help to bypass the locking problem, while not guarantee against other suprises one can face with running something on Mirror Backup member. Consulting with WRC might be of use... 

Hi Julius,

Your solution is beautiful; alas, this code

open jouFile:"WL":1 // try to get ownership

instantly returns $Test=1 in Caché for Linux as its behavior differs from Windows version in this case according to documentation:
Caché Development References  >  Caché I/O Device Guide  >  Sequential File I/O: OPEN Mode Locking

I've checked it in Cache for UNIX (Red Hat Enterprise Linux for x86-64) 2017.2.2 before writing here.

Here is my solution. A couple of words as a preface. There are two tasks:
#1

  • Switches journal and fixes the name of new journal file (e.g., in @..#GtrlJ@("JrnFirst")).
  • Processes the globals of a namespace. The algorithm of processing doesn't matter here, it's usually some kind of data re-coding. 

#2. This task occurred just because users' activity during the task #1 execution can introduce the changes in globals already processed by the task #1.

  • Wait for the next journal file available for processing (WaitForJrnSwitch());
  • Process the globals found in this journal using the algorithm similar to the task_#1's one.

The latter is a pseudo-code of WaitForJrnSwitch() method and GetJrnID(), its helper. 

 /// If new jrn is available, set %JrnID=Jrn ID and return 1;
/// waiting by ..#TimeWait steps till ..#TimeLimit
ClassMethod WaitForJrnSwitch() As %Boolean
{
 set rc=0
 set nTimes = ..#TimeLimit \ ..#TimeWait
 for i=1:1:nTimes {
  $$$TOE(sc, ..GetJrnID(.JrnID)) // current journal
  if %JrnID="" {
    set JrnNext=@..#GtrlJ@("JrnFirst")
  else {
    set JrnNext=%JrnID+1
  }
  if JrnNext<JrnID { // avoid extra journal switching ("by restore")
    set %JrnID=JrnNext
    set rc=1
    quit
  }  
  hang ..#TimeWait
 }
 quit rc
}


/// Get Jrn ID of the current journal file
/// Out:
/// returns %Status;
/// pJrnID - journal file name w/o prefix and "."
ClassMethod GetJrnID(Output pJrnID) As %Status
{
 set sc=1
 try {
   set file=##class(%File).GetFilename(##class(%SYS.Journal.System).GetCurrentFileName())
   set prefix=##class(%SYS.Journal.System).GetJournalFilePrefix() 
   set pJrnID=$tr($e(file,$l(prefix)+1,*),".")
 catch ex {
   set sc=$$$ERROR($s(ex.%IsA("%Exception.SystemException"):5002,'ex.Code:5002,1:ex.Code),$lg(ex.Data)_" "_ex.Location_" "_ex.Name)
 }
 quit sc
}

Hi Julius,

My solution is similar to yours while it is based on journal API only. If anybody is interested, I'll post it in a few days (being on vacations at the moment).

Thank you for reminding to check if the shutdown is in progress, while I am not sure how often my code should wake up to detect it for sure. Yours waking up each one second as I see.