synology_apm_repo.sdk.dedup.composition_reader module¶
Composition sub-file access and record decoding.
Two collaborating classes:
CompositionReader— resolves(stream_id, session_id)to the right sequence of composition sub-files (viacomposition_pathplus sequence-id resolution), and reads any byte range of the session’s global offset space, transparently crossing 16 MiB sub-file boundaries.CompositionRecord— one backup version’sRecordHeadplus binary-search-capable access to itsChunkMapRecordarray — seeCompositionRecord.entriesfor why it never linear-scans.
- class synology_apm_repo.sdk.dedup.composition_reader.CompositionReader(store, dir_cache, comp_root, stream_id, session_id)¶
Bases:
objectRead access to one
(stream_id, session_id)’s composition sub-files. Does not validate thesubID=0header automatically on every access (that would cost a read for a check that virtually never fails and whose only failure mode — an addressing-constant mismatch — would already surface as consistently wrong data); callverify_headerexplicitly when that specific, stricter, opt-in check is wanted.- async read_at(global_offset, n)¶
Read
nbytes starting at the session-globalglobal_offset, transparently crossing 16 MiB sub-file boundaries (FORMAT-SPEC.md: composition-splitting).
- async verify_header()¶
Explicitly validate the
subID=0sub-file’s 64-byte header (major version,subFileSizeconstant match).
- async record(head_off)¶
Open the composition record at global offset
head_off(db/file_map.comp_offset) — reads only the 32-byteRecordHead, not the (potentially huge) chunk-map array that follows it.
- class synology_apm_repo.sdk.dedup.composition_reader.CompositionRecord(head_off, record_head, reader, _pages=<factory>, _page_end_offsets=<factory>, _contiguous_scanned=0)¶
Bases:
objectOne backup version’s composition record: a
RecordHeadplus lazy, page-cached, binary-search-capable access to itsChunkMapRecordarray.Pages (
_PAGE_SIZEentries each) are fetched with one merged read and cached in_pagesas raw bytes, bounded to_DEFAULT_PAGE_CACHE_MAXSIZEpages (LRU beyond that) — see_page_end_offsetsfor how_locatestays free of that bound. A page’sChunkMapEntryvalues are materialized lazily, one at a time, only where a caller actually consumes them.A caller that already has this record’s own chunk-map array bytes in hand and already knows they’re correct (
seed_pages_from_array) can pre-populate the whole cache from them directly, instead of only ever filling it cold, page by page, off disk.- record_head: RecordHead¶
- reader: CompositionReader¶
- property status: CompositionStatus¶
- async entries(start=0, end=None)¶
Stream
ChunkMapEntryvalues whose range overlaps[start, end)(file offsets within the described file, not record indices). Locates the starting entry via_locate(binary search over known page boundaries, page-sized reads for anything not yet cached), then walks forward sequentially — never a linear, one-entry-at-a-time scan.start=0, end=None(the default) walks every entry, for a full sequential export/verify.
- async seed_pages_from_array(array_raw)¶
Pre-populate every page of this record’s cache from
array_raw— chunk-map array bytes a caller (verify_checks.check_map_and_attr_crc) already confirmed correct via Redundancy-blob parity repair — instead of leaving_get_pageto cold-fetch (and so re-derive from the still-corrupted on-disk copy) asentries()walks forward.array_rawmust be exactlymap_num * CHUNK_MAP_RECORD_LENGTHbytes.A page already resolved by an earlier
entries()/_locate()/extent()call — e.g.units/device_pcps.py’s ownopen_disk(), which callsextent()(cold-fetching page 0 and the last page straight off disk) while assembling a PC/PS disk’s fragments, before this repaired record is ever seeded — is force-refreshed here too, not left with its stale content (see the loop below for how).- Raises:
ValueError –
array_raw’s length doesn’t match this record’s ownmap_num— e.g. a concurrent writer changed the record between two independent reads of it, so the repair this is seeding from was computed against a differently shaped record than the one being seeded here.
- async extent()¶
(start, end): the real file-offset range this record actually covers — its first entry’sfile_offsetto its last entry’send_offset. Cheap regardless ofmap_num(two page reads, first and last, never a full scan).Needed by PC/PS’s per-region disk fragments (
units/content/pcps_disk.py): each fragment’s registeredfile_meta.file_sizeis the whole disk’s total capacity, identical across every sibling fragment, never that one fragment’s own real length — asking its own composition record directly is the only way to learn a fragment’s actual coverage (FORMAT-SPEC.md: pcps-fragments has the full story). VM’s model doesn’t need this: one composition record already covers the whole disk.