RecoverLand: Limits

Limits and Failure Cases

This page documents situations where RecoverLand does not work, works partially, or may produce an incorrect result. Each case includes an explanation of the cause and a recommendation to avoid it.

Why this page exists: A data recovery tool must be transparent about what it cannot do. Knowing the limits prevents false confidence.

Severity scale

LevelMeaning
CRITICALData loss or restore on the wrong feature. The user must understand this risk before using RecoverLand in this context.
MODERATEThe system works but with reduced reliability. History remains browsable but automatic restore is not recommended.
LOWRare edge case. Impact limited to a specific scenario.

File moved or renamed

MODERATE

The problem

The layer fingerprint uses the absolute path of the source file. If the file is moved, renamed, or copied elsewhere, the new path produces a different fingerprint. The history recorded under the old path is no longer linked to the layer.

ogr::C:/data/vegetation.shp History: 150 events ≠ ogr::D:/archive/vegetation.shp History: 0 events Same physical file, two different fingerprints

What 6.0.0 changed, and what it did not

Version 6.0.0 added an alias mechanism: the datasource_alias table links an obsolete fingerprint to the current one, and every read path expands a fingerprint into all of its historical forms. That mechanism repairs the fingerprints the plugin used to compute incorrectly. It does not repair a file that has actually moved.

The migration recomputes the canonical fingerprint from the source URI recorded in datasource_registry at capture time. For a moved file that URI still holds the old path, so the recomputed value equals the stored one, no alias row is written, and the source is simply counted as already canonical.

Two neighbouring cases are fixed and no longer fork the history of a file:

  • A display filter (Layer > Filter, which QGIS appends as |subset=...) or an opening option (|geometrytype=...). Only the identifying keys layername and layerid take part in the identity now, so filtering a layer no longer starts a parallel history that a filter-off rewind never sees.
  • A spatialite connection string or a delimitedtext URI, which used to be passed through abspath and therefore depended on the directory QGIS was launched from.

Consequence

The existing history remains intact and browsable (it is not deleted). But RecoverLand does not know that the moved file is the same as the old one. Searching by layer does not show the old history, and no action in the dialogs re-attaches it: the alias table is written by the migration, never by the interface. The add_alias function is part of the plugin's public Python API, so an alias can be written by hand from the QGIS console, but that is not a supported workflow: the pair you give it is refused only when it is empty, self-referential, or would close a cycle. Nothing checks that the two fingerprints designate the same data.

How to avoid it

  • Do not move data files after you have started editing them in QGIS
  • If you must reorganize folders, do it before starting editing work
  • Use stable paths (no USB keys, no temporary folders)

Network drive with variable mount point

MODERATE

The problem

By default the fingerprint of a file source contains its absolute path. If a file is accessible as Z:\share\vegetation.shp on one workstation and as \\server\share\vegetation.shp on another, the two paths produce different fingerprints even though they point to the same physical file.

Same problem if the drive letter changes: Z: one day, Y: the next.

Partial mitigation: project-relative fingerprints

The environment variable RECOVERLAND_FINGERPRINT_MODE=relative makes the fingerprint of file-based sources (ogr, spatialite, delimitedtext) carry the path relative to the QGIS project home instead of the absolute path. When the project file and its data live under the same share, the mount point disappears from the fingerprint and the same history is recognised whatever letter the drive was given.

What that mode does not do:

  • It is off by default. Any other value, or no value at all, means absolute mode.
  • It is an environment variable of the QGIS process, not a project setting. It has to be set on every workstation, and set before capture starts.
  • If the project has no home path (a project never saved) or if the data sits on a different drive from the project, no relative form exists: the plugin falls back to the absolute path and writes a WARNING in the log.
  • Turning it on later does not re-attach a journal already written under absolute paths. The two forms are recognised as the same layer when RecoverLand looks for the layer an event belongs to, or checks whether a layer is being tracked; the history query is widened only through alias rows, and no alias is created for a mode change.

How to avoid it

  • Always use the same path to access the file (always the drive letter, or always the UNC path)
  • Fix the network drive letter so it does not change between sessions
  • Or keep the project file and its data under one share, and set RECOVERLAND_FINGERPRINT_MODE=relative on every workstation before the first capture

Unstable FID (shapefile, GeoJSON)

MODERATE

The problem

The FID of a shapefile is the record number in the .dbf file. This number can change if:

  • The shapefile is re-exported ("Save As..." in QGIS or another tool)
  • The .dbf file is packed after deletions
  • An external script reorganizes the records

What happens now

Until 6.0.0 the consequence was corruption: you recorded a change on feature FID=42 (a building), the shapefile was re-exported, FID 42 became a road, and the restore wrote the building's attributes onto the road while reporting success. That is fixed. Before writing on a target reached by FID alone, the restore has to prove the target is the feature the event describes:

  • if a primary key resolved the target, the FID never entered the picture and there is nothing to check;
  • if the event captured a post-edit geometry, the feature sitting at that FID must still carry it;
  • for an attribute-only edit, the feature sitting at that FID must still carry the new value of every field the event changed.

When none of those can be established, the restore looks for the feature elsewhere in the layer, by the state the event captured after the edit. If that search finds nothing, RecoverLand writes nothing, and says in the log why the action was not applied. Refusing loses a restore; writing would overwrite a feature that is not the one the event describes.

Undoing an INSERT is itself a delete

Undoing an INSERT does not overwrite a feature, it removes one : and on a layer with no primary key that decision rests on a resemblance rather than on an identity. The weakest resemblance the engine still accepts is a single point in common between the feature sitting at the recorded FID and the state the event captured: the geometry, when the event carries one, or one captured attribute still holding its captured value (_occupant_shares_evidence, core/restore_executor.py).

A resemblance is not an identity. On a layer with no primary key, a feature that inherited a recycled FID can resemble an event closely enough to be deleted in place of the one that event described : the more easily as the INSERT captured few attributes, or captured values that many features of the layer share (a type, a status, a batch number). The run counts such an action among the ones it applied; only the debug file records that the target was accepted on partial evidence.
What is left: on a re-exported or repacked shapefile a growing share of restores is simply refused. Over the life of a layer with no primary key that becomes the dominant failure mode : see Recovery horizon without a primary key.

How to avoid it

  • Use a format that can carry a real primary key (GeoPackage, SpatiaLite, PostGIS) instead of a shapefile : but see the caveat below: the label RecoverLand puts on a source is not proof that a key exists
  • If you must use a shapefile, do not re-export it between captures and restores
  • Read the report of a restore, and read it in the log panel (View > Panels > Log Messages, RecoverLand tab): a refused action names its reason there

"Strong identity" is read off the file extension

refine_ogr_identity in core/support_policy.py decides the identity strength of an OGR source by testing its URI, lowercased, against a series of substrings and returning on the first one that matches: .gpkg, .sqlite, .db and .fgb are called strong, a handful of others medium or weak. The rule worth knowing is the last line of the function rather than the list: a URI that matches nothing in it : any extension the function does not enumerate : is called medium, the same label a shapefile gets. Nothing in that function asks the provider whether a primary key exists.

What actually decides is elsewhere. compute_feature_identity in core/identity.py writes pk_field / pk_value into the recorded identity only when dataProvider().pkAttributeIndexes() is non-empty and the field carries a non-null value on that feature; otherwise the identity is {"fid": N} and nothing else. The restore proof reads that recorded identity, never the strength label.

A GeoPackage with no declared primary key is labelled strong and recorded as a bare FID. It is then exposed to everything in Recovery horizon without a primary key, whatever the label says. Changing container format is not by itself the mitigation; declaring a key is. Whether a key exists is a property of the table, not of the file extension: read the table definition, not the name of the file it lives in.

Recovery horizon without a primary key

CRITICAL

The problem

When a layer's provider declares no primary key, the identity RecoverLand records for a feature is the FID alone: {"fid": 412}. There is nothing else to record.

OGR reuses the FIDs freed by a deletion. Every deletion followed by a compaction puts a different feature under a number that journal entries already refer to. The journal is not wrong : it says what it saw : but the number it holds stops designating the feature it described.

This is not the re-export accident documented above, which happens at one point in time and which you can decide to avoid. It is cumulative and it is silent. Ordinary editing consumes the ability to go back: the further back a rewind reaches, the larger the share of its actions that address FIDs since handed to another feature. Capture keeps working, the journal keeps growing, search keeps returning results. The loss only becomes visible at the moment of the rewind.

What it looks like on a real project

One rewind measured on a real project on 2026-08-12 gave this breakdown:

Outcome of the 73 planned actionsCounterCount
Restoredapplied6
Already in the target state, nothing to doskipped_idempotent3
Nothing written back: the captured geometry was not valid, and repairing it moved it too farfailed_geometry_drift5
Failed: the target could not be establishedfailed_target_absent59

Fifty-nine actions out of seventy-three ended with no feature the restore was willing to act on. The events were all present; what the run could not do is establish which feature each of them designates today. Those five numbers : 73, 6, 3, 5, 59 : are the ones pinned in the versioned scenario scripts/validation/scenarios/rw_cycle_summary_truth.py, which replays that breakdown through the real runner. All the repository keeps of that run is that one CYCLE_SUMMARY line of counters: how many layers the rewind covered, and what kind of identity their events carried, are not in it, so this page does not state them. What the scenario checks is that the summary line adds up (the run that produced these counters had announced failed=0 next to 59 failures); it does not re-execute the restore against a layer, so treat the table as one measurement, not as a repeatable benchmark.

failed_geometry_drift has a single producer, _buffer_update in core/restore_executor.py, and it does not mean "the geometry changed since the capture". It fires when the geometry the restore is about to write back is not GEOS-valid: makeValid() repairs it, and when the repaired shape has moved from the original by more than MAKEVALID_DRIFT_TOLERANCE (a value in the layer’s CRS units, core/constants.py) the geometry is not written, rather than putting on the map a shape the user never had. The attributes are written after the geometry, so that action writes nothing at all: the feature is left exactly as the restore found it, and the run counts the action under failed_geometry_drift.

The counter names an outcome, not a cause. failed_target_absent aggregates three refusals (_classify_restore_result in core/restore_service.py): no feature found at the recorded identity, an occupant that could not be proved to be the right one, and a target the update path declined to verify. FID recycling is the cause this section is about, and it fits the shape of the measured run : but a feature genuinely deleted since capture, a layer re-exported, or a feature whose geometry and captured attributes both changed since the capture, all land in the same bucket. Nothing in the journal separates them. Read 59 as "59 actions found nothing they could prove", not as "59 recycled FIDs".
What this means: on a layer with no primary key, the reliable recovery window is not the retention period. It is however far back the FIDs still designate the same features, and that horizon shortens on its own with every deletion. Past that horizon RecoverLand cannot restore what it can no longer identify: it refuses to write on a target it cannot prove, with the single exception described above : undoing an INSERT is itself a delete, and that one acts on a resemblance.

Why RecoverLand cannot repair this for you

The FID is the only identity the layer offers. Rebuilding a lost link would mean guessing which of the current features is the one the event described, and a wrong guess overwrites an unrelated feature : the exact failure this page exists to prevent. So the engine does not rebuild the link: it reports that it could not find the feature.

Where the numbers are, and where they are not. Nothing about a rewind is pushed to the QGIS message bar. The restored and failed counts and the first five failure reasons go to the Log Messages panel (View > Panels > Log Messages, RecoverLand tab). The CYCLE_SUMMARY line that carries the four counters of the table above goes further still: to the debug file only, <QGIS profile>/recoverland/recoverland_debug.log, with a copy in <project>/.recoverland/recoverland_debug.log. A user who never opens either sees a rewind end with no visible verdict at all.

What a primary key changes, and what it does not

When the provider declares a primary key, RecoverLand records it next to the FID (pk_field / pk_value) and resolves the target with it. If the key is not found, the restore stops there and never falls back to the FID, precisely because the FID may point at an unrelated feature. A primary key removes this cause of degradation: the identity travels with the row, and deletions elsewhere in the table cost nothing.

A primary key does not make the window unlimited. Retention prunes by age whatever the identity: the purge deletes every event whose date is older than now (UTC) − retention_days. retention_days defaults to 365 and is settable between 7 and 3650 days in the Maintenance dialog, alongside a max_events cap (default 1 000 000) that removes the oldest events beyond that count. Startup auto-purge is off by default, so nothing is deleted until you enable it or run a purge yourself : but once an event is purged it is gone. The time cursor of a rewind cannot be taken back further than the oldest event the journal still holds, so purged history is simply out of reach.

How to reduce the exposure

  • Give the layer a primary key the provider actually declares: an integer primary key on a GeoPackage or SpatiaLite table, or PostGIS. This is the only measure that removes the cause instead of delaying it. Changing container format without declaring a key changes only the label RecoverLand prints : see "Strong identity" is read off the file extension.
  • On a layer with no primary key, treat the journal as a short-term safety net: check and restore soon after the incident, not months later.
  • Do not compact or re-export such a layer between a capture and a restore.
  • After every rewind, open the Log Messages panel for the counts, and the debug file for the CYCLE_SUMMARY line behind them. failed_target_absent measures how much of that history the engine could no longer tie to a feature; nothing is written for those actions.

FID = -1 (feature being created)

LOW

The problem

During feature creation (before commit), QGIS assigns a temporary FID of -1. If the plugin captures a snapshot at this moment, the recorded identity is {"fid": -1}, which is not unique.

Real impact

This case is rare because RecoverLand captures at commit time (afterCommitChanges), not during editing. At that point, QGIS has already assigned a definitive FID. The risk is theoretical.

Pre-6.0.0 database history left unattached

MODERATE

The problem

Before 6.0.0, the fingerprint of a PostgreSQL, MS SQL or Oracle source captured the schema where it meant the table. Every table of one schema therefore shared a single identity and their histories were merged in the journal.

6.0.0 computes the fingerprint correctly, and the v6 migration attaches the obsolete fingerprints to the new ones by writing alias rows : without rewriting a single journal event. Read paths expand a fingerprint into all of its historical forms, so an old journal stays usable after the upgrade.

That repair is impossible in one case: when an old fingerprint would have to split into several current ones. The journal holds one merged history, several tables claim it, and nothing in the data says which events belong to which table. The migration refuses to guess. It writes no alias row for that source, and the old events stay filed under the merged fingerprint.

How the migration decides

The probe only runs on database providers, and only when the table part of the fingerprint actually moves. It then asks how many distinct QGIS layer identifiers were ever recorded under that fingerprint:

  • more than one : the fingerprint covered several layers: ambiguous, no alias;
  • exactly one : the fingerprint names one source: the alias is written;
  • none recorded at all (journal written by a very old build) : the layer names are used as a fallback, and more than one name is again ambiguous.

The same "leave it alone" outcome applies to a fingerprint the journal has no source address for, and to a pass that could not finish. A pass that did not complete is not marked done: it is retried the next time the project is opened.

Consequence

No event is lost and nothing is rewritten : deleting the alias rows restores the pre-migration state exactly. But a rewind scoped on the current identity of such a table does not reach the events captured under the merged fingerprint.

You are told which sources, but only in the log. RecoverLand names the affected layers through qlog, which writes to the debug file and to the QGIS Log Messages panel (View > Panels > Log Messages, RecoverLand tab). Nothing appears in the message bar, and nothing appears in the RecoverLand window itself. The warning is emitted once per journal when the RecoverLand dialog refreshes its layer list : not on project opening alone, so a user who never opens the dialog is never told : and again every time a rewind is launched on a degraded journal. If you see that message, treat the older history of the named layers as consultable but not restorable through a rewind on the current identity.
Capture is not affected: recording never depends on a journal row. A tracked-layer selection saved by any earlier version keeps designating the same layers, because membership is tested on both the current and the historical form of the fingerprint.

Changes outside QGIS

CRITICAL

The problem

RecoverLand listens to QGIS edit signals. If a file is modified by another application (ArcGIS, Python script, text editor, ogr2ogr), RecoverLand detects nothing. These changes are not in the journal.

QGIS editing Captured Python script Not captured Other software Not captured

How to avoid it

Make all your changes in QGIS. If you must use a script, run it in the QGIS Python console in edit mode (startEditing / commitChanges) so that the signals are emitted.

Recording disabled

CRITICAL

If the user disables the recording toggle, no change is captured. RecoverLand cannot recover what was never recorded. There is no catch-up mode.

What the status bar indicator says

The indicator is computed from runtime facts (tracker, writer thread, outbox, journal, disk), not from a saved setting. Its states, in the order they win:

  • ERROR: edits are, or were this session, not recorded anywhere: writer thread dead, events lost, capture cut by the disk monitor, event generation failed for a commit
  • PAUSED: capture is off (your toggle, or the memory pressure pause after a very large commit)
  • DEGRADED: edits are durable but not through the normal path: journal temporarily unwritable (edits wait in the outbox), outbox unwritable (edits written straight to the journal), low disk, records waiting in the quarantine, layers edited that cannot be captured
  • PERSISTING: healthy, the writer is indexing events that are already durable
  • PROTECTED: every accepted edit is in the journal or durably on its way

Every transition that puts history at risk is also pushed once to the QGIS message bar (then at most once every 2 minutes for the same cause), and written to the Log Messages panel and to the structured log.

Verify that the indicator is not red or grey before starting to edit. This is the first thing to check if you find the history is empty.

Disk full or critically low

CRITICAL

The problem

If the disk hosting the journal drops below 100 MB free, RecoverLand automatically disables tracking. Changes made after that point are not captured.

How it is handled

The DiskMonitor module checks disk space when the journal is opened and every 5 minutes after that. When the critical threshold is reached:

  • Recording is automatically disabled, and a persistent setting records that the disk monitor is the one who disabled it
  • A message giving the volume and the free space left is written to the QGIS Log Messages panel, at ERROR level, and the status bar indicator is refreshed
  • At the warning threshold (500 MB), the same channel gets a WARNING-level notice, once; recording continues
  • A volume that cannot be measured at all : an unplugged share, a drive letter that disappeared : is treated as critical, not as healthy
Both alerts reach the screen. The critical one is a QGIS message bar item that stays until you close it; the warning one is shown once. Both also go to the Log Messages panel (RecoverLand tab) and to the logs, and the status bar indicator changes state (ERROR when capture is cut, DEGRADED on low space). The disk state behind it is one of HEALTHY, LOW_SPACE, CRITICAL_SPACE, UNREACHABLE (no reading could be obtained) or UNKNOWN (no journal open yet).

Recovery

Free up disk space. The monitor re-enables recording on its own: at the next check : up to 5 minutes later, or immediately if the journal is reopened : if free space is back above the warning threshold of 500 MB and the setting says the disk monitor is the one who disabled recording, tracking is switched back on and a line saying so is written to the log.

Two conditions, both of which can leave it off. Recovering to 200 MB free is not enough: the re-arming path requires clearing the 500 MB warning threshold, not the 100 MB critical one. And if you toggled recording yourself at any point : on or off : that clears the tracking_disabled_by_disk marker, so the re-arming branch of the periodic check returns without doing anything and the monitor will not switch recording back on for you (recover.py). In that case the toggle is yours to switch back on. It can still switch it off: the critical branch disables recording whenever the check finds free space below the critical threshold and tracking active, whoever turned it on. What the monitor gives up after your toggle is the re-arming, not the cut.

QGIS crash, power cut

LOW

What is still lost

Changes that were never committed (still in the edit buffer when QGIS stops) are not captured: the provider never received them either, so the data and the history agree.

What is not lost any more

Before RecoverLand says a commit is recorded, the commit is on disk in the capture outbox, an append-only file next to the journal (<journal>.outbox/), written and flushed with fsync on the calling thread. The SQLite journal is filled from it afterwards by the writer thread. This costs time on the GUI thread at every commit, in proportion to the number of features changed (measured by scripts/validation/bench_capture.py and bench_durability.py).

  • Crash during the provider commit: the state of the features before the edit is written to the outbox before the provider starts writing. At the next start the commit is recorded as INTERRUPTED in the commit ledger (capture_commit) with that pre-edit state, compared with what the provider really holds, and reported. Nothing is inferred as "not applied".
  • Crash after the commit, before indexing: the outbox is replayed at the next start. Replay is idempotent (every event carries an event_uid): an event already indexed is counted, never duplicated.
  • Torn write (crash in the middle of an append): every complete line is replayed; the cut line is moved to the quarantine and reported, never deleted. A batch whose end marker is missing is recorded INCOMPLETE.
  • Power cut or operating system crash: the outbox copy of a batch is released only after the journal commit is itself synchronised (synchronous=FULL on the writer connection). A commit that SQLite has not yet written through keeps its outbox copy.

What you see after such a restart

A message bar item states how many edits were brought back from the outbox, how many commits were interrupted (with the layers to check), and how many lines were quarantined.

Tested, not assumed. The validation suite kills a child QGIS process at each step of the hand-off (before the outbox write, in the middle of it, after its fsync, after the provider commit, before and after the journal commit) and checks after restart that every event is in the journal exactly once.

Partial commit

MEDIUM

The problem

A QGIS commit is written in stages: geometries, attributes, deletions, additions. A stage that fails does not undo the stages already written: commitChanges() returns False while part of the edit is in the file (measured on GeoPackage with a UNIQUE constraint refusing an addition; the same holds for any provider that writes each stage on its own).

What RecoverLand does

The stages that the provider reported as written are journaled, with their pre-edit state, and the commit is recorded PARTIAL. A message bar item says that the commit was partial. When you fix the problem and validate again, the rest is journaled with the next commit; when you roll back, what was already written stays journaled (it is in the file).

What RecoverLand writes itself

A strict restore or rewind that meets a partial commit reads the file back, undoes the stages it had written, and reports the whole layer as refused (REFUSED_COMPENSATED). If the undo itself fails, or cannot be attempted (Shapefile and GeoJSON renumber their features, so what was written cannot be mapped back safely), the verdict is REFUSED_COMPENSATION_FAILED: the writes that stayed in the file are traced in the journal, counted as applied, kept in the undo scope, and named in the report.

Automatic transaction groups

CRITICAL

The problem

With the project option "Automatically create transaction groups where possible" (PostgreSQL, GeoPackage), QGIS sends every edit to the database immediately, inside a database transaction, and the usual commit signals carry nothing. RecoverLand cannot see the pre-edit state or the changes.

What RecoverLand does

Such a layer is not captured. When it enters edit mode, a message bar item says so and the indicator switches to DEGRADED for the session.

How to avoid it: in Project, Properties, Data Sources, set the transaction mode to the local edit buffer for projects whose history matters.

Schema drift

MODERATE

The problem

If a layer's structure changes between capture and restore (field added, removed, renamed, or type changed), the restore may fail or produce an incomplete result.

What RecoverLand does

Before restore, the schema_drift.py module compares the saved schema with the current schema and generates a compatibility report. Missing or incompatible fields are flagged.

Best practice: Avoid modifying a layer's structure (adding/removing fields) between captures and restores. If necessary, consult the history first to see whether restores are planned.

Concurrent editing

LOW

RecoverLand is designed for single-workstation use. If two users edit the same file simultaneously, each user has their own journal. A restore by one user may overwrite the other's changes without warning.

This scenario is not supported. For collaborative editing, use a database with a locking system (PostGIS, for example).

Memory layer

LOW

A memory layer (a QGIS scratch layer) has no persistent source file and no stable feature identity. RecoverLand does not capture it at all.

The provider policy in core/support_policy.py gives memory an identity strength of NONE, with capture and restore both off. EditSessionTracker.connect_layer refuses every layer whose strength is NONE and returns before connecting a single edit signal, logging action=refused reason=no_stable_identity. No signal is bound, so no commit is ever seen and no event is ever written.

You have no history for that layer, not a history you cannot restore. Nothing was recorded, so there is nothing to consult, search or export either : and the refusal is emitted through flog (core/edit_tracker.py), which writes to the debug file recoverland_debug.log and to nothing else. It does not reach the Log Messages panel, so the panel staying empty is not a sign the layer is being captured. Save a scratch layer to a real file before you start doing work in it you would mind losing.

Quarantine

MEDIUM

What it is

Records that were accepted but that the journal refuses (a value SQLite rejects), and lines of the outbox cut by a crash, are written to <journal>.outbox/quarantine/, synchronised, and never deleted by RecoverLand. Each event is either in the journal or in the quarantine, never both: a replay skips the events the quarantine already holds.

What you see

As long as the quarantine holds records, the indicator stays DEGRADED with their count, including after a restart. The files are JSON Lines: one record per line, with the reason and the full event.

Nothing re-integrates them automatically. A quarantined edit is not in the history and cannot be undone by RecoverLand. Keep the files for analysis; move them out of the folder once handled to clear the indicator.

Null and empty geometries

LOW

A feature without geometry (NULL) and a feature with an empty geometry (for example POINT EMPTY) are two different states, and the journal keeps them apart: a geometry that appears or disappears is recorded with an explicit marker, so a rewind removes a geometry added after the target date and gives back one removed after it.

The check made after a restore commit treats NULL and EMPTY as the same "no shape" value, because some providers store one as the other (a Shapefile has no empty geometry). A restore that should give back EMPTY and finds NULL, or the reverse, is reported as applied.

Journal size

LOW

The problem

The journal grows indefinitely if retention is not configured. On a project with thousands of daily changes, the file can reach several hundred MB.

What the plugin actually measures

RecoverLand publishes no query-latency measurement, and this page will not invent one. What exists is a classification: evaluate_journal_health in core/health_monitor.py grades the journal on file size and event count and keeps the worse of the two grades.

GradeReached atWhat it produces
healthybelow 50 MB and below 100,000 eventsnothing
info50 MB or 100,000 eventsthe temporal coverage of the journal, and no suggestion
warning200 MB or 500,000 events"journal growing, consider purging", plus a pointer to the Maintenance dialog
critical500 MB or 1,000,000 events"large journal, purge recommended"

These are thresholds someone chose, not the point at which anything was observed to slow down. Crossing 100,000 events changes a label from healthy to info; it does not mean a query became slower there.

Solution

Use the Maintenance dialog to configure a retention policy. For example: keep 90 days of history and purge the rest.

Do not wait to be told. The first grade that actually says something actionable is warning, at 200 MB or 500,000 events. At info the indicator only reports how far back the journal reaches, which reads as a status line, not as a warning.

Impact of delta storage

RecoverLand uses delta storage for UPDATE: only the attributes that actually changed are recorded, so changing one field of a fifty-column table stores one value and not fifty. No per-event size is published here, estimated or measured: it depends entirely on how many columns a table has, how many of them an edit touches, and whether the geometry changed. What the plugin does measure is the file itself, through the grades above.

Journal deleted

CRITICAL

If the .recoverland/recoverland_audit.sqlite file is deleted (manually, by a disk cleanup tool, or by an antivirus), the entire history is permanently lost. RecoverLand will create a new empty journal at the next startup.

Never delete the .recoverland folder in your QGIS project directory, unless you explicitly want to erase the entire change history.

QGIS project moved

MODERATE

If you move a QGIS project (.qgz) to another folder, the project_fingerprint changes. The existing journal (which is in the .recoverland/ folder at the old location) is no longer found automatically.

How to avoid it

If you move a project, also move the .recoverland/ folder that is next to the .qgz file. The journal will be found if .recoverland is present next to the project file.

Orphan journals (unsaved projects)

LOW

The situation

When a project is never saved, its journal lives in the QGIS profile directory under a hashed filename (audit_<hash>.sqlite). If the project is closed without saving, this journal file becomes an orphan: it exists on disk but is never opened again.

Automatic cleanup

At startup, the JournalManager scans the unsaved-project audit directory and removes orphan journals older than 30 days (excluding the currently active journal). This cleanup is automatic and silent.

If you want to keep an unsaved-project journal, export it from the Maintenance dialog before closing QGIS. Once it is deleted by the automatic cleanup, it cannot be recovered.

What RecoverLand is not

RecoverLand is not...Use instead...
A versioning system (no branches, no merge)Git, GeoGig, or a dedicated versioning system
A backup system (does not copy files)Classic file backup, rsync, or a NAS
A collaboration system (no sync between workstations)PostGIS with session management, or a collaborative GIS
A real-time monitoring system (no push notifications)Database triggers or a DBMS-level audit system
A Ctrl+Z replacement (only works after commit)The native QGIS Ctrl+Z for uncommitted changes

What RecoverLand is

A local safety net. It automatically records every committed change in a SQLite journal, and allows you to find and restore previous states. It protects against common human errors: accidental deletion, incorrect attribute modification, geometry overwrite.

It works best with strong-identity formats (GeoPackage, PostGIS), on a single workstation, with files that remain at the same location.