Skip to contents

Introduction

This article demonstrates how to perform time travel operations over fragments and metadata.

To set a timestamp range in TileDBArray() or TileDBGroup(), use either thetiledb_timestamp argument at instantiation or the mutable field tiledb_timestamp. Note these options applies to read mode only; for writing at arbitrary time points, you should use open_write() method. The separate interface is a design choice in order to avoid writing inadvertently at arbitrary timestamp other than current time point.

To get start with examples, please source the helpers from the Appendix.

Application

Use demo_tstamped_group() to create a group with two array members. The array members have identical fragments with timestamps prior to group creation.

Group Time - Travelling

uri <- tempfile()
res <- demo_tstamped_group(uri)
grp <- tdb_group(uri)

grp
# R6Class: <TileDBGroupExp>
# → URI Basename: file9f2c79ac397f
#   • Arrays: "testarray2" and "testarray1"
#   • Groups: ""

The group and its members were created at the following timestamps:

# Timestamps (UTC) ----------
# Group   @t0: 2026-01-29 12:27:38
# Member1 @t1: 2026-01-29 12:27:42
# Member2 @t2: 2026-01-29 12:27:45

Let’s open group object at previous time points and check its members:

# open group @t0
grp$tiledb_timestamp <- res$group_ts$t0

# check group timestamp range
group_timestamps(grp, tz = "UTC")
# Group Timestamps (ctx) • Mode (read) • TZ (UTC)
#  • start: 1970-01-01 00:00:00
#  • end  : 2026-01-29 12:27:38

# no members @t0
grp$count_members()
# [1] 0

# open group @t1
grp$tiledb_timestamp <- res$group_ts$t1

# one member @t1
grp$count_members()
# [1] 1

# open group now
grp$tiledb_timestamp <- NULL

# 2 members @ current timestamp
grp$count_members()
# [1] 2

Note that grp has no metadata. Let’s add a key value at group’s t1 timestamp (the time when member 1 was added):

# no metadata
grp$get_metadata()
# TileDB GROUP: <R6 Class: TileDBGroupExp>
# Metadata: <key,value> • total 0

# timestamp @t1
t1 <- res$group_ts$t1
t1_char <- format(t1, tz = "UTC")
t1_char
# [1] "2026-01-29 12:27:42"

# set metadata val1 @t1
set_metadata(grp, list(val1 = t1_char), timestamp = t1)

# open group @t0
grp$tiledb_timestamp <- res$group_ts$t0

# no metadata @t0
grp$get_metadata()
# TileDB GROUP: <R6 Class: TileDBGroupExp>
# Metadata: <key,value> • total 0

# open now
grp$tiledb_timestamp <- NULL

# metadata that was set @t1
grp$get_metadata()
# TileDB GROUP: <R6 Class: TileDBGroupExp>
# Metadata: <key,value> • total 1
#  • val1: '2026-01-29 12:27:42'

Array Time - Travelling

The helper function demo_tstamped_group() wrote two identical arrays with 3 fragments and metadata with the following timestamps:

# Timestamps (UTC) ----------
# Fragment @t0: 2025-08-18 16:12:50
# Fragment @t1: 2025-08-18 16:12:55
# Fragment @t2: 2025-08-18 16:13:01

Verify commit with expected timestamps was written on disk:

# uri array1
uri_arr1 <- grp$members$testarray1$uri

fraobj_1 <- tdb_fragments(uri_arr1)

fraobj_1$get_ifragment(1)
# ── FRAGMENT #1 ──────────────────────────────────────────────────────────────────────────────────────────────
#  ❯ URI: file:///C:/Users/Constantine/AppData/Local/Temp/RtmpW2GPj3/file9f2c79ac397f/testarray1/__fragments/__1755533570000_1755533570000_6f2ea5440a1befeafae9f53a203226f0_22
#  ❯ Type: sparse
#  ❯ Non-empty domain: 
#    • id: [1, 1] (INT32)
#  ❯ Size: 3.13 KiB
#  ❯ Cell num: 1
#  ❯ Timestamp range: [2025-08-18 16:12:50 UTC, 2025-08-18 16:12:50 UTC]
#  ❯ Format version: 22
#  ❯ Has consolidated metadata: FALSE

or alternatively:

fraobj_1$frag_uris()
#    Fragment     start_timestamp       end_timestamp
#      <char>              <POSc>              <POSc>
# 1:       #1 2025-08-18 16:12:50 2025-08-18 16:12:50
# 2:       #2 2025-08-18 16:12:55 2025-08-18 16:12:55
# 3:       #3 2025-08-18 16:13:01 2025-08-18 16:13:01
#                                                                  URI
#                                                               <char>
# 1: __1755533570000_1755533570000_6f2ea5440a1befeafae9f53a203226f0_22
# 2: __1755533575000_1755533575000_01d1ae9e6269bf4b457fbfb5a0359826_22
# 3: __1755533581000_1755533581000_1ae96fac8f9cc5b7e8e03bff730d81d4_22

Now, let’s time travel:

# toggle the result output
tiledb::set_return_as_preference("data.frame")

arrobj_1 <- grp$get_member("testarray1")

# first write @ "2025-08-18 16:12:50" UTC
arrobj_1$tiledb_timestamp <- as.POSIXct("2025-08-18 16:12:50", tz = "UTC")

# query upto "2025-08-18 16:12:50" UTC
arrobj_1$object[]
#   id val
# 1  1   1

# metadata
arrobj_1$get_metadata()
# TileDB ARRAY: <R6 Class: TileDBArray>
# Metadata: <key,value> • total 1
#  • key1: '2025-08-18 16:12:50'

# second write @ "2025-08-18 16:12:55" UTC
arrobj_1$tiledb_timestamp <- as.POSIXct("2025-08-18 16:12:55", tz = "UTC")

# query upto "2025-08-18 16:12:55" UTC
arrobj_1$object[]
#   id val
# 1  1   1
# 2  2   2

arrobj_1$get_metadata()
# TileDB ARRAY: <R6 Class: TileDBArray>
# Metadata: <key,value> • total 2
#  • key1: '2025-08-18 16:12:50'
#  • key2: '2025-08-18 16:12:55'

# reset
arrobj_1$tiledb_timestamp <- NULL

# query upto now
arrobj_1$object[]
#   id val
# 1  1   1
# 2  2   2
# 3  3   3

arrobj_1$get_metadata()
# TileDB ARRAY: <R6 Class: TileDBArray>
# Metadata: <key,value> • total 3
#  • key1: '2025-08-18 16:12:50'
#  • key2: '2025-08-18 16:12:55'
#  • key3: '2025-08-18 16:13:01'

arrobj_1$close()

Consider we want to change a written commit at a new timestamp. The following example use open_write() to write at t1 in order to change the value from 2 to 22; and because we’re not allow for duplicates, we see only the most recent update:


# open write @t1
arr <- open_write(arrobj_1, 
                  timestamp = as.POSIXct("2025-08-18 16:12:55",
                                         tz = "UTC"))

# check timestamps
array_timestamps(arr, tz = "UTC")
# Array Timestamps • Mode (write) • TZ (UTC)
#  Temporal Range
#   • start: none
#   • end  : 2025-08-18 16:12:55
#  Open Range
#   • start: 1970-01-01 00:00:00
#   • end  : 2025-08-18 16:12:55

# write data
arr[] <- data.frame(id = 2, val = 22L)

# close
arr <- tiledb::tiledb_array_close(arr)
rm(arr)

# print updated array
arrobj_1$reopen()
arrobj_1$object[]
#   id val
# 1  1   1
# 2  2  22
# 3  3   3

Notice that fragments 2, 3 have identical timestamp range:

fraobj_1$reload_finfo()
fraobj_1$get_last_ifragments(3)
# ── FRAGMENT #2 ──────────────────────────────────────────────────────────────────────────────────────────────
#  ❯ URI: file:///C:/Users/Constantine/AppData/Local/Temp/RtmpW2GPj3/file9f2c79ac397f/testarray1/__fragments/__1755533575000_1755533575000_01d1ae9e6269bf4b457fbfb5a0359826_22
#  ❯ Type: sparse
#  ❯ Non-empty domain: 
#    • id: [2, 2] (INT32)
#  ❯ Size: 3.13 KiB
#  ❯ Cell num: 1
#  ❯ Timestamp range: [2025-08-18 16:12:55 UTC, 2025-08-18 16:12:55 UTC]
#  ❯ Format version: 22
#  ❯ Has consolidated metadata: FALSE
# 
# ── FRAGMENT #3 ──────────────────────────────────────────────────────────────────────────────────────────────
#  ❯ URI: file:///C:/Users/Constantine/AppData/Local/Temp/RtmpW2GPj3/file9f2c79ac397f/testarray1/__fragments/__1755533575000_1755533575000_47dcf6db63ae4d3ccafd388aadc164b7_22
#  ❯ Type: sparse
#  ❯ Non-empty domain: 
#    • id: [2, 2] (INT32)
#  ❯ Size: 3.14 KiB
#  ❯ Cell num: 1
#  ❯ Timestamp range: [2025-08-18 16:12:55 UTC, 2025-08-18 16:12:55 UTC]
#  ❯ Format version: 22
#  ❯ Has consolidated metadata: FALSE
# 
# ── FRAGMENT #4 ──────────────────────────────────────────────────────────────────────────────────────────────
#  ❯ URI: file:///C:/Users/Constantine/AppData/Local/Temp/RtmpW2GPj3/file9f2c79ac397f/testarray1/__fragments/__1755533581000_1755533581000_1ae96fac8f9cc5b7e8e03bff730d81d4_22
#  ❯ Type: sparse
#  ❯ Non-empty domain: 
#    • id: [3, 3] (INT32)
#  ❯ Size: 3.13 KiB
#  ❯ Cell num: 1
#  ❯ Timestamp range: [2025-08-18 16:13:01 UTC, 2025-08-18 16:13:01 UTC]
#  ❯ Format version: 22
#  ❯ Has consolidated metadata: FALSE

Appendix


demo_tstamped_arrays <- function(uri, frags = 3) {

  ts <- as.POSIXct(c("2025-08-18 16:12:50",
                     "2025-08-18 16:12:55",
                     "2025-08-18 16:13:01"),
                   tz = "UTC")

  df <- data.frame(id = 1:frags, val = 1:frags)
  tiledb::fromDataFrame(df, uri, col_index = 1, mode = "schema_only", allows_dups = FALSE)

  out <- vector("numeric", frags)

  arr <- tiledb::tiledb_array(uri)

  for (i in seq_len(frags) ) {

    tm <- ts[i]
    arr <- tiledb::tiledb_array_open_at(arr, "WRITE", timestamp = tm)
    arr[] <- data.frame(id = i, val = i)
    arr <- tiledb::tiledb_array_open_at(arr, "WRITE", timestamp = tm)
    tiledb::tiledb_put_metadata(arr, paste0("key", i), as.character(tm))
    arr <- tiledb::tiledb_array_close(arr)
  }
  ts
}

demo_tstamped_group <- function(uri) {

  ctx <- tiledb::tiledb_ctx(cached = FALSE)

  group_uri <- uri
  uri1 <- R6.tiledb:::file_path(group_uri, "testarray1")
  uri2 <- R6.tiledb:::file_path(group_uri, "testarray2")

  # create group @ t0
  grp <- tiledb::tiledb_group_create(group_uri, ctx = ctx)
  t0 <- Sys.time()
  Sys.sleep(2)

  # create arr1 and add as member @ t1
  arr1 <- demo_tstamped_arrays(uri1)
  grp <- tiledb::tiledb_group(group_uri, type = "WRITE", ctx = ctx)
  tiledb::tiledb_group_add_member(
    grp = grp,
    uri = uri1,
    relative = FALSE,
    name = basename(uri1)
  )
  grp <- tiledb::tiledb_group_close(grp)
  t1 <- Sys.time()

  Sys.sleep(2)

  # create arr2 and add as member @ t2
  arr2 <- demo_tstamped_arrays(uri2)

  grp <- tiledb::tiledb_group_open(grp, type = "WRITE")
  tiledb::tiledb_group_add_member(
    grp = grp,
    uri = uri2,
    relative = FALSE,
    name = basename(uri2)
  )
  grp <- tiledb::tiledb_group_close(grp)
  t2 <- Sys.time()

  list(uri = group_uri,
       group_ts = list(t0 = t0, t1 = t1, t2 = t2),
       arr1_ts = arr1,
       arr2_ts = arr2)
}