How to replace the CACHE.DAT of a mirrored database (i.e: when you need to upgrade a software version)

Context: 

mirrored configuration with one primary member and one async member (without failover/backup member)

Purpose:

replace the CACHE.DAT of a mirrored database on the primary member.

 Steps to follow:

  1. on the PRIMARY
    1. remove the database to replace from the mirror
    2. dismount the database
    3. copy the new database
    4. mount the database
    5. add the database to the mirror
    6. backup the database (external backup)
  2. on the ASYNC
    1. remove the database to replace from the mirror
    2. dismount the database
    3. restore the new database backup from the PRIMARY
    4. mount the database
    5. activate database
    6. catch up database

The following script use the same update() method on both PRIMARY and ASYNC members with 2 parameters:

  • when executed on the PRIMARY member:
    1. the first parameter = the directory containing the database to be updated on the PRIMARY
    2. the second parameter = the directory containing the new database 

USER>set db="d:/databases/application"
 
USER>set newDB="t:/newVersion"
 
USER>w ##class(install.tools).update(db,newDB)
(1) removing d:\databases\application from MYMIRROR .../...
[USER] d:\databases\application successfully removed from MYMIRROR
(2) dismounting d:\databases\application .../...
[USER] d:\databases\application successfully dismounted
(3) copying t:\newVersion\user to d:\databases\application .../...
[USER] t:\newVersion\user successfully copied in d:\databases\application
(4) mounting d:\databases\application .../...
[USER] d:\databases\application successfully mounted
(5) adding d:\databases\application to MYMIRROR .../...
[USER] database d:\databases\application successfully added to mirror MYMIRROR
NOW YOU HAVE TO BACKUP THE FOLLOWING DATABASE:
d:\databases\application
1
  • when executed on the ASYNC member:
    1. the first parameter = the directory containing the database to be updated on the ASYNC
    2. the second parameter = the directory containing the backup of the database (newly updated on the PRIMARY)

USER>set db="d:/databases/application"
 
USER>set backupDB="d:\backup\databases\application"
 
USER>w ##class(install.tools).update(db,backupDB)
(1) removing d:\databases\application from MYMIRROR .../...
[USER] d:\databases\application successfully removed from MYMIRROR
(2) dismounting d:\databases\application .../...
[USER] d:\databases\application successfully dismounted
(3) copying d:\backup\databases\application to d:\databases\application .../...
[USER] d:\databases\application successfully copied in d:\databases\application
(4) mounting d:\databases\application .../...
[USER] d:\databases\application successfully mounted
(5) activating d:\databases\application to MYMIRROR .../...
[USER] database d:\databases\application successfully activated in mirror MYMIRROR
(6) catching up d:\databases\application .../...
[USER] d:\databases\application database has been caught up successfully
1

Script:

ClassMethod update(database As %String, newdatabase As %String, verbose As %Boolean = 1) As %Status
{
  set ns=$namespace,sc=$$$OK
  set mirrorName=$system.Mirror.MirrorName()
  set:mirrorName="" mirrorName="MYMIRROR"
  set preMsg="["_$namespace_"] "
  try {
    zn "%sys"
    set:$system.Version.GetOS()="Windows" database=$replace(database,"/","\"),newdatabase=$replace(newdatabase,"/","\")
    set:$system.Version.GetOS()="UNIX" database=$replace(database,"\","/"),newdatabase=$replace(newdatabase,"\","/")
    
    // check CACHE.DAT files
    if '##class(%File).Exists(database_"/CACHE.DAT") || '##class(%File).Exists(newdatabase_"/CACHE.DAT") {
      set msg="the 2 CACHE.DAT of "_database_" and "_newdatabase_" not found - update not possible "
      set sc=$system.Status.Error(5001,msg)
      write:verbose msg,!
      return sc
    }
    
    // remove database from mirror 
    write:verbose "("_$i(step)_") "_"removing "_database_" from "_mirrorName_" .../...",!
    set sc=##class(SYS.Mirror).RemoveMirroredDatabase(database)
    set:sc msg=preMsg_database_" successfully removed from "_mirrorName,severity=0
    set:'sc msg=preMsg_"Error while trying to remove "_database_" from "_mirrorName_" : "_$system.Status.GetErrorText(sc),severity=1
    do ##class(%SYS.System).WriteToConsoleLog(msg,1,severity)
    write:verbose msg,!
    
    // dismount database
    write:verbose "("_$i(step)_") "_"dismounting "_database_" .../...",!
    set sc=##class(SYS.Database).DismountDatabase(database)
    set:sc msg=preMsg_database_" successfully dismounted",severity=0
    set:'sc msg=preMsg_"Error while trying to dismount "_database_" : "_$system.Status.GetErrorText(sc),severity=1
    do ##class(%SYS.System).WriteToConsoleLog(msg,1,severity)
    write:verbose msg,!
    
    // replace database
    write:verbose "("_$i(step)_") "_"copying "_newdatabase_" to "_database_" .../...",!
    set:$system.Version.GetOS()="Windows" command="copy /Y "_newdatabase_"\CACHE.DAT "_database_"\CACHE.DAT"
    set:$system.Version.GetOS()="UNIX" command="cp -f "_newdatabase_"/CACHE.DAT "_database_"/CACHE.DAT"
    set sc=$zf(-1,command)
    set:sc=0 msg=preMsg_newdatabase_" successfully copied in "_database,severity=0
    set:'sc=0 msg=preMsg_"Error while trying to execute "_command_" : "_sc,severity=1
    do ##class(%SYS.System).WriteToConsoleLog(msg,1,severity)
    write:verbose msg,!
    
    // mount database
    write:verbose "("_$i(step)_") "_"mounting "_database_" .../...",!
    set sc=##class(SYS.Database).MountDatabase(database)
    set:sc msg=preMsg_database_" successfully mounted",severity=0
    set:'sc msg=preMsg_"Error while trying to mount "_database_" : "_$system.Status.GetErrorText(sc),severity=1
    do ##class(%SYS.System).WriteToConsoleLog(msg,1,severity)
    write:verbose msg,!
    
    // add database to mirror on PRIMARY
    if $system.Mirror.IsPrimary() {
      write:verbose "("_$i(step)_") "_"adding "_database_" to "_mirrorName_" .../...",!
      set sc=##class(SYS.Mirror).AddDatabase(database)
      set:sc msg=preMsg_"database "_database_" successfully added to mirror "_mirrorName,severity=0
      set:'sc msg=preMsg_"error while adding database "_database_" to mirror "_mirrorName_" : "_$system.Status.GetErrorText(sc),severity=1
      do ##class(%SYS.System).WriteToConsoleLog(msg,1,severity)
      write:verbose msg,!
      // message recalling to launch the external backup of database newly added to the mirror
      write:verbose "NOW YOU HAVE TO BACKUP THE FOLLOWING DATABASE:",!
      write:verbose database,!
    }
    
    // activate and catch up database on ASYNC
    if $system.Mirror.IsAsyncMember() {
      // activate database
      write:verbose "("_$i(step)_") "_"activating "_database_" to "_mirrorName_" .../...",!
      set sc=##class(SYS.Mirror).ActivateMirroredDatabase(database)
      set:sc msg=preMsg_"database "_database_" successfully activated in mirror "_mirrorName,severity=0
      set:'sc msg=preMsg_"error while activating database "_database_" in mirror "_mirrorName_" : "_$system.Status.GetErrorText(sc),severity=1
      do ##class(%SYS.System).WriteToConsoleLog(msg,1,severity)    
      write:verbose msg,!
      
      // catch up database
      set db="",SFNlist="",dbName=""    
      set db=##class(SYS.Database).%OpenId(database)
      if $IsObject(db) {
          set SFNlist=SFNlist_$lb(db.SFN)
          set dbName=dbName_$lb(db.Directory)
      }
      set err=""
      write:verbose "("_$i(step)_") "_"catching up "_database_" .../...",!
      set sc=##class(SYS.Mirror).CatchupDB(SFNlist,"",.err)
      if (sc)&&(err="") {
        set msg=preMsg_database_" database has been caught up successfully",severity=0
        do ##class(%SYS.System).WriteToConsoleLog(msg,1,severity)    
        write:verbose msg,!    
        
      } else {
        set catchupErr=""
        for i=1:1:$ll(err) {
          set msg=preMsg_"error while catching up database "_$lg(dbName,i)_"(SFN:"_$lg(err,i)_")",severity=1
          do ##class(%SYS.System).WriteToConsoleLog(msg,1,severity)    
          write:verbose msg,!
          set catchupErr=catchupErr_"("_i_"):"_msg
        }
        set:sc=0 sc=$system.Status.Error(5001,catchupErr)
      }
    }
   }
  catch ex {
    set msg=preMsg_"error while executing "_..%ClassName(1)_": upgrade : "_ex.DisplayString(),severity=1
    write:verbose msg,!
    do ##class(%SYS.System).WriteToConsoleLog(msg,1,severity)
    set sc=ex.AsStatus()
  }
  do ..checkRW(1,0)
  zn ns
  return sc
}
 

Comments

Thanks for sharing this. But some comments about code.

  • postconditionals is a very good part of our language, but here is too much, and quite difficult to read such code. In this case much better will be simple if else
  • return in try block is a bad idea, you should throw an error, which will be caught anyway
  • we can do new $namespace, but in your code, this method returns to wrong namespace, look at my previous point
  • instead of $replace(database,"/","\"), use ##class(%File).NormalizeDirectory(database), in this case you should not check on OS, even you forgot some of OS's, and in this case NormalizeDirectory/NormalizeFilename, a bit better.
  • verbose mode will be better to do with a macros, something like $$$DebugInfo("some message")
  • In your code I see so many checks on errors, and looks like, after such error we should exit, but you go further. You should look at some system macroses like $$$ThrowOnError() and so on, I think you should use it. And code will much more clear for readers
  • and at the end you have some call to method checkRW, I think you should remove it, or show this method, because it's not clear what it does

And, before publishing, please change tabs to spaces, because tabs here is too big. And width of lines should be a bit less, because read such long lines a bit difficult. 

I fixed tabs here, for more readability.

 

I have to disagree with your first point. Postconditionals actually increase the readability and add much less clutter to the code than having if/then/else blocks everywhere. 

Yes, but in this code, for me is too mach, and also, postconditionals should be quite short, like sc, and should not be more or close to length of real statement