Temporal Collections - Bitemporal vs Unitemporal

Understanding Time-Based Versioning

personClever Llamas
CleverLlamasMinimum Llamaverse Version: 2.3.1
databaseMinimum MarkLogic Version: 8

Temporal design decisions become expensive to reverse. Choosing between unitemporal and bitemporal is therefore not just a modelling preference; it is an audit, compliance, and operational choice.

The short version is:

  • Unitemporal tracks valid time only.
  • Bitemporal tracks valid time and system time.

Valid time answers when a fact is true in the real world. System time answers when the database knew that fact. If those two timelines can diverge in your domain, bitemporal is usually the safer choice.

The examples in this article assume the llamaverse (v2.3.1+) is deployed. The llamaverse sample data is freely available from github.com/cleverllamas/llamaverse — see the llamaverse article for full setup instructions.

Time Dimensions

DimensionWhat it meansTypical question
Valid timeWhen a fact is true in the modelled worldWho owned this llama on 2025-06-15?
System timeWhen the database stored a given versionWhat did we believe on 2025-07-01?

This distinction is what enables powerful audit queries such as: "What did we believe then about facts valid at that date?"

Visual: Time Through a Clever Llama Lens

The key difference is simple: how many clocks your model needs to answer the business questions.

Unitemporal: valid-time only

Bitemporal: valid time plus system time

Model choice flow

Required Indexing

Temporal axes depend on dateTime range indexes. You must configure those indexes before axis creation succeeds.

Axis fieldExpected typeUsed by
valid-startdateTimeUnitemporal and bitemporal
valid-enddateTimeUnitemporal and bitemporal
system-startdateTimeBitemporal
system-enddateTimeBitemporal

Create Axes and Collections

Create the valid-time axis

xquery version "1.0-ml";
import module namespace temporal = "http://marklogic.com/xdmp/temporal"
  at "/MarkLogic/temporal.xqy";

(: Create a valid-time axis backed by dateTime range indexes. :)

temporal:axis-create(
  "valid",
  cts:element-reference(xs:QName("valid-start"), ("type=dateTime")),
  cts:element-reference(xs:QName("valid-end"), ("type=dateTime"))
)

Create the system-time axis

xquery version "1.0-ml";
import module namespace temporal = "http://marklogic.com/xdmp/temporal"
  at "/MarkLogic/temporal.xqy";

(: Create a system-time axis for bitemporal collections. :)

temporal:axis-create(
  "system",
  cts:element-reference(xs:QName("system-start"), ("type=dateTime")),
  cts:element-reference(xs:QName("system-end"), ("type=dateTime"))
)

Create unitemporal and bitemporal collections

xquery version "1.0-ml";
import module namespace temporal = "http://marklogic.com/xdmp/temporal"
  at "/MarkLogic/temporal.xqy";

(: Unitemporal collection: valid axis only. :)
temporal:collection-create("llama-ownership", "valid"),

(: Bitemporal collection: valid axis + system axis. :)
temporal:collection-create("llama-health-audit", "valid", "system")

Unitemporal: Operational Simplicity

Unitemporal collections are often enough when your primary concern is "state as valid on date X" and you do not need to preserve the full history of when the database learned each correction.

xquery version "1.0-ml";
import module namespace temporal = "http://marklogic.com/xdmp/temporal"
  at "/MarkLogic/temporal.xqy";

(: Insert a unitemporal ownership record. :)

temporal:document-insert(
  "llama-ownership",
  "/cleverllamas/llamaverse/temporal/ownership/llama-001.xml",
  <ownership>
    <llama-id>llama-001</llama-id>
    <owner>Alice</owner>
    <valid-start>2025-01-01T00:00:00Z</valid-start>
    <valid-end>9999-12-31T23:59:59Z</valid-end>
  </ownership>,
  (xdmp:permission("cleverllamas-llamaverse-reader", "read")),
  ("temporal", "ownership")
)

Unitemporal strengths:

  1. Simpler mental model.
  2. Lower storage and operational complexity.
  3. Straightforward historical-as-of queries on valid time.

Unitemporal limitations:

  1. Cannot answer "what did we know then" independently of valid time.
  2. Harder to satisfy strict audit reconstruction requirements.

Bitemporal: Strong Auditability

Bitemporal collections add a system-time axis managed by MarkLogic. You supply valid-time boundaries; MarkLogic records system-time boundaries as versions are inserted and superseded.

xquery version "1.0-ml";
import module namespace temporal = "http://marklogic.com/xdmp/temporal"
  at "/MarkLogic/temporal.xqy";

(: Insert a bitemporal health record. MarkLogic manages system time values. :)

temporal:document-insert(
  "llama-health-audit",
  "/cleverllamas/llamaverse/temporal/health/llama-001.xml",
  <health-assessment>
    <llama-id>llama-001</llama-id>
    <status>Healthy</status>
    <assessed-by>Dr Woolfe</assessed-by>
    <valid-start>2025-03-01T00:00:00Z</valid-start>
    <valid-end>9999-12-31T23:59:59Z</valid-end>
  </health-assessment>,
  (xdmp:permission("cleverllamas-llamaverse-reader", "read")),
  ("temporal", "health")
)

Bitemporal strengths:

  1. Reconstructs both real-world and database-knowledge timelines.
  2. Supports compliance, investigation, and legal replay scenarios.
  3. Makes late-arriving corrections explicit rather than destructive.

Bitemporal trade-offs:

  1. More complex query design.
  2. More retained versions and storage overhead.
  3. Higher governance discipline required.

Querying As-Of State

The core as-of pattern is a period query on the valid axis.

xquery version "1.0-ml";

(: Query a temporal collection as of a valid-time point. :)

let $point := xs:dateTime("2025-06-15T00:00:00Z")
return cts:search(
  fn:collection("llama-ownership"),
  cts:period-range-query(
    "valid",
    "AL_CONTAINS",
    cts:period($point, $point)
  )
)

For bitemporal models, you typically combine valid-time criteria with system-time criteria to answer dual-timeline questions. Design these queries early in a project so your axis naming and index strategy match audit requirements.

Choosing the Right Model

RequirementBetter fit
Business-state history onlyUnitemporal
Regulatory replay of knowledge stateBitemporal
Simple operational reportingUnitemporal
Legal/compliance-grade lineageBitemporal

A practical default is to start with unitemporal only when you are certain system-time reconstruction will not be required. If uncertainty is high, bitemporal is often cheaper than a later migration.

Common Mistakes

MistakeImpactBetter approach
Choosing unitemporal for strict audit domainsMissing evidence of knowledge timelineUse bitemporal from the start
Treating system time as user-managedInconsistent and untrustworthy lineageLet MarkLogic manage system time
Delaying index planningAxis creation and query failures laterDefine range indexes first
Designing queries before axis conventionsFragile, hard-to-maintain codeStandardise axis and field names early

Need Some Help?


Looking for more information on this subject or any other topic related to MarkLogic? Contact Us (info@cleverllamas.com) to find out how we can assist you with consulting or training!