or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

examples

edge-cases.mdreal-world-scenarios.md
index.md
tile.json

ruby-api.mddocs/reference/

FoundationDB Ruby API Reference

A comprehensive reference for the Ruby language binding for FoundationDB, a distributed transactional key-value database with ACID guarantees.

Package Information

Module: FDB Location: bindings/ruby/lib/ Entry File: fdb.rb Version: 7.4.5

Installation

require 'fdb'

Initialization and Connection

Capabilities

The Ruby binding requires API version selection before any database operations. The network thread starts automatically upon opening a database connection.

API Version Selection { .api }

FDB.api_version(version)

Select the FoundationDB API version (required first call).

Parameters:

  • version (Integer) - API version number (e.g., 740 for version 7.4.0)

Example:

FDB.api_version(740)

Database Connection { .api }

FDB.open(cluster_file = nil)

Open a database connection. Returns a Database object. The network thread starts automatically.

Parameters:

  • cluster_file (String, optional) - Path to cluster file. If nil, uses default cluster file location.

Returns: Database object

Example:

db = FDB.open
# Or with specific cluster file
db = FDB.open('/etc/foundationdb/fdb.cluster')

Network Control { .api }

FDB.stop()

Stop the network thread. Should be called before application exit.

Example:

FDB.stop()

Network Options { .api }

FDB.options

Returns network options object for configuring network-level settings.

Returns: Network options object

Example:

FDB.options.set_trace_enable('/var/log/fdb')

Database Class

The Database class represents a connection to FoundationDB and provides methods for creating transactions and performing transactional operations.

Capabilities

Transaction Management { .api }

create_transaction()

Create a new transaction.

Returns: Transaction object

Example:

tr = db.create_transaction
begin
  tr.set('key', 'value')
  tr.commit.wait
rescue FDB::Error => e
  puts "Error: #{e.description}"
end

Transactional Execution { .api }

transact(&block)

Execute a block with automatic retry logic. The block receives a transaction object as a parameter. Automatically handles retryable errors.

Parameters:

  • block (Block) - Block that receives Transaction as parameter

Returns: Return value of the block

Example:

result = db.transact do |tr|
  value = tr.get('counter').value
  new_value = value.to_i + 1
  tr.set('counter', new_value.to_s)
  new_value
end

Direct Operations

These methods provide transactional wrappers for single operations.

Read Operations { .api }

get(key)
get_key(key_selector)
get_range(begin_key, end_key, options = {})

Perform transactional read operations.

Parameters:

  • key (String) - Key to read
  • key_selector (KeySelector) - Key selector for key resolution
  • begin_key, end_key (String or KeySelector) - Range boundaries
  • options (Hash) - Options including :limit, :reverse, :streaming_mode

Returns:

  • get: Value bytes or nil if not found
  • get_key: Key bytes
  • get_range: Array of KeyValue objects

Example:

value = db['mykey']  # Using [] operator
value = db.get('mykey')

keys = db.get_range('key_a', 'key_z', limit: 100, reverse: false)
keys.each do |kv|
  puts "#{kv.key}: #{kv.value}"
end

Write Operations { .api }

set(key, value)
clear(key)
clear_range(begin_key, end_key)
clear_range_start_with(prefix)

Perform transactional write operations.

Parameters:

  • key (String) - Key to write or delete
  • value (String) - Value to write
  • begin_key, end_key (String) - Range to clear
  • prefix (String) - Prefix of keys to clear

Example:

db['mykey'] = 'myvalue'  # Using []= operator
db.set('mykey', 'myvalue')
db.clear('oldkey')
db.clear_range('temp_', 'temp_~')
db.clear_range_start_with('temp_')

Database Options { .api }

options

Returns database options object for configuring database-level settings.

Returns: Database options object

Example:

db.options.set_location_cache_size(100000)
db.options.set_transaction_timeout(5000)

Dictionary-Style Access { .api }

db[key]
db[key] = value

Convenient access using subscript operators.

Example:

# Get value
value = db['user:123']

# Set value
db['user:123'] = 'John Doe'

Atomic Operations { .api }

The Database class provides convenience methods for atomic operations that automatically wrap the operation in a transaction.

add(key, param)
bit_and(key, param)
bit_or(key, param)
bit_xor(key, param)
min(key, param)
max(key, param)
set_versionstamped_key(key, param)
set_versionstamped_value(key, param)
compare_and_clear(key, param)

These methods automatically execute within a transaction with retry logic.

Parameters:

  • key (String) - Key to modify
  • param (String) - Parameter value (encoding depends on operation)

Example:

# Increment counter
db.add('counter', [1].pack('q<'))

# Set bitwise flag
db.bit_or('flags', [0x04].pack('C'))

# Update high watermark
db.max('high_watermark', [1000].pack('q<'))

Watch Operations { .api }

Monitor keys for changes and receive notifications when modified.

watch(key)
get_and_watch(key)
set_and_watch(key, value)
clear_and_watch(key)

Parameters:

  • key (String) - Key to watch
  • value (String) - Value to set (for set_and_watch)

Returns:

  • watch() - Future that resolves when key changes
  • get_and_watch() - Array [value, watch_future]
  • set_and_watch() - Future that resolves when key changes
  • clear_and_watch() - Future that resolves when key changes

Example:

# Simple watch
watch_future = db.watch('config_key')
# ... do other work ...
watch_future.wait  # Blocks until key changes

# Get current value and watch for changes
value, watch = db.get_and_watch('important_key')
puts "Current value: #{value}"
watch.wait  # Blocks until key changes

# Set value and watch for subsequent changes
db.set_and_watch('status', 'active').wait

Transaction Class

The Transaction class provides read and write operations within an ACID transaction context.

Capabilities

Read Operations

Single Key Read { .api }

get(key)

Read a single key's value.

Parameters:

  • key (String) - Key to read

Returns: Future object that resolves to value bytes or nil

Example:

future = tr.get('user:123')
value = future.wait  # Block and get result
# Or use the convenient form
value = tr['user:123']

Key Selector Resolution { .api }

get_key(key_selector)

Resolve a key selector to an actual key.

Parameters:

  • key_selector (KeySelector) - Key selector to resolve

Returns: Future object that resolves to key bytes

Example:

selector = FDB::KeySelector.first_greater_than('prefix_')
future = tr.get_key(selector)
key = future.wait

Range Read { .api }

get_range(begin_key, end_key, options = {})
get_range_start_with(prefix, options = {})

Read a range of key-value pairs.

Parameters:

  • begin_key, end_key (String or KeySelector) - Range boundaries
  • prefix (String) - Key prefix for range
  • options (Hash) - Options hash with:
    • :limit (Integer) - Maximum number of results
    • :reverse (Boolean) - Reverse order if true
    • :streaming_mode (Integer) - Streaming mode constant

Returns: Lazy Enumerator that yields KeyValue objects

Example:

# Iterate over range
tr.get_range('user:', 'user;', limit: 100).each do |kv|
  puts "#{kv.key}: #{kv.value}"
end

# Get items with prefix
tr.get_range_start_with('config_', reverse: true).each do |kv|
  process(kv)
end

Range Size Estimation { .api }

get_estimated_range_size_bytes(begin_key, end_key)

Estimate the storage size of a key range in bytes.

Parameters:

  • begin_key, end_key (String) - Range boundaries

Returns: Future that resolves to size estimate (Integer)

Example:

size = tr.get_estimated_range_size_bytes('data:', 'data;').wait
puts "Estimated size: #{size} bytes"

Range Split Points { .api }

get_range_split_points(begin_key, end_key, chunk_size)

Get suggested split points for parallel processing of a range.

WARNING: This method has known implementation bugs in the source code and may not work correctly. Use with caution or consider alternative approaches for range splitting.

Parameters:

  • begin_key, end_key (String) - Range boundaries
  • chunk_size (Integer) - Target chunk size in bytes

Returns: Future that resolves to array of key split points

Example:

splits = tr.get_range_split_points('large:', 'large;', 10_000_000).wait
splits.each do |split_key|
  # Process chunk in parallel
end

Write Operations

Set Key-Value { .api }

set(key, value)

Write a key-value pair.

Parameters:

  • key (String) - Key to write
  • value (String) - Value to write

Example:

tr.set('user:123', 'John Doe')
# Or using subscript
tr['user:123'] = 'John Doe'

Clear Key { .api }

clear(key)

Delete a single key.

Parameters:

  • key (String) - Key to delete

Example:

tr.clear('user:123')

Clear Range { .api }

clear_range(begin_key, end_key)
clear_range_start_with(prefix)

Delete all keys in a range.

Parameters:

  • begin_key, end_key (String) - Range boundaries (begin inclusive, end exclusive)
  • prefix (String) - Prefix of keys to clear

Example:

tr.clear_range('temp_', 'temp_~')
tr.clear_range_start_with('temp_')

Atomic Operations

Atomic operations perform read-modify-write operations atomically without conflicts.

Arithmetic Operations { .api }

add(key, param)

Atomically add an integer value to a key. The key's value is treated as a little-endian integer.

Parameters:

  • key (String) - Key to modify
  • param (String) - Value to add (encoded as little-endian integer)

Example:

# Increment counter
tr.add('counter', [1].pack('q<'))

Bitwise Operations { .api }

bit_and(key, param)
bit_or(key, param)
bit_xor(key, param)

Atomically perform bitwise operations.

Parameters:

  • key (String) - Key to modify
  • param (String) - Value for bitwise operation

Example:

tr.bit_or('flags', [0x04].pack('C'))
tr.bit_and('mask', [0xFF].pack('C'))
tr.bit_xor('toggle', [0x01].pack('C'))

Min/Max Operations { .api }

min(key, param)
max(key, param)

Atomically set key to minimum or maximum of current and provided value.

Parameters:

  • key (String) - Key to modify
  • param (String) - Value to compare

Example:

tr.min('low_watermark', [100].pack('q<'))
tr.max('high_watermark', [1000].pack('q<'))

Versionstamp Operations { .api }

set_versionstamped_key(key, param)
set_versionstamped_value(key, param)

Set a key or value with an incomplete versionstamp that will be filled in by the database on commit.

Parameters:

  • key (String) - Key (for versionstamped key, must contain incomplete versionstamp placeholder)
  • param (String) - Value (for versionstamped value, must contain incomplete versionstamp placeholder)

Example:

# Create key with versionstamp
incomplete_vs = "\xFF" * 10  # Placeholder
tr.set_versionstamped_key("log_#{incomplete_vs}", 'event data')

# Create value with versionstamp
tr.set_versionstamped_value('latest', incomplete_vs)

Compare and Clear { .api }

compare_and_clear(key, param)

Atomically compare key's value with param and clear if equal.

Parameters:

  • key (String) - Key to potentially clear
  • param (String) - Value to compare against

Example:

tr.compare_and_clear('lock', 'my_lock_id')

Transaction Control

Commit Transaction { .api }

commit()

Commit the transaction.

Returns: Future that completes when commit finishes

Example:

tr.set('key', 'value')
commit_future = tr.commit
commit_future.wait  # Block until committed

Error Handling { .api }

on_error(error)

Handle a transaction error with automatic retry logic. Returns a future that completes after appropriate retry delay.

Parameters:

  • error (FDB::Error) - Error to handle

Returns: Future that completes when ready to retry (or raises if non-retryable)

Example:

begin
  # ... transaction operations ...
  tr.commit.wait
rescue FDB::Error => e
  tr.on_error(e).wait
  retry
end

Reset Transaction { .api }

reset()

Reset transaction to initial state, discarding all operations.

Example:

tr.reset

Cancel Transaction { .api }

cancel()

Cancel the transaction. Outstanding futures will raise errors.

Example:

tr.cancel

Watch Key { .api }

watch(key)

Create a watch that triggers when the key changes. The watch must be committed before it becomes active.

Parameters:

  • key (String) - Key to watch

Returns: Future that completes when key changes

Example:

watch_future = tr.watch('config')
tr.commit.wait

# Later, in another thread
watch_future.wait  # Blocks until key changes
puts "Config changed!"

Note: Watches can also be created at the Database level using db.transact. The watch future remains valid after the transaction commits.

Version Operations

Get Read Version { .api }

get_read_version()

Get the transaction's read version.

Returns: Future that resolves to read version (Integer)

Example:

version = tr.get_read_version.wait
puts "Reading at version: #{version}"

Set Read Version { .api }

set_read_version(version)

Set the transaction's read version for snapshot reads at specific version.

Parameters:

  • version (Integer) - Version number to read at

Example:

tr.set_read_version(12345678)

Get Committed Version { .api }

get_committed_version()

Get the version at which the transaction was committed. Must be called after successful commit.

Returns: Version number (Integer)

Example:

tr.commit.wait
version = tr.get_committed_version
puts "Committed at version: #{version}"

Get Versionstamp { .api }

get_versionstamp()

Get the versionstamp for the transaction. Must be called after commit.

Returns: Future that resolves to 10-byte versionstamp

Example:

tr.set_versionstamped_value('latest', "\xFF" * 10)
tr.commit.wait
versionstamp = tr.get_versionstamp.wait

Get Transaction Size { .api }

get_approximate_size()

Get approximate transaction size in bytes.

Returns: Future that resolves to size estimate (Integer)

Example:

size = tr.get_approximate_size.wait
if size > 10_000_000
  puts "Warning: Large transaction (#{size} bytes)"
end

Conflict Ranges

Advanced methods for manually controlling transaction conflicts.

Read Conflict Ranges { .api }

add_read_conflict_range(begin_key, end_key)
add_read_conflict_key(key)

Add a read conflict range or key.

Parameters:

  • begin_key, end_key (String) - Range boundaries
  • key (String) - Single key

Example:

tr.add_read_conflict_range('critical:', 'critical;')
tr.add_read_conflict_key('metadata')

Write Conflict Ranges { .api }

add_write_conflict_range(begin_key, end_key)
add_write_conflict_key(key)

Add a write conflict range or key.

Parameters:

  • begin_key, end_key (String) - Range boundaries
  • key (String) - Single key

Example:

tr.add_write_conflict_range('index:', 'index;')
tr.add_write_conflict_key('counter')

Snapshot Access

Snapshot View { .api }

snapshot

Get a snapshot view of the transaction that performs reads without adding read conflicts.

Returns: Transaction object with snapshot semantics

Example:

# Normal read adds conflict
value1 = tr.get('key').wait

# Snapshot read does not add conflict
value2 = tr.snapshot.get('key').wait

Transaction Options

Options Object { .api }

options

Returns transaction options object for configuring transaction behavior.

Returns: Transaction options object

Example:

tr.options.set_timeout(5000)  # 5 second timeout
tr.options.set_retry_limit(3)
tr.options.set_priority_system_immediate

Dictionary-Style Access

Subscript Operators { .api }

tr[key]
tr[key] = value

Convenient access using subscript operators.

Example:

# Get value (blocks for result)
value = tr['user:123']

# Set value
tr['user:123'] = 'John Doe'

Future Class

The Future class represents an asynchronous operation result.

Capabilities

Wait for Result { .api }

wait()
value()

Block until the future is ready and return the result. Raises exception if the operation failed.

Returns: Operation result

Example:

future = tr.get('key')
value = future.wait  # Blocks until ready
# Or
value = future.value  # Alias for wait

Check Ready Status { .api }

ready?()

Check if the future is ready without blocking.

Returns: true if ready, false otherwise

Example:

future = tr.get('key')
if future.ready?
  value = future.wait
else
  puts "Still waiting..."
end

Block Without Result { .api }

block_until_ready()

Block until ready but don't return the result.

Example:

commit_future = tr.commit
commit_future.block_until_ready
# Transaction is now committed

Cancel Future { .api }

cancel()

Cancel the future operation.

Example:

future = tr.get('key')
future.cancel

KeyValue Class

Simple class representing a key-value pair from range reads.

Properties { .api }

kv.key
kv.value

Access key and value bytes.

Example:

tr.get_range('user:', 'user;').each do |kv|
  puts "Key: #{kv.key}"
  puts "Value: #{kv.value}"
end

KeySelector Class

The KeySelector class provides sophisticated key selection for range boundaries.

Capabilities

Constructor { .api }

FDB::KeySelector.new(key, or_equal, offset)

Create a key selector.

Parameters:

  • key (String) - Reference key
  • or_equal (Boolean) - Whether to include the reference key
  • offset (Integer) - Offset from reference key

Example:

selector = FDB::KeySelector.new('mykey', true, 0)

Static Factory Methods

First Greater Than { .api }

FDB::KeySelector.first_greater_than(key)

Select the first key greater than the given key.

Parameters:

  • key (String) - Reference key

Returns: KeySelector object

Example:

selector = FDB::KeySelector.first_greater_than('prefix_')

First Greater Or Equal { .api }

FDB::KeySelector.first_greater_or_equal(key)

Select the first key greater than or equal to the given key.

Parameters:

  • key (String) - Reference key

Returns: KeySelector object

Example:

selector = FDB::KeySelector.first_greater_or_equal('start')

Last Less Than { .api }

FDB::KeySelector.last_less_than(key)

Select the last key less than the given key.

Parameters:

  • key (String) - Reference key

Returns: KeySelector object

Example:

selector = FDB::KeySelector.last_less_than('end')

Last Less Or Equal { .api }

FDB::KeySelector.last_less_or_equal(key)

Select the last key less than or equal to the given key.

Parameters:

  • key (String) - Reference key

Returns: KeySelector object

Example:

selector = FDB::KeySelector.last_less_or_equal('finish')

Operators

Offset Adjustment { .api }

selector + offset
selector - offset

Adjust the selector offset.

Parameters:

  • offset (Integer) - Offset to add or subtract

Returns: New KeySelector with adjusted offset

Example:

selector = FDB::KeySelector.first_greater_or_equal('key')
next_selector = selector + 1
prev_selector = selector - 1

Error Class

The FDB::Error class represents FoundationDB errors.

Properties { .api }

error.code
error.description

Access error code and description.

Properties:

  • code (Integer) - Numeric error code
  • description (String) - Human-readable error description

Example:

begin
  tr.commit.wait
rescue FDB::Error => e
  puts "Error #{e.code}: #{e.description}"
  if e.code == 1020  # Not committed
    # Handle specific error
  end
end

Tuple Module

The FDB::Tuple module provides tuple encoding for structured keys.

Capabilities

Pack Tuple { .api }

FDB::Tuple.pack(t)

Encode a tuple of elements to bytes. Supports nested tuples, integers, strings, floats, booleans, and nil.

Parameters:

  • t (Array) - Array of elements to encode

Returns: Encoded bytes (String)

Example:

key = FDB::Tuple.pack(['user', 123, 'profile'])
# Result: Encoded bytes representing the tuple

# For prefixed keys, use Subspace instead
subspace = FDB::Subspace.new(['prefix'])
key = subspace.pack([42, 'name'])

Unpack Tuple { .api }

FDB::Tuple.unpack(key)

Decode bytes to tuple elements.

Parameters:

  • key (String) - Encoded tuple bytes

Returns: Array of decoded elements

Example:

packed = FDB::Tuple.pack(['user', 123])
elements = FDB::Tuple.unpack(packed)
# elements => ['user', 123]

# For prefixed keys, use Subspace#unpack instead

Tuple Range { .api }

FDB::Tuple.range(tuple = [])

Get the key range that encompasses all tuples starting with the given prefix.

Parameters:

  • tuple (Array) - Tuple prefix elements (default: empty array for all tuples)

Returns: Array of two keys [begin_key, end_key]

Example:

begin_key, end_key = FDB::Tuple.range(['user', 123])
tr.get_range(begin_key, end_key).each do |kv|
  # Iterate all keys with prefix ['user', 123]
end

# Get range for all tuples
begin_key, end_key = FDB::Tuple.range

Tuple Types

Note: The Ruby binding does not provide a FDB::Tuple::Versionstamp class. For versionstamped keys and values, use raw byte strings with the incomplete versionstamp placeholder (10 bytes of \xFF) and the set_versionstamped_key or set_versionstamped_value transaction methods.

SingleFloat { .api }

FDB::Tuple::SingleFloat.new(value)

Represents a single-precision (32-bit) float for tuple encoding.

Parameters:

  • value (Float) - Float value

Example:

float_val = FDB::Tuple::SingleFloat.new(3.14)
key = FDB::Tuple.pack(['measurement', float_val])

UUID { .api }

FDB::Tuple::UUID.new(data)

Represents a 128-bit UUID for tuple encoding. The UUID is stored as 16 bytes of binary data.

Parameters:

  • data (String) - 16-byte binary string representing the UUID

Methods:

  • data - Returns the 16-byte binary UUID data
  • to_s - Returns hexadecimal string representation of the UUID
  • <=> (other) - Compare two UUIDs for sorting

Example:

# Create UUID from 16 bytes
uuid_bytes = "\x01\x23\x45\x67\x89\xAB\xCD\xEF" \
             "\x01\x23\x45\x67\x89\xAB\xCD\xEF"
uuid = FDB::Tuple::UUID.new(uuid_bytes)

# Use in tuple
key = FDB::Tuple.pack(['record', uuid])

# Compare UUIDs
other_uuid = FDB::Tuple::UUID.new(other_bytes)
if uuid < other_uuid
  # UUIDs are sortable
end

# Get hex representation
puts uuid.to_s  # Outputs hex string of UUID

Note: The UUID must be exactly 16 bytes. Passing data of any other length will raise an error (error code 2268: invalid_uuid_size).

Subspace Class

The FDB::Subspace class provides key prefixing for namespace isolation.

Capabilities

Constructor { .api }

FDB::Subspace.new(prefix_tuple = [], raw_prefix = '')

Create a subspace with tuple or raw prefix.

Parameters:

  • prefix_tuple (Array) - Tuple elements for prefix (default: empty array)
  • raw_prefix (String) - Raw byte prefix to prepend (default: empty string)

Example:

# From tuple
subspace = FDB::Subspace.new(['app', 'users'])

# From raw bytes only
subspace = FDB::Subspace.new([], "\x01\x02")

# Combine raw prefix and tuple
subspace = FDB::Subspace.new(['users'], "\x01")

Get Raw Prefix { .api }

key()

Get the raw byte prefix of the subspace.

Returns: Prefix bytes (String)

Example:

prefix = subspace.key

Pack Tuple { .api }

pack(items = [])

Pack tuple items with the subspace prefix.

Parameters:

  • items (Array) - Tuple elements

Returns: Encoded key with prefix (String)

Example:

subspace = FDB::Subspace.new(['users'])
key = subspace.pack([123, 'profile'])
tr.set(key, 'data')

Unpack Key { .api }

unpack(key)

Unpack a key from the subspace, removing the prefix.

Parameters:

  • key (String) - Key to unpack

Returns: Array of tuple elements (without prefix)

Example:

key = subspace.pack([123, 'name'])
elements = subspace.unpack(key)
# elements => [123, 'name']

Get Range { .api }

range(items = [])

Get the key range for the subspace or a tuple prefix within it.

Parameters:

  • items (Array) - Optional tuple prefix elements

Returns: Array of two keys [begin_key, end_key]

Example:

# All keys in subspace
begin_key, end_key = subspace.range

# Keys with specific prefix in subspace
begin_key, end_key = subspace.range([123])
tr.get_range(begin_key, end_key).each do |kv|
  # Process keys
end

Check Containment { .api }

contains?(key)

Check if a key belongs to this subspace.

Parameters:

  • key (String) - Key to check

Returns: true if key starts with subspace prefix, false otherwise

Example:

if subspace.contains?(key)
  elements = subspace.unpack(key)
end

Create Nested Subspace { .api }

subspace(items)
[item]

Create a nested subspace by appending tuple elements.

Parameters:

  • items (Array) - Tuple elements to append (for subspace method)
  • item (Any) - Single tuple element to append (for [] operator)

Returns: New Subspace object

Example:

users_subspace = FDB::Subspace.new(['app', 'users'])
user_subspace = users_subspace.subspace([123])

# Using [] operator with single item only
user_subspace = users_subspace[123]

profile_key = user_subspace.pack(['profile'])

Note: The [] operator only accepts a single argument, not multiple items. To append multiple tuple elements, use subspace([item1, item2, ...]) instead.

Directory Layer

The directory layer provides automatic prefix allocation and management for organizing key spaces.

Module: FDB::DirectoryLayer

Default Instance { .api }

FDB.directory

Access the default directory layer instance.

Returns: Default DirectoryLayer object

Example:

dir = FDB.directory

DirectoryLayer Class

Constructor { .api }

FDB::DirectoryLayer.new(options = {})

Create a custom directory layer.

Parameters:

  • options (Hash) - Options hash with:
    • :node_subspace (Subspace) - Subspace for directory metadata
    • :content_subspace (Subspace) - Subspace for directory content
    • :allow_manual_prefixes (Boolean) - Allow manual prefix specification

Example:

dir_layer = FDB::DirectoryLayer.new(
  node_subspace: FDB::Subspace.new(['dir_metadata']),
  content_subspace: FDB::Subspace.new(['dir_content'])
)

Directory Operations

Create or Open { .api }

create_or_open(db_or_tr, path, options = {})

Open a directory if it exists, or create it if it doesn't.

Parameters:

  • db_or_tr (Database or Transaction) - Context for operation
  • path (Array or String) - Directory path (array of path components or single string)
  • options (Hash) - Options with:
    • :layer (String) - Layer identifier for validation
    • :prefix (String) - Manual prefix (requires allow_manual_prefixes)

Returns: DirectorySubspace object

Example:

# Open or create directory
dir = FDB.directory.create_or_open(db, ['app', 'users'])

# With layer validation
dir = FDB.directory.create_or_open(db, ['cache'], layer: 'cache_v1')

# Single path string (converted to array)
dir = FDB.directory.create_or_open(db, 'users')

Create Directory { .api }

create(db_or_tr, path, options = {})

Create a new directory. Raises error if it already exists.

Parameters:

  • db_or_tr (Database or Transaction) - Context for operation
  • path (Array or String) - Directory path
  • options (Hash) - Options including :layer, :prefix

Returns: DirectorySubspace object

Example:

dir = FDB.directory.create(db, ['app', 'new_module'])

Open Directory { .api }

open(db_or_tr, path, options = {})

Open an existing directory. Raises error if it doesn't exist.

Parameters:

  • db_or_tr (Database or Transaction) - Context for operation
  • path (Array or String) - Directory path
  • options (Hash) - Options including :layer for validation

Returns: DirectorySubspace object

Example:

dir = FDB.directory.open(db, ['app', 'users'])

# With layer validation
dir = FDB.directory.open(db, ['cache'], layer: 'cache_v1')

Check Existence { .api }

exists?(db_or_tr, path)

Check if a directory exists.

Parameters:

  • db_or_tr (Database or Transaction) - Context for operation
  • path (Array or String) - Directory path

Returns: true if exists, false otherwise

Example:

if FDB.directory.exists?(db, ['app', 'users'])
  dir = FDB.directory.open(db, ['app', 'users'])
end

List Subdirectories { .api }

list(db_or_tr, path = [])

List immediate subdirectories of a directory.

Parameters:

  • db_or_tr (Database or Transaction) - Context for operation
  • path (Array or String) - Directory path (empty for root)

Returns: Array of subdirectory names (Strings)

Example:

# List root directories
subdirs = FDB.directory.list(db)
# subdirs => ['app', 'cache', 'temp']

# List subdirectories of specific path
subdirs = FDB.directory.list(db, ['app'])
# subdirs => ['users', 'config', 'sessions']

Move Directory { .api }

move(db_or_tr, old_path, new_path)

Move/rename a directory.

Parameters:

  • db_or_tr (Database or Transaction) - Context for operation
  • old_path (Array or String) - Current directory path
  • new_path (Array or String) - New directory path

Returns: DirectorySubspace at new path

Example:

dir = FDB.directory.move(db, ['app', 'users'], ['app', 'accounts'])

Remove Directory { .api }

remove(db_or_tr, path)
remove_if_exists(db_or_tr, path)

Remove a directory and all its contents.

Parameters:

  • db_or_tr (Database or Transaction) - Context for operation
  • path (Array or String) - Directory path

Returns: true if removed, false if didn't exist (for remove_if_exists)

Example:

# Remove (raises if doesn't exist)
FDB.directory.remove(db, ['temp', 'cache'])

# Remove if exists (no error if missing)
FDB.directory.remove_if_exists(db, ['temp', 'old_data'])

DirectorySubspace Class

The DirectorySubspace class combines directory and subspace functionality.

Properties { .api }

dir.layer
dir.path

Access directory metadata.

Properties:

  • layer (String) - Layer identifier bytes
  • path (Array) - Directory path components

Example:

dir = FDB.directory.open(db, ['app', 'users'])
puts "Path: #{dir.path.inspect}"  # ['app', 'users']
puts "Layer: #{dir.layer}"

Subspace Methods { .api }

key()
pack()
unpack()
range()
contains?()
subspace()
[]

DirectorySubspace inherits all Subspace methods.

Example:

dir = FDB.directory.create_or_open(db, ['users'])

# Use as subspace
key = dir.pack([123, 'profile'])
db.transact do |tr|
  tr.set(key, 'John Doe')
end

# Create nested subspace
user_space = dir[123]
profile_key = user_space.pack(['profile'])

Locality Module

The FDB::Locality module provides information about data distribution and server locations.

Capabilities

Get Boundary Keys { .api }

FDB::Locality.get_boundary_keys(db_or_tr, begin_key, end_key)

Get keys that divide a range into contiguous chunks stored on different servers.

Parameters:

  • db_or_tr (Database or Transaction) - Context for operation
  • begin_key, end_key (String) - Range boundaries

Returns: Array of boundary keys

Example:

boundaries = FDB::Locality.get_boundary_keys(db, 'data:', 'data;')
boundaries.each_cons(2) do |begin_key, end_key|
  # Process each chunk that resides on a single server
  process_chunk(begin_key, end_key)
end

Get Addresses for Key { .api }

FDB::Locality.get_addresses_for_key(db_or_tr, key)

Get server addresses that store replicas of a key.

Parameters:

  • db_or_tr (Database or Transaction) - Database or Transaction context
  • key (String) - Key to query

Returns: Array of server address strings

Example:

db.transact do |tr|
  addresses = FDB::Locality.get_addresses_for_key(tr, 'important_key')
  puts "Key stored on servers: #{addresses.join(', ')}"
end

StreamingMode Constants

The FDB::StreamingMode module defines constants for controlling range read behavior.

Note: In the Ruby binding, streaming modes are specified using symbols (like :iterator, :want_all), not module constants.

Available Modes { .api }

:iterator
:want_all
:exact
:small
:medium
:large
:serial

Modes:

  • :iterator - Balanced iteration (default, good for most cases)
  • :want_all - Fetch entire range in one request
  • :exact - Fetch exactly the specified limit
  • :small - Hint that range is small (optimizes for small data)
  • :medium - Hint that range is medium-sized
  • :large - Hint that range is large (optimizes for large data)
  • :serial - Minimize latency for serial access

Example:

# Efficient for large ranges - use symbol :large
tr.get_range('data:', 'data;',
  streaming_mode: :large
).each do |kv|
  process(kv)
end

# Get all at once - use symbol :want_all
tr.get_range('config:', 'config;',
  streaming_mode: :want_all
).each do |kv|
  process(kv)
end

# Minimize latency - use symbol :serial
tr.get_range('items:', 'items;',
  streaming_mode: :serial
).each do |kv|
  process(kv)
end

Options Objects

Options objects provide methods for configuring network, database, and transaction behavior.

Network Options

Available through FDB.options.

Example Methods { .api }

FDB.options.set_trace_enable(path)
FDB.options.set_trace_roll_size(size)
FDB.options.set_knob(name, value)
FDB.options.set_tls_cert_path(path)
FDB.options.set_tls_key_path(path)
FDB.options.set_tls_ca_path(path)

Example:

FDB.options.set_trace_enable('/var/log/fdb_client')
FDB.options.set_trace_roll_size(10_000_000)

Database Options

Available through db.options.

Example Methods { .api }

db.options.set_location_cache_size(size)
db.options.set_max_watches(count)
db.options.set_datacenter_id(id)
db.options.set_machine_id(id)
db.options.set_transaction_timeout(milliseconds)
db.options.set_transaction_retry_limit(count)

Example:

db.options.set_location_cache_size(100_000)
db.options.set_transaction_timeout(5000)

Transaction Options

Available through tr.options.

Example Methods { .api }

tr.options.set_timeout(milliseconds)
tr.options.set_retry_limit(count)
tr.options.set_max_retry_delay(milliseconds)
tr.options.set_priority_system_immediate
tr.options.set_priority_batch
tr.options.set_causal_read_risky
tr.options.set_read_your_writes_disable
tr.options.set_access_system_keys
tr.options.set_snapshot_ryw_enable
tr.options.set_snapshot_ryw_disable

Example:

db.transact do |tr|
  # Set 5 second timeout
  tr.options.set_timeout(5000)

  # Limit retries
  tr.options.set_retry_limit(10)

  # Set high priority
  tr.options.set_priority_system_immediate

  # Disable read-your-writes
  tr.options.set_read_your_writes_disable

  # ... transaction operations ...
end

Usage Patterns

Basic Read-Write Transaction

require 'fdb'

FDB.api_version(740)
db = FDB.open

# Simple transactional operations
db.transact do |tr|
  # Read
  value = tr['user:123'].value

  # Write
  tr['user:123'] = 'John Doe'

  # Delete
  tr.clear('old_key')

  # Transaction commits automatically
end

FDB.stop

Range Iteration

db.transact do |tr|
  # Iterate over range
  tr.get_range('user:', 'user;', limit: 100).each do |kv|
    user_id = kv.key.sub('user:', '')
    user_data = kv.value
    puts "User #{user_id}: #{user_data}"
  end
end

Using Tuples and Subspaces

# Define subspace for users
users = FDB::Subspace.new(['app', 'users'])

db.transact do |tr|
  # Write with tuple keys
  user_id = 123
  tr.set(users.pack([user_id, 'name']), 'John Doe')
  tr.set(users.pack([user_id, 'email']), 'john@example.com')

  # Read back
  name = tr[users.pack([user_id, 'name'])].value

  # Iterate all fields for user
  begin_key, end_key = users.range([user_id])
  tr.get_range(begin_key, end_key).each do |kv|
    field = users.unpack(kv.key).last
    puts "#{field}: #{kv.value}"
  end
end

Using Directories

# Open or create directory
users_dir = FDB.directory.create_or_open(db, ['app', 'users'])

db.transact do |tr|
  # Directory provides automatic prefix allocation
  key = users_dir.pack([123, 'profile'])
  tr.set(key, 'profile data')

  # Read back
  value = tr[key].value
end

# List directories
subdirs = FDB.directory.list(db, ['app'])
puts "Subdirectories: #{subdirs.inspect}"

Atomic Operations

db.transact do |tr|
  # Increment counter
  tr.add('counter', [1].pack('q<'))

  # Set max value
  tr.max('high_score', [1000].pack('q<'))

  # Toggle bits
  tr.bit_xor('flags', [0x01].pack('C'))
end

Error Handling

max_retries = 10
retry_count = 0

begin
  tr = db.create_transaction

  # Perform operations
  tr.set('key', 'value')
  tr.commit.wait

rescue FDB::Error => e
  retry_count += 1

  if retry_count <= max_retries
    tr.on_error(e).wait
    retry
  else
    puts "Transaction failed after #{max_retries} retries: #{e.description}"
    raise
  end
ensure
  tr.cancel if tr
end

Multi-Tenancy

Note: The Ruby binding does not expose a Tenant class or open_tenant() method directly. Multi-tenancy should be implemented using the Directory Layer or Subspaces for namespace isolation.

# Use Directory Layer for tenant isolation
tenant_dir = FDB.directory.create_or_open(db, ['tenants', 'customer_123'])

# Use tenant directory as a namespace
db.transact do |tr|
  key = tenant_dir.pack(['config'])
  tr.set(key, 'value')
  value = tr.get(key).value
end

# Or use Subspaces directly
tenant_space = FDB::Subspace.new(['tenant', 'customer_123'])
db.transact do |tr|
  tr.set(tenant_space.pack(['setting']), 'enabled')
  value = tr.get(tenant_space.pack(['setting'])).value
end

Watches

# Start a watch in one transaction
watch_future = db.transact do |tr|
  tr.watch('config_key')
end

# Later, in another thread or fiber
puts "Waiting for config change..."
watch_future.wait
puts "Config changed!"

Versionstamps

Note: The Ruby binding does not provide a FDB::Tuple::Versionstamp class. Use raw byte strings with incomplete versionstamp placeholders.

db.transact do |tr|
  # Create log entry with versionstamp
  # Incomplete versionstamp: 10 bytes of \xFF
  incomplete_vs = "\xFF" * 10

  # For versionstamped keys, construct key with placeholder
  # The database will replace the incomplete versionstamp on commit
  key = "log_#{incomplete_vs}"

  tr.set_versionstamped_key(key, 'event data')
  tr.commit.wait

  # Get the actual versionstamp after commit
  versionstamp = tr.get_versionstamp.wait
  puts "Logged at versionstamp: #{versionstamp.unpack('H*').first}"
end

Locality Queries

db.transact do |tr|
  # Get boundary keys for parallel processing
  boundaries = FDB::Locality.get_boundary_keys(tr, 'data:', 'data;')

  boundaries.each_cons(2) do |begin_key, end_key|
    # Each chunk can be processed in parallel
    Thread.new do
      process_chunk(begin_key, end_key)
    end
  end

  # Get server addresses for a key
  addresses = FDB::Locality.get_addresses_for_key(tr, 'critical_key')
  puts "Key replicated on: #{addresses.join(', ')}"
end

Best Practices

1. Use Transactional Wrappers

Always use transact for automatic retry handling:

# Good - automatic retry
db.transact do |tr|
  value = tr['counter'].value
  tr['counter'] = (value.to_i + 1).to_s
end

# Avoid - manual retry logic is error-prone
tr = db.create_transaction
tr.set('key', 'value')
tr.commit.wait

2. Organize Keys with Subspaces

Use subspaces and tuples for structured key organization:

# Good - structured keys
users = FDB::Subspace.new(['users'])
key = users.pack([user_id, 'email'])

# Avoid - string concatenation
key = "users:#{user_id}:email"  # Harder to parse, no ordering guarantees

3. Use Directories for Dynamic Modules

Use the directory layer when you need dynamic prefix allocation:

# Good - automatic prefix allocation
tenant_dir = FDB.directory.create_or_open(db, ['tenants', tenant_id])

4. Leverage Snapshot Reads

Use snapshot reads when you don't need conflict detection:

db.transact do |tr|
  # Write with conflicts
  tr['key'] = 'value'

  # Read without conflicts for reporting
  total_count = tr.snapshot.get_range('items:', 'items;').count
end

5. Handle Large Ranges Efficiently

Use appropriate streaming modes and consider splitting large ranges:

# For large ranges - use symbol :large
tr.get_range('data:', 'data;',
  streaming_mode: :large
).each do |kv|
  process(kv)
end

# Or split using boundaries
boundaries = FDB::Locality.get_boundary_keys(db, 'data:', 'data;')

6. Set Timeouts

Always set reasonable timeouts for transactions:

db.transact do |tr|
  tr.options.set_timeout(5000)  # 5 seconds
  # ... operations ...
end

7. Clean Up Resources

Always stop the network thread before exit:

begin
  # Application code
ensure
  FDB.stop
end

Error Codes Reference

Common error codes you may encounter:

  • 1007 - Transaction too old
  • 1009 - Request maybe delivered
  • 1020 - Not committed (retriable)
  • 1021 - Commit unknown result
  • 1025 - Transaction timed out
  • 1031 - Transaction cancelled
  • 1100 - Operation failed
  • 2000 - Broken promise

Check error.code and error.description for details, and use on_error() for automatic retry handling.

Version Compatibility

This documentation covers FoundationDB 7.4.5. The Ruby API is stable across minor versions, but always call api_version() with the version your code is designed for:

FDB.api_version(740)  # API version 740 = FDB 7.4.0

Additional Resources

  • Official FoundationDB Documentation: https://apple.github.io/foundationdb/
  • Ruby API Examples: bindings/ruby/examples/
  • Error Codes Reference: https://apple.github.io/foundationdb/api-error-codes.html
  • Data Modeling Guide: https://apple.github.io/foundationdb/data-modeling.html