rda-python-dsquasar 3.0.24__tar.gz → 3.0.25__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (20) hide show
  1. {rda_python_dsquasar-3.0.24/src/rda_python_dsquasar.egg-info → rda_python_dsquasar-3.0.25}/PKG-INFO +1 -1
  2. {rda_python_dsquasar-3.0.24 → rda_python_dsquasar-3.0.25}/pyproject.toml +1 -1
  3. {rda_python_dsquasar-3.0.24 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar/dsquasar.py +134 -41
  4. {rda_python_dsquasar-3.0.24 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar/dsquasar.usg +15 -12
  5. {rda_python_dsquasar-3.0.24 → rda_python_dsquasar-3.0.25/src/rda_python_dsquasar.egg-info}/PKG-INFO +1 -1
  6. {rda_python_dsquasar-3.0.24 → rda_python_dsquasar-3.0.25}/LICENSE +0 -0
  7. {rda_python_dsquasar-3.0.24 → rda_python_dsquasar-3.0.25}/MANIFEST.in +0 -0
  8. {rda_python_dsquasar-3.0.24 → rda_python_dsquasar-3.0.25}/README.md +0 -0
  9. {rda_python_dsquasar-3.0.24 → rda_python_dsquasar-3.0.25}/setup.cfg +0 -0
  10. {rda_python_dsquasar-3.0.24 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar/__init__.py +0 -0
  11. {rda_python_dsquasar-3.0.24 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar/ds_quasar.py +0 -0
  12. {rda_python_dsquasar-3.0.24 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar/dstacc.py +0 -0
  13. {rda_python_dsquasar-3.0.24 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar/taccrec.py +0 -0
  14. {rda_python_dsquasar-3.0.24 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar/tacctar.py +0 -0
  15. {rda_python_dsquasar-3.0.24 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar.egg-info/SOURCES.txt +0 -0
  16. {rda_python_dsquasar-3.0.24 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar.egg-info/dependency_links.txt +0 -0
  17. {rda_python_dsquasar-3.0.24 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar.egg-info/entry_points.txt +0 -0
  18. {rda_python_dsquasar-3.0.24 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar.egg-info/requires.txt +0 -0
  19. {rda_python_dsquasar-3.0.24 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar.egg-info/top_level.txt +0 -0
  20. {rda_python_dsquasar-3.0.24 → rda_python_dsquasar-3.0.25}/test/test_dsquasar.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_dsquasar
3
- Version: 3.0.24
3
+ Version: 3.0.25
4
4
  Summary: RDA Python package to backup and recover RDA data archives to and from GLOBUS Quasar backup server
5
5
  Author-email: Zaihua Ji <zji@ucar.edu>
6
6
  Project-URL: Homepage, https://github.com/NCAR/rda-python-dsquasar
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rda_python_dsquasar"
7
- version = "3.0.24"
7
+ version = "3.0.25"
8
8
  authors = [
9
9
  { name="Zaihua Ji", email="zji@ucar.edu" },
10
10
  ]
@@ -89,7 +89,12 @@ class DsQuasar(PgCMD, PgSplit):
89
89
  self.SUBLMTS = 2000 # file count limit for a sub-group
90
90
  # tarring (TARACT) forks one child per tar file, so its parallel unit is the tar
91
91
  # file: use a small per-process limit and a high cap to spread the work widely.
92
- self.MPTLIMIT = 200 # tar file count per batch process for tarring
92
+ self.MPTLIMIT = 200 # tar file count per batch process for tarring on Glade
93
+ # a tar file of an object store dataset has every member staged down from Boreas and
94
+ # checksummed first, so it takes many times longer than a Glade one: give it a
95
+ # process per far fewer tar files. this is the only way to speed up a backlog held
96
+ # in one dataset, since a second worker cannot share the dataset lock.
97
+ self.MPOLIMIT = 20 # tar file count per batch process for tarring on the object store
93
98
  self.MPTMAX = 12 # maximum number of batch processes for tarring
94
99
  # uploading (BCKACT) forks one child per BCKSIZE transfer batch (~BCKSIZE/TARSIZE
95
100
  # tar files each), so far fewer parallel units than tar files: use a large
@@ -107,8 +112,9 @@ class DsQuasar(PgCMD, PgSplit):
107
112
  # dscheck work done, which is action independent because dcount counts GDEX files
108
113
  # for -A 3 but tar files for -A 2/-A 4.
109
114
  self.MAXWORKERS = 2 # default maximum concurrent workers per command (-W)
110
- self.WORKGRACE = 2*3600 # 2 hours before a running job is judged on progress
111
- self.MINWPROJ = 0.9 # projected done fraction at the walltime that counts as finishing
115
+ self.WORKGRACE = 3600 # 1 hour before a running job is judged on progress
116
+ self.GATHGRACE = 2*3600 # 2 hours before a job with nothing done yet counts as stalled
117
+ self.MINWPROJ = 1.5 # projected done fraction at the walltime that counts as finishing
112
118
  self.PGBACK = {
113
119
  'workdir' : "{}/{}/quasar_backup".format(self.PGLOG['GDEXWORK'], self.PGLOG['COMMONUSER']),
114
120
  'mproc' : 1,
@@ -202,6 +208,7 @@ class DsQuasar(PgCMD, PgSplit):
202
208
  # an even worker slot walks the datasets from the other end so that it and the odd
203
209
  # slot before it meet in the middle instead of fighting over the same dataset locks
204
210
  if self.reverse_dsids(): self.dsids.reverse()
211
+ self.order_dsids()
205
212
  self.PGBACK['cmd'] = "dsquasar {}".format(' '.join(argv))
206
213
 
207
214
  # do quasar backup process
@@ -275,6 +282,18 @@ class DsQuasar(PgCMD, PgSplit):
275
282
  dsid = self.format_dataset_id(dsid)
276
283
  if dsid not in self.dsids: self.dsids.append(dsid)
277
284
 
285
+ # put the -t datasets on the object store first for an odd worker slot and the ones on
286
+ # Glade first for an even one, keeping the given (or worker-reversed) order within each
287
+ # class; see dsid_order for why
288
+ def order_dsids(self):
289
+ if len(self.dsids) < 2: return
290
+ dcnd = "locflag = 'O' AND dsid IN ('{}')".format("', '".join(self.dsids))
291
+ pgrecs = self.pgmget('dataset', 'dsid', dcnd, self.LGWNEX)
292
+ if not pgrecs: return
293
+ odsids = set(pgrecs['dsid'])
294
+ reverse = self.reverse_dsids()
295
+ self.dsids.sort(key = lambda dsid: (dsid in odsids) == reverse)
296
+
278
297
  # Check backup and file information and dump the backup statistics#
279
298
  def dump_statistics_action(self):
280
299
  self.start_dsquasar_none_daemon(self.STTACT)
@@ -375,7 +394,8 @@ class DsQuasar(PgCMD, PgSplit):
375
394
  # file (one child per tar): new infiles CINACT will create this run (estimated from
376
395
  # the total bytes of GDEX files ready to back up, ~one tar per TARSIZE) plus existing
377
396
  # status 'N' infile records to build (TARACT), scaled one process per MPTLIMIT tar
378
- # files, capped at MPTMAX. uploading's unit is the BCKSIZE transfer batch (one child
397
+ # files on Glade or one per MPOLIMIT tar files on the object store, the two summed and
398
+ # capped at MPTMAX. uploading's unit is the BCKSIZE transfer batch (one child
379
399
  # per ~BCKSIZE/TARSIZE tar files): existing status 'T' tar records to transfer
380
400
  # (BCKACT), scaled one process per MPBLIMIT tar files, capped at MPBMAX. the phases
381
401
  # run sequentially in one run and share a single reserved ncpus, so return the max of
@@ -383,16 +403,34 @@ class DsQuasar(PgCMD, PgSplit):
383
403
  # never reaches here and stays single process.
384
404
  def batch_process_count(self, acts):
385
405
  mproc = 1
386
- tcnt = 0
406
+ gcnt = ocnt = 0 # tar files on Glade and on the object store
407
+ # an extra worker cannot touch the datasets another live process holds, typically the
408
+ # big object store dataset worker 1 is on, so it leaves them out of its sizing as its
409
+ # real gather will; counting them reserved worker 1's cpus again for the Glade work
410
+ # left. the gather's unlock pass skips a live holder and frees a dead one's lock.
411
+ # worker 1 keeps counting everything: a held dataset is usually released well within
412
+ # its 24 hours, and checking each holder costs an rdaps call per dataset
413
+ extra = self.PGBACK['worker'] > 1
387
414
  if acts&self.CINACT:
388
- sizes = [0]
389
- if self.gather_dataset_files(None, False, sizes):
390
- tcnt += max(1, (sizes[0] + self.TARSIZE - 1)//self.TARSIZE)
391
- if acts&self.TARACT: tcnt += self.batch_tar_count('N')
392
- if tcnt > self.MPTLIMIT:
393
- tproc = (tcnt + self.MPTLIMIT - 1)//self.MPTLIMIT
394
- if tproc > self.MPTMAX: tproc = self.MPTMAX
395
- if tproc > mproc: mproc = tproc
415
+ sizes = [0, 0] # bytes on Glade and on the object store
416
+ if self.gather_dataset_files(None, extra, sizes):
417
+ gcnt += (sizes[0] + self.TARSIZE - 1)//self.TARSIZE
418
+ ocnt += (sizes[1] + self.TARSIZE - 1)//self.TARSIZE
419
+ if not (gcnt or ocnt): gcnt = 1
420
+ if acts&self.TARACT:
421
+ # a dataset lock left by a dead process also drops out here, though the gather
422
+ # would free it; that undercounts by a stale lock's infiles at most
423
+ xcnd = " AND dsid NOT IN (SELECT dsid FROM dataset WHERE pid > 0)" if extra else ''
424
+ ncnt = self.batch_tar_count('N', None, xcnd)
425
+ oncnt = self.batch_tar_count('N', 'O', xcnd) if ncnt else 0
426
+ gcnt += ncnt - oncnt
427
+ ocnt += oncnt
428
+ # ceil(gcnt/MPTLIMIT + ocnt/MPOLIMIT): one process per MPTLIMIT Glade tar files plus
429
+ # one per MPOLIMIT object store tar files, rounded up once over the sum
430
+ tunit = self.MPTLIMIT*self.MPOLIMIT
431
+ tproc = (gcnt*self.MPOLIMIT + ocnt*self.MPTLIMIT + tunit - 1)//tunit
432
+ if tproc > self.MPTMAX: tproc = self.MPTMAX
433
+ if tproc > mproc: mproc = tproc
396
434
  if acts&self.BCKACT:
397
435
  bcnt = self.batch_tar_count('T')
398
436
  if bcnt > self.MPBLIMIT:
@@ -401,10 +439,25 @@ class DsQuasar(PgCMD, PgSplit):
401
439
  if bproc > mproc: mproc = bproc
402
440
  return mproc
403
441
 
404
- # count the bfile tar records of a given status honoring backflag/dataset scope
405
- def batch_tar_count(self, status):
442
+ # the -L scope of a bfile condition, or the scope of the given locflag. bfile has no
443
+ # locflag, so a record is matched through its dataset with the same "O" / "not O" split
444
+ # gather_dataset_files uses, keeping the two halves exhaustive. without it an -L O and
445
+ # an -L G run that overlap each tar, transfer and report the tar records of both halves
446
+ def bfile_locflag_condition(self, locflag = None):
447
+ if not locflag: locflag = self.PGBACK['locflag']
448
+ if not locflag: return ''
449
+ lnot = '' if locflag == 'O' else 'NOT '
450
+ return " AND dsid {}IN (SELECT dsid FROM dataset WHERE locflag = 'O')".format(lnot)
451
+
452
+ # count the bfile tar records of a given status honoring backflag/dataset/locflag scope;
453
+ # a locflag given narrows the count further to that half, inside any -L scope, and xcnd
454
+ # is any extra condition to AND on
455
+ def batch_tar_count(self, status, locflag = None, xcnd = ''):
406
456
  bcnd = "status = '{}'".format(status)
407
457
  if self.PGBACK['backflag']: bcnd += " AND type = '{}'".format(self.PGBACK['backflag'])
458
+ bcnd += self.bfile_locflag_condition()
459
+ if locflag: bcnd += self.bfile_locflag_condition(locflag)
460
+ bcnd += xcnd
408
461
  if self.dsids:
409
462
  tcnt = 0
410
463
  for dsid in self.dsids:
@@ -433,11 +486,16 @@ class DsQuasar(PgCMD, PgSplit):
433
486
  dargv = dargv[0:100]
434
487
  return self.get_dscheck("dsquasar", dargv, self.PGBACK['workdir'], self.PGLOG['CURUID'], dargx, self.LOGWRN)
435
488
 
436
- # a running batch job counts as stalled once it has run longer than WORKGRACE, has
437
- # recorded some work done, and its progress so far projects to less than MINWPROJ of
438
- # its recorded work by the walltime:
439
- # such a job cannot finish this attempt however long it is left alone, so an extra
440
- # worker is what helps it, not more retries.
489
+ # a running batch job counts as stalled once it has run longer than WORKGRACE and its
490
+ # progress so far projects to less than MINWPROJ of its recorded work by the walltime,
491
+ # or once it has run longer than GATHGRACE with nothing recorded done at all:
492
+ # such a job cannot finish this attempt comfortably however long it is left alone, so an
493
+ # extra worker is what helps it, not more retries.
494
+ # MINWPROJ is above 1 on purpose: the first worker starts on the slow object store
495
+ # datasets and an extra one on the fast Glade ones (see dsid_order), so a second worker
496
+ # is wanted whenever the first is not well on its way to finishing, not only when it is
497
+ # sure to miss the walltime. 1.5 leaves alone only a job on pace to finish within about
498
+ # two thirds of the walltime.
441
499
  # the test is on the RATE, not on an absolute done fraction as it first was. a fixed
442
500
  # 1% threshold only catches a job that is nearly frozen, and reads a job that needs
443
501
  # weeks as progressing: the real case was 2% done after 13H35M of a 24 hour walltime,
@@ -450,15 +508,19 @@ class DsQuasar(PgCMD, PgSplit):
450
508
  # runs this and set_walltime_deadline reads the granted walltime back in the batch job.
451
509
  # when the daemon caps it to a shorter queue limit the projection is optimistic, which
452
510
  # errs towards leaving the job alone.
453
- # a job that has recorded nothing done is still in its gather phases, which read as a
454
- # zero rate; it is left for the next submit rather than judged. that guard is what lets
455
- # WORKGRACE stay short: an extra worker can only be added by a cron submit, every 4
456
- # hours, so a 6 hour grace kept it out of the first 6 to 10 hours of every attempt.
511
+ # a job that has recorded nothing done may still be in its gather phases, which read as
512
+ # a zero rate, so it gets the longer GATHGRACE before it is judged. but a job that has
513
+ # recorded nothing after that long, typically one downloading its first object store
514
+ # dataset (dcount is only flushed every pstep files), is the slowest case of all and
515
+ # must not be left alone forever. the graces stay short because an extra worker can
516
+ # only be added by a cron submit, every 4 hours: a 6 hour grace kept it out of the first
517
+ # 6 to 10 hours of every attempt.
457
518
  def worker_progress_stalled(self, pgrec):
458
519
  if not (pgrec['pid'] and pgrec['stttime']): return False
459
520
  if self.check_host_pid(pgrec['lockhost'], pgrec['pid']) <= 0: return False
460
521
  elapsed = tm() - pgrec['stttime']
461
- if elapsed < self.WORKGRACE or not pgrec['dcount']: return False
522
+ if elapsed < self.WORKGRACE: return False
523
+ if not pgrec['dcount']: return elapsed >= self.GATHGRACE
462
524
  done = pgrec['dcount']/pgrec['fcount'] if pgrec['fcount'] else 0
463
525
  return (done*self.WALLTIME/elapsed) < self.MINWPROJ
464
526
 
@@ -609,7 +671,7 @@ class DsQuasar(PgCMD, PgSplit):
609
671
  self.check_batch_deadline()
610
672
  if len(qinfo['dsids']) == self.DSCNT: self.process_one_backup_file(qinfo, True, False)
611
673
  if self.PGBACK['dolock'] and dsid not in qinfo['dslocks']:
612
- if self.lock_dataset(dsid, 1, self.LOGERR) < 1: continue
674
+ if self.lock_work_dataset(dsid) < 1: continue
613
675
  qinfo['dslocks'].append(dsid)
614
676
  if not self.regather_dataset_files(bfiles, dsid, backflag):
615
677
  self.lock_dataset(dsid, 0, self.LGEREX)
@@ -675,12 +737,27 @@ class DsQuasar(PgCMD, PgSplit):
675
737
  # run, including the primary one, are left alone. returns the datasets locked here, to be
676
738
  # unlocked once the tar file is dispatched, or None if one of them is held by another
677
739
  # worker, in which case nothing new is left locked and the tar file must wait for a later run
740
+ # lock a dataset before working on it. a dataset held by another live process is the
741
+ # normal case once a second worker or the other backup action runs, and the library
742
+ # wording for it ("Locked by N/PBS: Cannot Relock") was logged as an ERROR, which counted
743
+ # in the error count and the error email although the dataset is just skipped. lock
744
+ # quietly and report the skip as information instead, naming the holder; a database
745
+ # error is still logged as an error by lock_dataset. returns what lock_dataset returns:
746
+ # 1 locked, -1 held by another process, 0 no dataset record or a database error
747
+ def lock_work_dataset(self, dsid, logact = None):
748
+ lstat = self.lock_dataset(dsid, 1, 0)
749
+ if lstat < 0:
750
+ pgrec = self.pgget('dataset', 'pid, lockhost', "dsid = '{}'".format(dsid), self.LOGERR)
751
+ holder = "{}/{}".format(pgrec['pid'], pgrec['lockhost']) if pgrec else 'another process'
752
+ self.pglog("{}: skipped, locked by {}".format(dsid, holder), self.LOGACT if logact is None else logact)
753
+ return lstat
754
+
678
755
  def lock_backup_datasets(self, qinfo, dsids):
679
756
  dslocks = []
680
757
  if not self.PGBACK['dolock']: return dslocks
681
758
  for dsid in dsids:
682
759
  if dsid in qinfo['dslocks']: continue
683
- if self.lock_dataset(dsid, 1, self.LOGERR) < 1:
760
+ if self.lock_work_dataset(dsid, self.DTLACT) < 1:
684
761
  self.unlock_backup_datasets(qinfo, dslocks)
685
762
  return None
686
763
  qinfo['dslocks'].append(dsid)
@@ -720,7 +797,7 @@ class DsQuasar(PgCMD, PgSplit):
720
797
  'qsize' : 0, 'qcnt' : 0}
721
798
  for dsid in bfiles:
722
799
  if self.PGBACK['dolock']:
723
- if self.lock_dataset(dsid, 1, self.LOGERR) < 1: continue
800
+ if self.lock_work_dataset(dsid) < 1: continue
724
801
  qinfo['dslocks'].append(dsid)
725
802
  for bid in bfiles[dsid]:
726
803
  self.check_batch_deadline()
@@ -1497,11 +1574,20 @@ class DsQuasar(PgCMD, PgSplit):
1497
1574
  if self.pgget_wfile(dsid, 'wid', "bid " + bcnd, self.LGWNEX): fopt |= self.WOPT
1498
1575
  return fopt
1499
1576
 
1500
- # dataset ordering for gathering work; even worker slots start from the high end (d999999)
1501
- # and odd ones from the low end, so that a pair of workers meets in the middle instead of
1502
- # fighting over the same dataset locks
1503
- def dsid_order(self):
1504
- return " ORDER BY dsid DESC" if self.reverse_dsids() else " ORDER BY dsid"
1577
+ # dataset ordering for gathering work. an odd worker slot starts on the datasets on the
1578
+ # object store and an even one on the datasets on Glade: an Object(O) dataset has every
1579
+ # file staged down from Boreas and checksummed before it can be tarred, while a Glade
1580
+ # one is only stat'ed, so the slow downloads get the most time from the first worker
1581
+ # while an added second worker clears the fast Glade work. an even slot walks the exact
1582
+ # reverse of an odd one (Glade from d999999 down, then the object store from d999999
1583
+ # down), so that a pair of workers meets in the middle instead of fighting over the same
1584
+ # dataset locks. dataset.locflag is matched as "O" / "not O" (a NULL locflag counts as
1585
+ # Glade), the same split -L uses. bfile has no locflag, so its records are matched
1586
+ # through their dataset
1587
+ def dsid_order(self, bfile = False):
1588
+ ocnd = "dsid IN (SELECT dsid FROM dataset WHERE locflag = 'O')" if bfile else "locflag = 'O'"
1589
+ dord = " DESC" if self.reverse_dsids() else ""
1590
+ return " ORDER BY CASE WHEN {} THEN 0 ELSE 1 END{}, dsid{}".format(ocnd, dord, dord)
1505
1591
 
1506
1592
  # whether this worker slot walks the datasets from the high end
1507
1593
  def reverse_dsids(self):
@@ -1512,11 +1598,9 @@ class DsQuasar(PgCMD, PgSplit):
1512
1598
  fcnt = 0
1513
1599
  # warn per named dataset with no files; stay silent when scanning all datasets
1514
1600
  logact = self.LOGWRN if self.dsids else 0
1515
- flds = "dsid, backflag, pid, lockhost"
1516
- # -L splits the work by where the data sits: an Object(O) dataset has to be staged
1517
- # down from the object store and checksummed before it can be tarred, while a Glade(G)
1518
- # one is only stat'ed, so the two classes run at wildly different rates and are better
1519
- # off in separate runs than sharing one walltime.
1601
+ flds = "dsid, backflag, locflag, pid, lockhost"
1602
+ # -L limits the run to where the data sits. it is optional: without it an odd worker
1603
+ # slot starts on the object store datasets and an even one on Glade (see dsid_order).
1520
1604
  # G is matched as "not O" rather than "= G" on purpose: dataset.locflag is only a
1521
1605
  # summary of the per-file flags (set_dataset_locflag writes it only when every public
1522
1606
  # web file agrees, and leaves the old value otherwise), so it can hold a stale value.
@@ -1557,7 +1641,14 @@ class DsQuasar(PgCMD, PgSplit):
1557
1641
  if lstat < 0:
1558
1642
  self.pglog("{}: skipped, still in use by {}/{}".format(dsid, pgrec['pid'], pgrec['lockhost']), self.LOGACT)
1559
1643
  continue
1560
- fcnt += self.get_dataset_files(dsid, dsfiles, pgrec['backflag'], logact, sizes)
1644
+ if sizes is None:
1645
+ fcnt += self.get_dataset_files(dsid, dsfiles, pgrec['backflag'], logact)
1646
+ else:
1647
+ # sizes holds the bytes ready on Glade and on the object store apart, since a
1648
+ # tar file of each takes a very different time to build (see MPOLIMIT)
1649
+ dsizes = [0]
1650
+ fcnt += self.get_dataset_files(dsid, dsfiles, pgrec['backflag'], logact, dsizes)
1651
+ sizes[1 if pgrec['locflag'] == 'O' else 0] += dsizes[0]
1561
1652
  if dsfiles:
1562
1653
  s = 's' if fcnt > 1 else ''
1563
1654
  bmsg = self.BACKMSG[self.PGBACK['backflag']] if self.PGBACK['backflag'] else 'backup'
@@ -1571,10 +1662,11 @@ class DsQuasar(PgCMD, PgSplit):
1571
1662
  bcnd = "status = 'N'"
1572
1663
  flds = "bid, dsid, bfile, type, data_size size, (scount + wcount) fcnt, dsids, note"
1573
1664
  if self.PGBACK['backflag']: bcnd += " AND type = '{}'".format(self.PGBACK['backflag'])
1665
+ bcnd += self.bfile_locflag_condition()
1574
1666
  if self.dsids:
1575
1667
  cnds = ["dsid = '{}' AND {}".format(dsid, bcnd) for dsid in self.dsids]
1576
1668
  else:
1577
- cnds = [bcnd + self.dsid_order()]
1669
+ cnds = [bcnd + self.dsid_order(True)]
1578
1670
  for dcnd in cnds:
1579
1671
  pgrecs = self.pgmget("bfile", flds, dcnd, self.LGWNEX)
1580
1672
  bcnt = len(pgrecs['bid']) if pgrecs else 0
@@ -1597,10 +1689,11 @@ class DsQuasar(PgCMD, PgSplit):
1597
1689
  bcnd = "status = 'T'"
1598
1690
  flds = "bid, dsid, bfile, type, data_size size, (scount + wcount) fcnt, note"
1599
1691
  if self.PGBACK['backflag']: bcnd += " AND type = '{}'".format(self.PGBACK['backflag'])
1692
+ bcnd += self.bfile_locflag_condition()
1600
1693
  if self.dsids:
1601
1694
  cnds = ["dsid = '{}' AND {}".format(dsid, bcnd) for dsid in self.dsids]
1602
1695
  else:
1603
- cnds = [bcnd + self.dsid_order()]
1696
+ cnds = [bcnd + self.dsid_order(True)]
1604
1697
  for dcnd in cnds:
1605
1698
  pgrecs = self.pgmget("bfile", flds, dcnd, self.LGWNEX)
1606
1699
  bcnt = len(pgrecs['bid']) if pgrecs else 0
@@ -29,10 +29,10 @@
29
29
  store, G takes everything else. Applies to -a and to -t alike. The
30
30
  two classes back up at very different rates: an O dataset has every
31
31
  file staged down from the object store and checksummed before it can
32
- be tarred, while a G dataset is only stat'ed. Running them in one job
33
- lets the slow class eat the whole walltime, so give them separate
34
- runs, e.g. one submit with -L G and another with -L O. Defaults to
35
- all datasets.
32
+ be tarred, while a G dataset is only stat'ed. -L is optional: without
33
+ it the first worker (and a run without -w) starts on the O datasets,
34
+ so the slow downloads get the most time, and a second worker (-w 2)
35
+ starts on the G datasets. Defaults to all datasets.
36
36
 
37
37
  -L G and -L O together always cover every dataset, so a pair of runs
38
38
  loses nothing. The split is by dataset, though, and dataset.locflag
@@ -97,9 +97,11 @@
97
97
  -m ProcessCount
98
98
  Number of concurrent child processes. Defaults to 1. When -m is not
99
99
  given, a delayed backup run sizes the count from the number of tar
100
- files it expects to handle and reserves matching PBS cpus, so in delay
101
- mode the process count normally travels through the reserved cpus
102
- rather than the command line. An explicit -m overrides that sizing,
100
+ files it expects to handle (one process per 200 tar files on Glade
101
+ and per 20 on the object store, whose files are downloaded first, up
102
+ to 12) and reserves matching PBS cpus, so in delay mode the process
103
+ count normally travels through the reserved cpus rather than the
104
+ command line. An explicit -m overrides that sizing,
103
105
  and -m 1 forces a single process.
104
106
 
105
107
  -n Gather and show the available file counts only; no backup actions are
@@ -108,11 +110,12 @@
108
110
  -W MaxWorkers
109
111
  Maximum number of concurrent batch workers for the same command.
110
112
  Defaults to 2. A repeat delayed submit normally blocks while the
111
- batch job is running; if that job has run for more than 2 hours,
112
- has recorded some work done, and at its rate so far would finish
113
- less than 90% of its recorded work within the walltime, it is
114
- treated as stalled and an extra worker is submitted instead, up to
115
- this limit. Extra workers rely on dataset locking to stay off each
113
+ batch job is running; if that job has run for more than 1 hour and
114
+ at its rate so far would not finish its recorded work within about
115
+ two thirds of the walltime (less than 150% of it by the walltime),
116
+ or has run for more than 2 hours with nothing recorded done yet, it
117
+ is treated as stalled and an extra worker is submitted instead, up
118
+ to this limit. Extra workers rely on dataset locking to stay off each
116
119
  other's files, so this is pinned to 1 with -l N, and also for the
117
120
  transfer action (-A 4, 6, 7), which claims tar files without locking.
118
121
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_dsquasar
3
- Version: 3.0.24
3
+ Version: 3.0.25
4
4
  Summary: RDA Python package to backup and recover RDA data archives to and from GLOBUS Quasar backup server
5
5
  Author-email: Zaihua Ji <zji@ucar.edu>
6
6
  Project-URL: Homepage, https://github.com/NCAR/rda-python-dsquasar