A comprehensive reference for the Ruby language binding for FoundationDB, a distributed transactional key-value database with ACID guarantees.
Module: FDB
Location: bindings/ruby/lib/
Entry File: fdb.rb
Version: 7.4.5
require 'fdb'The Ruby binding requires API version selection before any database operations. The network thread starts automatically upon opening a database connection.
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)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')FDB.stop()Stop the network thread. Should be called before application exit.
Example:
FDB.stop()FDB.optionsReturns network options object for configuring network-level settings.
Returns: Network options object
Example:
FDB.options.set_trace_enable('/var/log/fdb')The Database class represents a connection to FoundationDB and provides methods for creating transactions and performing transactional operations.
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}"
endtransact(&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 parameterReturns: 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
endThese methods provide transactional wrappers for single operations.
get(key)
get_key(key_selector)
get_range(begin_key, end_key, options = {})Perform transactional read operations.
Parameters:
key (String) - Key to readkey_selector (KeySelector) - Key selector for key resolutionbegin_key, end_key (String or KeySelector) - Range boundariesoptions (Hash) - Options including :limit, :reverse, :streaming_modeReturns:
get: Value bytes or nil if not foundget_key: Key bytesget_range: Array of KeyValue objectsExample:
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}"
endset(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 deletevalue (String) - Value to writebegin_key, end_key (String) - Range to clearprefix (String) - Prefix of keys to clearExample:
db['mykey'] = 'myvalue' # Using []= operator
db.set('mykey', 'myvalue')
db.clear('oldkey')
db.clear_range('temp_', 'temp_~')
db.clear_range_start_with('temp_')optionsReturns 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)db[key]
db[key] = valueConvenient access using subscript operators.
Example:
# Get value
value = db['user:123']
# Set value
db['user:123'] = 'John Doe'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 modifyparam (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<'))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 watchvalue (String) - Value to set (for set_and_watch)Returns:
watch() - Future that resolves when key changesget_and_watch() - Array [value, watch_future]set_and_watch() - Future that resolves when key changesclear_and_watch() - Future that resolves when key changesExample:
# 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').waitThe Transaction class provides read and write operations within an ACID transaction context.
get(key)Read a single key's value.
Parameters:
key (String) - Key to readReturns: 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']get_key(key_selector)Resolve a key selector to an actual key.
Parameters:
key_selector (KeySelector) - Key selector to resolveReturns: Future object that resolves to key bytes
Example:
selector = FDB::KeySelector.first_greater_than('prefix_')
future = tr.get_key(selector)
key = future.waitget_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 boundariesprefix (String) - Key prefix for rangeoptions (Hash) - Options hash with:
:limit (Integer) - Maximum number of results:reverse (Boolean) - Reverse order if true:streaming_mode (Integer) - Streaming mode constantReturns: 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)
endget_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 boundariesReturns: Future that resolves to size estimate (Integer)
Example:
size = tr.get_estimated_range_size_bytes('data:', 'data;').wait
puts "Estimated size: #{size} bytes"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 boundarieschunk_size (Integer) - Target chunk size in bytesReturns: 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
endset(key, value)Write a key-value pair.
Parameters:
key (String) - Key to writevalue (String) - Value to writeExample:
tr.set('user:123', 'John Doe')
# Or using subscript
tr['user:123'] = 'John Doe'clear(key)Delete a single key.
Parameters:
key (String) - Key to deleteExample:
tr.clear('user:123')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 clearExample:
tr.clear_range('temp_', 'temp_~')
tr.clear_range_start_with('temp_')Atomic operations perform read-modify-write operations atomically without conflicts.
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 modifyparam (String) - Value to add (encoded as little-endian integer)Example:
# Increment counter
tr.add('counter', [1].pack('q<'))bit_and(key, param)
bit_or(key, param)
bit_xor(key, param)Atomically perform bitwise operations.
Parameters:
key (String) - Key to modifyparam (String) - Value for bitwise operationExample:
tr.bit_or('flags', [0x04].pack('C'))
tr.bit_and('mask', [0xFF].pack('C'))
tr.bit_xor('toggle', [0x01].pack('C'))min(key, param)
max(key, param)Atomically set key to minimum or maximum of current and provided value.
Parameters:
key (String) - Key to modifyparam (String) - Value to compareExample:
tr.min('low_watermark', [100].pack('q<'))
tr.max('high_watermark', [1000].pack('q<'))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(key, param)Atomically compare key's value with param and clear if equal.
Parameters:
key (String) - Key to potentially clearparam (String) - Value to compare againstExample:
tr.compare_and_clear('lock', 'my_lock_id')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 committedon_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 handleReturns: 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
endreset()Reset transaction to initial state, discarding all operations.
Example:
tr.resetcancel()Cancel the transaction. Outstanding futures will raise errors.
Example:
tr.cancelwatch(key)Create a watch that triggers when the key changes. The watch must be committed before it becomes active.
Parameters:
key (String) - Key to watchReturns: 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.
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(version)Set the transaction's read version for snapshot reads at specific version.
Parameters:
version (Integer) - Version number to read atExample:
tr.set_read_version(12345678)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()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.waitget_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)"
endAdvanced methods for manually controlling transaction conflicts.
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 boundarieskey (String) - Single keyExample:
tr.add_read_conflict_range('critical:', 'critical;')
tr.add_read_conflict_key('metadata')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 boundarieskey (String) - Single keyExample:
tr.add_write_conflict_range('index:', 'index;')
tr.add_write_conflict_key('counter')snapshotGet 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').waitoptionsReturns 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_immediatetr[key]
tr[key] = valueConvenient access using subscript operators.
Example:
# Get value (blocks for result)
value = tr['user:123']
# Set value
tr['user:123'] = 'John Doe'The Future class represents an asynchronous operation result.
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 waitready?()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..."
endblock_until_ready()Block until ready but don't return the result.
Example:
commit_future = tr.commit
commit_future.block_until_ready
# Transaction is now committedcancel()Cancel the future operation.
Example:
future = tr.get('key')
future.cancelSimple class representing a key-value pair from range reads.
kv.key
kv.valueAccess key and value bytes.
Example:
tr.get_range('user:', 'user;').each do |kv|
puts "Key: #{kv.key}"
puts "Value: #{kv.value}"
endThe KeySelector class provides sophisticated key selection for range boundaries.
FDB::KeySelector.new(key, or_equal, offset)Create a key selector.
Parameters:
key (String) - Reference keyor_equal (Boolean) - Whether to include the reference keyoffset (Integer) - Offset from reference keyExample:
selector = FDB::KeySelector.new('mykey', true, 0)FDB::KeySelector.first_greater_than(key)Select the first key greater than the given key.
Parameters:
key (String) - Reference keyReturns: KeySelector object
Example:
selector = FDB::KeySelector.first_greater_than('prefix_')FDB::KeySelector.first_greater_or_equal(key)Select the first key greater than or equal to the given key.
Parameters:
key (String) - Reference keyReturns: KeySelector object
Example:
selector = FDB::KeySelector.first_greater_or_equal('start')FDB::KeySelector.last_less_than(key)Select the last key less than the given key.
Parameters:
key (String) - Reference keyReturns: KeySelector object
Example:
selector = FDB::KeySelector.last_less_than('end')FDB::KeySelector.last_less_or_equal(key)Select the last key less than or equal to the given key.
Parameters:
key (String) - Reference keyReturns: KeySelector object
Example:
selector = FDB::KeySelector.last_less_or_equal('finish')selector + offset
selector - offsetAdjust the selector offset.
Parameters:
offset (Integer) - Offset to add or subtractReturns: New KeySelector with adjusted offset
Example:
selector = FDB::KeySelector.first_greater_or_equal('key')
next_selector = selector + 1
prev_selector = selector - 1The FDB::Error class represents FoundationDB errors.
error.code
error.descriptionAccess error code and description.
Properties:
code (Integer) - Numeric error codedescription (String) - Human-readable error descriptionExample:
begin
tr.commit.wait
rescue FDB::Error => e
puts "Error #{e.code}: #{e.description}"
if e.code == 1020 # Not committed
# Handle specific error
end
endThe FDB::Tuple module provides tuple encoding for structured keys.
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 encodeReturns: 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'])FDB::Tuple.unpack(key)Decode bytes to tuple elements.
Parameters:
key (String) - Encoded tuple bytesReturns: 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 insteadFDB::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.rangeNote: 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.
FDB::Tuple::SingleFloat.new(value)Represents a single-precision (32-bit) float for tuple encoding.
Parameters:
value (Float) - Float valueExample:
float_val = FDB::Tuple::SingleFloat.new(3.14)
key = FDB::Tuple.pack(['measurement', float_val])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 UUIDMethods:
data - Returns the 16-byte binary UUID datato_s - Returns hexadecimal string representation of the UUID<=> (other) - Compare two UUIDs for sortingExample:
# 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 UUIDNote: The UUID must be exactly 16 bytes. Passing data of any other length will raise an error (error code 2268: invalid_uuid_size).
The FDB::Subspace class provides key prefixing for namespace isolation.
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")key()Get the raw byte prefix of the subspace.
Returns: Prefix bytes (String)
Example:
prefix = subspace.keypack(items = [])Pack tuple items with the subspace prefix.
Parameters:
items (Array) - Tuple elementsReturns: Encoded key with prefix (String)
Example:
subspace = FDB::Subspace.new(['users'])
key = subspace.pack([123, 'profile'])
tr.set(key, 'data')unpack(key)Unpack a key from the subspace, removing the prefix.
Parameters:
key (String) - Key to unpackReturns: Array of tuple elements (without prefix)
Example:
key = subspace.pack([123, 'name'])
elements = subspace.unpack(key)
# elements => [123, 'name']range(items = [])Get the key range for the subspace or a tuple prefix within it.
Parameters:
items (Array) - Optional tuple prefix elementsReturns: 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
endcontains?(key)Check if a key belongs to this subspace.
Parameters:
key (String) - Key to checkReturns: true if key starts with subspace prefix, false otherwise
Example:
if subspace.contains?(key)
elements = subspace.unpack(key)
endsubspace(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.
The directory layer provides automatic prefix allocation and management for organizing key spaces.
FDB.directoryAccess the default directory layer instance.
Returns: Default DirectoryLayer object
Example:
dir = FDB.directoryFDB::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 specificationExample:
dir_layer = FDB::DirectoryLayer.new(
node_subspace: FDB::Subspace.new(['dir_metadata']),
content_subspace: FDB::Subspace.new(['dir_content'])
)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 operationpath (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(db_or_tr, path, options = {})Create a new directory. Raises error if it already exists.
Parameters:
db_or_tr (Database or Transaction) - Context for operationpath (Array or String) - Directory pathoptions (Hash) - Options including :layer, :prefixReturns: DirectorySubspace object
Example:
dir = FDB.directory.create(db, ['app', 'new_module'])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 operationpath (Array or String) - Directory pathoptions (Hash) - Options including :layer for validationReturns: DirectorySubspace object
Example:
dir = FDB.directory.open(db, ['app', 'users'])
# With layer validation
dir = FDB.directory.open(db, ['cache'], layer: 'cache_v1')exists?(db_or_tr, path)Check if a directory exists.
Parameters:
db_or_tr (Database or Transaction) - Context for operationpath (Array or String) - Directory pathReturns: true if exists, false otherwise
Example:
if FDB.directory.exists?(db, ['app', 'users'])
dir = FDB.directory.open(db, ['app', 'users'])
endlist(db_or_tr, path = [])List immediate subdirectories of a directory.
Parameters:
db_or_tr (Database or Transaction) - Context for operationpath (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(db_or_tr, old_path, new_path)Move/rename a directory.
Parameters:
db_or_tr (Database or Transaction) - Context for operationold_path (Array or String) - Current directory pathnew_path (Array or String) - New directory pathReturns: DirectorySubspace at new path
Example:
dir = FDB.directory.move(db, ['app', 'users'], ['app', 'accounts'])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 operationpath (Array or String) - Directory pathReturns: 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'])The DirectorySubspace class combines directory and subspace functionality.
dir.layer
dir.pathAccess directory metadata.
Properties:
layer (String) - Layer identifier bytespath (Array) - Directory path componentsExample:
dir = FDB.directory.open(db, ['app', 'users'])
puts "Path: #{dir.path.inspect}" # ['app', 'users']
puts "Layer: #{dir.layer}"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'])The FDB::Locality module provides information about data distribution and server locations.
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 operationbegin_key, end_key (String) - Range boundariesReturns: 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)
endFDB::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 contextkey (String) - Key to queryReturns: 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(', ')}"
endThe 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.
:iterator
:want_all
:exact
:small
:medium
:large
:serialModes:
: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 accessExample:
# 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)
endOptions objects provide methods for configuring network, database, and transaction behavior.
Available through FDB.options.
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)Available through db.options.
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)Available through tr.options.
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_disableExample:
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 ...
endrequire '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.stopdb.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# 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# 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}"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'))
endmax_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
endNote: 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# 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!"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}"
enddb.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(', ')}"
endAlways 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.waitUse 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 guaranteesUse the directory layer when you need dynamic prefix allocation:
# Good - automatic prefix allocation
tenant_dir = FDB.directory.create_or_open(db, ['tenants', tenant_id])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
endUse 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;')Always set reasonable timeouts for transactions:
db.transact do |tr|
tr.options.set_timeout(5000) # 5 seconds
# ... operations ...
endAlways stop the network thread before exit:
begin
# Application code
ensure
FDB.stop
endCommon error codes you may encounter:
1007 - Transaction too old1009 - Request maybe delivered1020 - Not committed (retriable)1021 - Commit unknown result1025 - Transaction timed out1031 - Transaction cancelled1100 - Operation failed2000 - Broken promiseCheck error.code and error.description for details, and use on_error() for automatic retry handling.
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.0bindings/ruby/examples/