Version vs Snapshot Isolation for Batch Jobs
Almost every batch job in a utility estate is pointed at a version, and most of them should not be. The habit is understandable: versioning is how the network is edited, so a version feels like the correct way to address it. But a job that only reads gains nothing from a version and inherits all of its costs — contention with editors, results that move while the job runs, and a cleanup obligation. A read-only snapshot gives the opposite trade: no writes, no visibility of work in progress, and in exchange, inputs that do not move and no interference with anyone editing. This guide sets out how to choose, how to materialise a snapshot cadence that analytics can rely on, and what to do about the jobs that genuinely need both. It follows the isolation policy described in branch versioning and conflict resolution.
Environment Prerequisites
- A reconciled default version with a predictable post rhythm, so a snapshot taken after a post represents a coherent state rather than a moment mid-merge.
- Somewhere to materialise the snapshot — a read-only replica, a file geodatabase export, or a versioned read against a recorded moment. Any of the three works; what matters is that the moment is recorded with it.
- Python 3.11 with
arcpyand, for the analytical jobs themselves,geopandaswhere the work does not need the network solver. - A job inventory listing every scheduled process against the network, with whether it writes and whether it needs uncommitted edits. Building this list is usually the moment the problem becomes obvious.
- A convention for stamping results with the snapshot moment they were computed from, so two reports that disagree can be reconciled by their inputs rather than argued about.
Schema-Aware Selection Protocol — Score Each Job Before You Point It Anywhere
- Does the job write? If yes, it needs a version, and it needs its own — never the default version during editing hours. This single question settles most of the inventory.
- Does it need to see uncommitted edits? Only validation inside an editor’s own version genuinely does. A job that “needs current data” almost always means “needs data as of the last post”, which is exactly what a snapshot provides.
- Must two runs agree? A report re-run for the same period must produce the same numbers. If it reads the live network, it will not, and the difference will be attributed to the report.
- How long does it run? A long read against the default version is the most common source of blocking in a versioned estate, and it is invisible in a write-path investigation.
- What does it publish? A job whose output drives an operational decision needs a recorded input moment for the same reason a trace does: without it, nobody can reconstruct what the decision was based on.
Materialising a Snapshot Cadence
The mechanics are less important than the discipline: whatever produces the snapshot must record the moment it represents, and every consumer must carry that moment through to its output.
from __future__ import annotations
import logging
from dataclasses import dataclass
from datetime import datetime, timezone
import arcpy
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
LOG = logging.getLogger("snapshot")
@dataclass(frozen=True)
class Snapshot:
"""A read-only view of the network with the moment it represents attached."""
path: str
moment: datetime
source_version: str = "sde.DEFAULT"
def stamp(self) -> dict:
"""The provenance block every result computed from this snapshot must carry."""
return {
"snapshot_path": self.path,
"snapshot_moment": self.moment.isoformat(),
"source_version": self.source_version,
}
def materialise(workspace: str, out_gdb: str, datasets: list[str]) -> Snapshot:
"""Export a coherent read-only copy of the network and record its moment.
The moment is taken before the export starts, not after: a consumer needs to know
the earliest state the data could represent, so that a comparison against an edit
log is conservative rather than optimistic.
"""
moment = datetime.now(timezone.utc)
arcpy.env.workspace = workspace
for name in datasets:
try:
arcpy.management.Copy(name, f"{out_gdb}/{name.split('.')[-1]}")
LOG.info("copied %s", name)
except arcpy.ExecuteError as exc:
LOG.error("copy failed for %s: %s", name, exc)
raise
return Snapshot(path=out_gdb, moment=moment)
def assert_fresh(snap: Snapshot, max_age_minutes: int) -> None:
"""Refuse to run an analysis against a snapshot older than the job tolerates."""
age = (datetime.now(timezone.utc) - snap.moment).total_seconds() / 60
if age > max_age_minutes:
raise RuntimeError(
f"snapshot is {age:.0f} minutes old, job tolerates {max_age_minutes}")
assert_fresh is the part worth copying. A snapshot cadence fails quietly when the export job
stops: every downstream analysis keeps running against an increasingly stale copy, and nothing
reports a problem because the data is perfectly well formed. Making each consumer declare its
tolerance turns a silent staleness into a loud failure in the job that cares.
Production Deployment Pattern
- Export the snapshot immediately after the post window. A snapshot taken mid-post can catch a partially applied change, which is the one state nobody wants to analyse.
- Point every read-only job at it, by default. Make the version the exception that has to be justified in the job’s configuration, not the default that is never questioned.
- Stamp every output with the snapshot moment. A report, an impact set, a compliance table — all of them carry the moment, so a disagreement between two of them is resolved by comparing inputs.
- Assert freshness in each consumer. Let the job that needs data less than an hour old fail loudly rather than silently computing on yesterday’s copy.
- Keep write jobs in their own versions and delete them. An automation version that outlives its run is the second most common source of reconcile slowness after abandoned project versions.
- Handle the jobs that need both. A few genuinely need to write and to see a stable input set — a bulk remediation that computes from a fixed picture and writes its corrections back. Run those as two phases: read the snapshot, compute the change set entirely in memory or in a staging table, then open a version and apply it. Splitting the read from the write keeps the version open for seconds rather than for the length of the analysis, which is where the contention would otherwise come from.
- Retire the habit deliberately. Existing jobs will keep using a version because that is how they were written, so the change has to be made job by job with the inventory as the checklist. Each conversion is small; the aggregate effect on contention is not.
- Measure where blocking actually happens. Before tuning the write path, count blocking events by the connection that caused them. On most estates the answer is long analytical reads, and the fix is this pattern rather than anything about editing.
Conclusion
The version-or-snapshot decision looks like an implementation detail and behaves like an architectural one. Writing jobs need a version of their own; reading jobs almost always want a snapshot, and giving them one removes most of the contention an estate attributes to versioning itself. The discipline that makes it work is recording the moment and carrying it through to every result, because a reproducible number is one whose inputs can be named. Once analytics run off the editing path, the reconcile and post loop has room to run on a schedule rather than in the gaps, which is what makes reconcile and post automation dependable.
Related
- Up to the parent topic: Branch Versioning & Conflict Resolution
- Up to the section: Topology & Tracing Workflows
- Reconcile & Post Automation for Utility Network Edits
- Resolving Branch Version Conflicts in Utility Networks
- Batch Topology Processing with Python
For authoritative reference, consult the ArcGIS Pro versioning documentation and the Python datetime library.