Skip to contents

This vignette covers advanced patterns and edge cases when working with artcore.

Connection Pooling Patterns

Single Operation (Auto-managed)

For simple one-off operations, let the higher-level packages manage connections:

# Higher-level packages handle connections internally
# Example: some_package::get_data(artist)

Multiple Operations (Shared Connection)

For batch operations, share a connection to reduce overhead:

cn <- dbc()
on.exit(dbd(cn))

# Pass connection to multiple functions
# Pass cn to functions that support it
stats <- DBI::dbGetQuery(cn, "SELECT * FROM app.artist_stats WHERE ...")
data <- DBI::dbGetQuery(cn, "SELECT * FROM app.artwork_index WHERE ...")
# ... more operations ...

Transaction Blocks

For operations that must succeed or fail together:

cn <- dbc()
on.exit(dbd(cn))

DBI::dbBegin(cn)
tryCatch(
  {
    DBI::dbExecute(cn, "UPDATE ...")
    DBI::dbExecute(cn, "INSERT ...")
    DBI::dbCommit(cn)
  },
  error = function(e) {
    DBI::dbRollback(cn)
    stop(e)
  }
)

CDN Batch Operations

Uploading Large Directories

For directories with many files, monitor progress:

# write_art_vault logs progress automatically
keys <- write_art_vault(artist, artwork, local_path = large_bundle)
#> ℹ art-vault Uploading 500 file(s) => uploads/88xxx.../99xxx.../
#> ℹ art-vault Progress: 100/500 files uploaded
#> ℹ art-vault Progress: 200/500 files uploaded
#> ...

Verifying Large Uploads

After uploading many files, verify the count:

expected_count <- length(list.files(bundle_dir, recursive = TRUE))
actual_count <- cdn_count_keys(
  paste0("uploads/", artist, "/", artwork),
  bucket = "art-vault"
)

if (actual_count != expected_count) {
  stop("Upload incomplete: expected ", expected_count, " got ", actual_count)
}

Soft Delete with Verification

# Always dry run first
cdn_soft_delete(artist, artwork, dry_run = TRUE)

# Review the output, then execute
cdn_soft_delete(artist, artwork)

# Verify deletion
stopifnot(!has_prefix("art-vault", paste0("uploads/", artist, "/", artwork)))
stopifnot(!has_object("art-public", paste0("thumbnails/", artist, "/", artwork, ".jpeg")))

UUID Validation

Validate Before Operations

# validate_uuid throws error for invalid format
validate_uuid(artist) # Returns TRUE or throws error

# Check prefix matches expected entity type
if (!startsWith(artist, "88")) {
  stop("Expected artist UUID (prefix 88), got: ", substr(artist, 1, 2))
}

Using Test UUIDs

Test UUIDs have extended prefixes for easy identification in production databases:

# Production UUID: 99xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# Test UUID:       90000000-xxxx-xxxx-xxxx-xxxxxxxxxxxx

artwork <- gen_artwork_id(test = TRUE)

# Easy to find and clean up test data
# SELECT * FROM app.artwork_index WHERE art_uuid LIKE '90000000%'

Error Handling Patterns

Graceful Degradation

get_thumbnail_url <- function(artist, artwork) {
  key <- paste0("thumbnails/", artist, "/", artwork, ".jpeg")

  if (has_object("art-public", key)) {
    cdn_asset_url("art-public", key, signed = FALSE)
  } else {
    # Return placeholder instead of failing
    cdn_asset_url("art-public", "site/images/placeholder.jpeg", signed = FALSE)
  }
}

Retry Logic for Transient Failures

upload_with_retry <- function(artist, artwork, local_path, max_attempts = 3) {
  for (i in seq_len(max_attempts)) {
    tryCatch(
      {
        return(write_art_vault(artist, artwork, local_path = local_path))
      },
      error = function(e) {
        if (i == max_attempts) stop(e)
        Sys.sleep(2^i) # Exponential backoff
        message("Retry ", i, "/", max_attempts, ": ", conditionMessage(e))
      }
    )
  }
}

Working with Multiple Databases

Primary vs Marketing Database

# Primary database (default) - artwork, artists, analytics
cn_main <- dbc()
result <- DBI::dbGetQuery(cn_main, "SELECT * FROM app.artist_index LIMIT 5")
dbd(cn_main)

# Marketing database - leads, waitlist, whitepaper downloads
cn_site <- dbc("artsite")
leads <- DBI::dbGetQuery(cn_site, "SELECT * FROM waitlist.entries LIMIT 5")
dbd(cn_site)

Performance Tips

Minimize Connection Overhead

# BAD: Opens/closes connection for each artwork
for (artwork in artworks) {
  stats <- get_stats(artist, artwork) # New connection each time
}

# GOOD: Share connection across all operations
cn <- dbc()
on.exit(dbd(cn))
for (artwork in artworks) {
  stats <- get_stats(artist, artwork, cn = cn)
}

Batch CDN Checks

# BAD: Individual checks
for (artwork in artworks) {
  has_object("art-public", paste0("thumbnails/", artist, "/", artwork, ".jpeg"))
}

# GOOD: List once, filter locally
keys <- cdn_list_keys("art-public", paste0("thumbnails/", artist, "/"))
existing <- vapply(artworks, function(a) {
  any(grepl(a, keys))
}, logical(1))

Debugging

Enable Verbose Logging

artcore uses rdstools for logging. All operations log their actions:

✔ Connected user@host:5432/artalytics
ℹ (art-vault) Upload to Prefix -> uploads/88xxx.../99xxx.../
ℹ art-vault Uploading 5 file(s) => uploads/88xxx.../99xxx.../
✔ art-vault Vault upload complete: 5 objects
ℹ Disconnected

Check Environment State

# View all ART_* environment variables
env_vars <- Sys.getenv()
art_vars <- env_vars[grepl("^ART_", names(env_vars))]
print(art_vars)