carbon-a-database-explorer / build_snapshot.py
cgeorgiaw's picture
cgeorgiaw HF Staff
Publish full accession snapshot from bucket storage and synchronize atlas coverage
7034da5 verified
Raw History Blame Contribute Delete
3.07 kB
"""Build a full accession snapshot in resumable parallel partitions.
Run list_annotation_objects.py first. This command indexes the captured inventory,
merges all partitions, and leaves publication to the operator.
"""
import argparse
import json
from pathlib import Path
import sqlite3
import subprocess
import sys
ROOT=Path(__file__).resolve().parent
def main():
p=argparse.ArgumentParser(description=__doc__)
p.add_argument('--inventory',type=Path,required=True)
p.add_argument('--cache-dir',type=Path,default=ROOT/'.cache/full-index')
p.add_argument('--output',type=Path,default=ROOT/'data/catalog.sqlite')
p.add_argument('--parts',type=int,default=8)
p.add_argument('--workers-per-part',type=int,default=32)
args=p.parse_args()
if not 1<=args.parts<=32 or not 1<=args.workers_per_part<=128: p.error('Invalid concurrency')
work=args.cache_dir.resolve(); work.mkdir(parents=True,exist_ok=True)
config=work/'partition-config.json'
if config.exists() and json.loads(config.read_text())['parts']!=args.parts:
p.error('Use the same partition count when resuming, or a new cache directory.')
config.write_text(json.dumps({'parts':args.parts}))
items=[json.loads(s) for s in args.inventory.open()]
expected={x['path']:x['hash'] for x in items}
seen=set(); assigned=[[] for _ in range(args.parts)]
for i in range(args.parts):
done=work/f'part-{i}.sqlite'; checkpoint=work/f'part-{i}.building.sqlite'
if done.exists() and not checkpoint.exists(): done.rename(checkpoint)
if checkpoint.exists():
with sqlite3.connect(checkpoint) as c:
retained={r[0] for r in c.execute('SELECT path,hash FROM files') if expected.get(r[0])==r[1]}
if retained & seen: raise ValueError('Duplicate checkpoint objects')
assigned[i]=[x for x in items if x['path'] in retained]
seen.update(retained)
for n,item in enumerate(x for x in items if x['path'] not in seen):
assigned[n%args.parts].append(item)
processes=[]
for i in range(args.parts):
inventory=work/f'inventory-{i}.jsonl'
inventory.write_text(''.join(json.dumps(x)+'\n' for x in assigned[i]))
log=(work/f'part-{i}.log').open('w')
command=[sys.executable,str(ROOT/'build_full_index.py'),'--inventory',str(inventory),
'--output',str(work/f'part-{i}.sqlite'),'--workers',str(args.workers_per_part)]
processes.append((i,subprocess.Popen(command,stdout=log,stderr=subprocess.STDOUT),log))
failed=[]
for i,process,log in processes:
status=process.wait(); log.close()
if status: failed.append(i)
if failed: raise RuntimeError(f'Partitions {failed} failed; inspect logs and rerun to resume.')
subprocess.run([sys.executable,str(ROOT/'merge_full_index.py'),'--inventory',str(args.inventory.resolve()),
'--parts',*[str(work/f'part-{i}.sqlite') for i in range(args.parts)],'--output',str(args.output.resolve())],check=True)
if __name__=='__main__': main()