The Subspace Layer provides namespace isolation through key prefixing, enabling organized, hierarchical data management in FoundationDB. Subspaces create isolated key ranges that prevent naming conflicts and allow for clean data organization patterns.
A Subspace is a logical namespace within FoundationDB that automatically prefixes all keys with a specific byte string. This provides:
Package: fdb
Module: fdb.subspace_impl
Class: Subspace
from fdb.subspace_impl import Subspace
# Or via main module
from fdb import SubspacePackage: github.com/apple/foundationdb/bindings/go/src/fdb/subspace
Type: Subspace (interface)
import "github.com/apple/foundationdb/bindings/go/src/fdb/subspace"Package: com.apple.foundationdb.subspace
Class: Subspace
import com.apple.foundationdb.subspace.Subspace;Module: FDB
Class: FDB::Subspace
require 'fdb'
# Subspace available as FDB::SubspaceEvery subspace has a raw prefix - a byte string that is automatically prepended to all keys. When you pack a tuple or key within a subspace, the prefix is added to the front, creating an isolated key range.
Subspace Prefix: [0x01, 0x02]
Packed Key: ("user", 123)
Final Key: [0x01, 0x02] + <tuple-encoded: "user", 123>Subspaces work seamlessly with the Tuple Layer. You can create subspaces from tuples and pack tuples within subspaces:
Subspace from tuple ("users",)
Pack ("john", 42) within that subspace
Result: <tuple: "users", "john", 42>Create hierarchical structures by nesting subspaces:
Root subspace: ("app",)
├─ Subspace: ("app", "users")
│ ├─ ("app", "users", "profile")
│ └─ ("app", "users", "settings")
└─ Subspace: ("app", "products")Subspaces define contiguous key ranges. All keys in a subspace are consecutive in the database's sorted order, enabling efficient range operations.
Subspace(prefixTuple: tuple = (), rawPrefix: Optional[bytes] = None) -> SubspaceCreate a new subspace with a raw byte prefix.
Parameters:
prefixTuple: Tuple prefix (encoded to bytes if provided)rawPrefix: Raw byte prefix (takes precedence over prefixTuple)Returns: New Subspace instance
Example:
from fdb.subspace_impl import Subspace
# Empty subspace (no prefix)
root = Subspace()
# Raw byte prefix
users = Subspace(rawPrefix=b'\x01\x00users')
# From tuple (uses tuple encoding)
products = Subspace(prefixTuple=('products',))subspace.FromBytes(b []byte) SubspaceCreate a subspace from raw bytes.
Parameters:
b: Raw byte prefixReturns: New Subspace
Example:
import "github.com/apple/foundationdb/bindings/go/src/fdb/subspace"
// From raw bytes
users := subspace.FromBytes([]byte{0x01, 0x00})
// Empty subspace
root := subspace.FromBytes([]byte{})Subspace()
Subspace(byte[] rawPrefix)Create a new subspace.
Parameters:
rawPrefix: Raw byte prefix (optional, empty if not provided)Returns: New Subspace instance
Example:
import com.apple.foundationdb.subspace.Subspace;
// Empty subspace
Subspace root = new Subspace();
// Raw byte prefix
Subspace users = new Subspace(new byte[]{0x01, 0x00});FDB::Subspace.new(prefix_tuple = [], raw_prefix = '') -> SubspaceCreate a new subspace.
Parameters:
prefix_tuple: Tuple prefix (array of elements)raw_prefix: Raw byte prefix (string, takes precedence)Returns: New Subspace instance
Example:
# Empty subspace
root = FDB::Subspace.new
# Raw byte prefix
users = FDB::Subspace.new([], "\x01\x00users")
# From tuple array
products = FDB::Subspace.new(['products'])
# Note: prefix_tuple and raw_prefix can also be specified as keyword argumentsSubspace(prefix: tuple = ()) -> SubspaceCreate a subspace from a tuple prefix.
Example:
from fdb.subspace_impl import Subspace
# Single element tuple
users = Subspace(('users',))
# Multi-element tuple
user_profiles = Subspace(('users', 'profiles'))
# Numeric prefixes
partition = Subspace((0, 1, 'data'))subspace.Sub(el ...tuple.TupleElement) SubspaceCreate a subspace from tuple elements (package-level function).
Parameters:
el: Variable number of tuple elementsReturns: New Subspace
Example:
import (
"github.com/apple/foundationdb/bindings/go/src/fdb/subspace"
"github.com/apple/foundationdb/bindings/go/src/fdb/tuple"
)
// Single element
users := subspace.Sub("users")
// Multiple elements
userProfiles := subspace.Sub("users", "profiles")
// Mixed types
partition := subspace.Sub(int64(0), int64(1), "data")Subspace(Tuple prefix)Create a subspace from a tuple.
Parameters:
prefix: Tuple to use as prefixReturns: New Subspace instance
Example:
import com.apple.foundationdb.subspace.Subspace;
import com.apple.foundationdb.tuple.Tuple;
// Single element
Subspace users = new Subspace(Tuple.from("users"));
// Multiple elements
Subspace userProfiles = new Subspace(Tuple.from("users", "profiles"));
// Mixed types
Subspace partition = new Subspace(Tuple.from(0L, 1L, "data"));FDB::Subspace.new(prefix = []) -> SubspaceCreate a subspace from a tuple array.
Example:
# Single element
users = FDB::Subspace.new(['users'])
# Multiple elements
user_profiles = FDB::Subspace.new(['users', 'profiles'])
# Mixed types
partition = FDB::Subspace.new([0, 1, 'data'])Add the subspace prefix to tuple elements to create fully-qualified keys.
Subspace.pack(items: tuple = ()) -> bytesPack a tuple with the subspace prefix.
Parameters:
items: Tuple of elements to pack (default: empty tuple)Returns: Packed key bytes with prefix
Example:
users = Subspace(('users',))
# Pack user ID
user_key = users.pack((123,))
# Result: <tuple: "users", 123>
# Pack user ID and field
profile_key = users.pack((123, 'profile'))
# Result: <tuple: "users", 123, "profile">
# Get just the prefix
prefix_key = users.pack()
# Result: <tuple: "users">Subspace.Pack(t tuple.Tuple) fdb.KeyPack a tuple with the subspace prefix.
Parameters:
t: Tuple to packReturns: Packed key as fdb.Key
Example:
users := subspace.Sub("users")
// Pack user ID
userKey := users.Pack(tuple.Tuple{int64(123)})
// Pack user ID and field
profileKey := users.Pack(tuple.Tuple{int64(123), "profile"})
// Get just the prefix
prefixKey := users.Pack(tuple.Tuple{})byte[] pack(Tuple tuple)
byte[] pack()Pack a tuple with the subspace prefix.
Parameters:
tuple: Tuple to pack (optional)Returns: Packed key bytes
Example:
Subspace users = new Subspace(Tuple.from("users"));
// Pack user ID
byte[] userKey = users.pack(Tuple.from(123L));
// Pack user ID and field
byte[] profileKey = users.pack(Tuple.from(123L, "profile"));
// Get just the prefix
byte[] prefixKey = users.pack();Subspace.pack(items = []) -> StringPack a tuple array with the subspace prefix.
Parameters:
items: Array of tuple elements (default: empty array)Returns: Packed key bytes as String
Example:
users = FDB::Subspace.new(['users'])
# Pack user ID
user_key = users.pack([123])
# Pack user ID and field
profile_key = users.pack([123, 'profile'])
# Get just the prefix
prefix_key = users.packRemove the subspace prefix and decode the remaining tuple.
Subspace.unpack(key: bytes) -> tupleUnpack a key, removing the subspace prefix.
Parameters:
key: Key bytes to unpackReturns: Tuple of unpacked elements (without prefix)
Raises:
ValueError: If key doesn't start with subspace prefixExample:
users = Subspace(('users',))
# Pack and unpack
key = users.pack((123, 'profile'))
elements = users.unpack(key)
# Result: (123, 'profile')
# Unpacking key from wrong subspace raises error
products = Subspace(('products',))
try:
products.unpack(key) # Error!
except ValueError:
print("Key not in subspace")Subspace.Unpack(k fdb.KeyConvertible) (tuple.Tuple, error)Unpack a key, removing the subspace prefix.
Parameters:
k: Key to unpackReturns:
tuple.Tuple: Unpacked tupleerror: Error if key doesn't have subspace prefixExample:
users := subspace.Sub("users")
// Pack and unpack
key := users.Pack(tuple.Tuple{int64(123), "profile"})
elements, err := users.Unpack(key)
if err != nil {
log.Fatal(err)
}
// elements: {123, "profile"}
// Unpacking key from wrong subspace returns error
products := subspace.Sub("products")
_, err = products.Unpack(key)
if err != nil {
fmt.Println("Key not in subspace")
}Tuple unpack(byte[] key)Unpack a key, removing the subspace prefix.
Parameters:
key: Key bytes to unpackReturns: Unpacked Tuple (without prefix)
Throws:
IllegalArgumentException: If key doesn't start with subspace prefixExample:
Subspace users = new Subspace(Tuple.from("users"));
// Pack and unpack
byte[] key = users.pack(Tuple.from(123L, "profile"));
Tuple elements = users.unpack(key);
// elements: {123, "profile"}
// Unpacking key from wrong subspace throws exception
Subspace products = new Subspace(Tuple.from("products"));
try {
products.unpack(key); // Throws!
} catch (IllegalArgumentException e) {
System.out.println("Key not in subspace");
}Subspace.unpack(key) -> ArrayUnpack a key, removing the subspace prefix.
Parameters:
key: Key bytes to unpackReturns: Array of unpacked elements (without prefix)
Raises:
ArgumentError: If key doesn't start with subspace prefixExample:
users = FDB::Subspace.new(['users'])
# Pack and unpack
key = users.pack([123, 'profile'])
elements = users.unpack(key)
# Result: [123, 'profile']
# Unpacking key from wrong subspace raises error
products = FDB::Subspace.new(['products'])
begin
products.unpack(key) # Error!
rescue ArgumentError
puts "Key not in subspace"
endGet the key range that encompasses all keys in a subspace.
Subspace.range(items: tuple = ()) -> tupleGet the key range for a tuple prefix within the subspace.
Parameters:
items: Tuple prefix (default: empty tuple for entire subspace)Returns: Tuple of (begin_key, end_key) for range queries
Example:
users = Subspace(('users',))
# Range for entire subspace
begin, end = users.range()
all_users = list(tr.get_range(begin, end))
# Range for specific user
begin, end = users.range((123,))
user_data = list(tr.get_range(begin, end))
# Range for user's profile fields
begin, end = users.range((123, 'profile'))
profile_fields = list(tr.get_range(begin, end))Subspace.FDBRangeKeys() (fdb.KeyConvertible, fdb.KeyConvertible)Get the key range for the entire subspace.
Returns: Begin and end keys for range queries
Example:
users := subspace.Sub("users")
// Range for entire subspace
begin, end := users.FDBRangeKeys()
kvs, err := tr.GetRange(fdb.KeyRange{Begin: begin, End: end}, fdb.RangeOptions{}).GetSliceWithError()
// For nested ranges, create nested subspace first
userSpace := users.Sub(int64(123))
begin, end = userSpace.FDBRangeKeys()
userData, err := tr.GetRange(fdb.KeyRange{Begin: begin, End: end}, fdb.RangeOptions{}).GetSliceWithError()Range range(Tuple tuple)
Range range()Get the key range for a tuple prefix within the subspace.
Parameters:
tuple: Tuple prefix (optional, omit for entire subspace)Returns: Range object for range queries
Example:
Subspace users = new Subspace(Tuple.from("users"));
// Range for entire subspace
Range allUsers = users.range();
List<KeyValue> results = tr.getRange(allUsers).asList().join();
// Range for specific user
Range userData = users.range(Tuple.from(123L));
List<KeyValue> userResults = tr.getRange(userData).asList().join();
// Range for user's profile fields
Range profileData = users.range(Tuple.from(123L, "profile"));Subspace.range(items = []) -> ArrayGet the key range for a tuple prefix within the subspace.
Parameters:
items: Array of tuple elements (default: empty for entire subspace)Returns: Array of [begin_key, end_key]
Example:
users = FDB::Subspace.new(['users'])
# Range for entire subspace
begin_key, end_key = users.range
all_users = tr.get_range(begin_key, end_key)
# Range for specific user
begin_key, end_key = users.range([123])
user_data = tr.get_range(begin_key, end_key)
# Range for user's profile fields
begin_key, end_key = users.range([123, 'profile'])
profile_fields = tr.get_range(begin_key, end_key)Test whether a key belongs to a subspace.
Subspace.contains(key: bytes) -> boolCheck if a key belongs to this subspace.
Parameters:
key: Key bytes to testReturns: True if key is in subspace, False otherwise
Example:
users = Subspace(('users',))
products = Subspace(('products',))
user_key = users.pack((123,))
product_key = products.pack((456,))
print(users.contains(user_key)) # True
print(users.contains(product_key)) # False
print(products.contains(product_key)) # True
# Useful for validation
def process_user_key(key):
if not users.contains(key):
raise ValueError("Not a user key")
user_id, *fields = users.unpack(key)
# ... process user dataSubspace.Contains(k fdb.KeyConvertible) boolCheck if a key belongs to this subspace.
Parameters:
k: Key to testReturns: true if key is in subspace, false otherwise
Example:
users := subspace.Sub("users")
products := subspace.Sub("products")
userKey := users.Pack(tuple.Tuple{int64(123)})
productKey := products.Pack(tuple.Tuple{int64(456)})
fmt.Println(users.Contains(userKey)) // true
fmt.Println(users.Contains(productKey)) // false
fmt.Println(products.Contains(productKey)) // true
// Useful for validation
func processUserKey(key fdb.Key) error {
if !users.Contains(key) {
return errors.New("not a user key")
}
elements, _ := users.Unpack(key)
// ... process user data
return nil
}boolean contains(byte[] key)Check if a key belongs to this subspace.
Parameters:
key: Key bytes to testReturns: true if key is in subspace, false otherwise
Example:
Subspace users = new Subspace(Tuple.from("users"));
Subspace products = new Subspace(Tuple.from("products"));
byte[] userKey = users.pack(Tuple.from(123L));
byte[] productKey = products.pack(Tuple.from(456L));
System.out.println(users.contains(userKey)); // true
System.out.println(users.contains(productKey)); // false
System.out.println(products.contains(productKey)); // true
// Useful for validation
void processUserKey(byte[] key) {
if (!users.contains(key)) {
throw new IllegalArgumentException("Not a user key");
}
Tuple elements = users.unpack(key);
// ... process user data
}Subspace.contains?(key) -> BooleanCheck if a key belongs to this subspace.
Parameters:
key: Key bytes to testReturns: true if key is in subspace, false otherwise
Example:
users = FDB::Subspace.new(['users'])
products = FDB::Subspace.new(['products'])
user_key = users.pack([123])
product_key = products.pack([456])
puts users.contains?(user_key) # true
puts users.contains?(product_key) # false
puts products.contains?(product_key) # true
# Useful for validation
def process_user_key(key)
raise "Not a user key" unless users.contains?(key)
user_id, *fields = users.unpack(key)
# ... process user data
endCreate hierarchical namespace structures by nesting subspaces.
Subspace.subspace(items: tuple) -> SubspaceCreate a nested subspace by extending the prefix.
Parameters:
items: Tuple of additional elements to append to prefixReturns: New Subspace with extended prefix
Example:
# Create hierarchical structure
app = Subspace(('myapp',))
# First level subspaces
users = app.subspace(('users',))
products = app.subspace(('products',))
# Second level subspaces
user_profiles = users.subspace(('profiles',))
user_settings = users.subspace(('settings',))
# Use nested subspaces
profile_key = user_profiles.pack((123, 'email'))
# Result: <tuple: "myapp", "users", "profiles", 123, "email">
settings_key = user_settings.pack((123, 'theme'))
# Result: <tuple: "myapp", "users", "settings", 123, "theme">
# Verify hierarchy
print(app.contains(profile_key)) # True
print(users.contains(profile_key)) # True
print(user_profiles.contains(profile_key)) # True
print(products.contains(profile_key)) # FalseSubspace.Sub(el ...tuple.TupleElement) SubspaceCreate a nested subspace by extending the prefix.
Parameters:
el: Variable number of tuple elements to appendReturns: New Subspace with extended prefix
Example:
// Create hierarchical structure
app := subspace.Sub("myapp")
// First level subspaces
users := app.Sub("users")
products := app.Sub("products")
// Second level subspaces
userProfiles := users.Sub("profiles")
userSettings := users.Sub("settings")
// Use nested subspaces
profileKey := userProfiles.Pack(tuple.Tuple{int64(123), "email"})
// Result: <tuple: "myapp", "users", "profiles", 123, "email">
settingsKey := userSettings.Pack(tuple.Tuple{int64(123), "theme"})
// Result: <tuple: "myapp", "users", "settings", 123, "theme">
// Verify hierarchy
fmt.Println(app.Contains(profileKey)) // true
fmt.Println(users.Contains(profileKey)) // true
fmt.Println(userProfiles.Contains(profileKey)) // true
fmt.Println(products.Contains(profileKey)) // falseSubspace get(Tuple tuple)Create a nested subspace by extending the prefix.
Parameters:
tuple: Tuple of additional elements to appendReturns: New Subspace with extended prefix
Example:
// Create hierarchical structure
Subspace app = new Subspace(Tuple.from("myapp"));
// First level subspaces
Subspace users = app.get(Tuple.from("users"));
Subspace products = app.get(Tuple.from("products"));
// Second level subspaces
Subspace userProfiles = users.get(Tuple.from("profiles"));
Subspace userSettings = users.get(Tuple.from("settings"));
// Use nested subspaces
byte[] profileKey = userProfiles.pack(Tuple.from(123L, "email"));
// Result: <tuple: "myapp", "users", "profiles", 123, "email">
byte[] settingsKey = userSettings.pack(Tuple.from(123L, "theme"));
// Result: <tuple: "myapp", "users", "settings", 123, "theme">
// Verify hierarchy
System.out.println(app.contains(profileKey)); // true
System.out.println(users.contains(profileKey)); // true
System.out.println(userProfiles.contains(profileKey)); // true
System.out.println(products.contains(profileKey)); // falseSubspace.subspace(items) -> Subspace
Subspace[item] -> SubspaceCreate a nested subspace by extending the prefix.
Parameters:
items: Array of additional elements to append (for subspace method)item: Single element to append (for [] operator - accepts only one argument)Returns: New Subspace with extended prefix
Note: The [] operator accepts only a single argument, not multiple arguments.
Example:
# Create hierarchical structure
app = FDB::Subspace.new(['myapp'])
# First level subspaces
users = app.subspace(['users'])
products = app.subspace(['products'])
# Second level subspaces (using [] shorthand)
user_profiles = users['profiles']
user_settings = users['settings']
# Use nested subspaces
profile_key = user_profiles.pack([123, 'email'])
# Result: <tuple: "myapp", "users", "profiles", 123, "email">
settings_key = user_settings.pack([123, 'theme'])
# Result: <tuple: "myapp", "users", "settings", 123, "theme">
# Verify hierarchy
puts app.contains?(profile_key) # true
puts users.contains?(profile_key) # true
puts user_profiles.contains?(profile_key) # true
puts products.contains?(profile_key) # falseAccess the underlying byte prefix of a subspace.
Subspace.key() -> bytesGet the raw byte prefix of the subspace.
Returns: Raw prefix bytes
Example:
users = Subspace(('users',))
prefix = users.key()
# Use prefix for low-level operations
begin_key = prefix
end_key = prefix + b'\xff'
all_users = list(tr.get_range(begin_key, end_key))Subspace.Bytes() []byte
Subspace.FDBKey() fdb.KeyGet the raw byte prefix of the subspace.
Returns: Raw prefix bytes
Example:
users := subspace.Sub("users")
prefix := users.Bytes()
// Use as key directly
prefixKey := users.FDBKey()byte[] getKey()Get the raw byte prefix of the subspace.
Returns: Raw prefix bytes
Example:
Subspace users = new Subspace(Tuple.from("users"));
byte[] prefix = users.getKey();
// Use prefix for low-level operations
byte[] value = tr.get(prefix).join();Subspace.key() -> StringGet the raw byte prefix of the subspace.
Returns: Raw prefix bytes as String
Example:
users = FDB::Subspace.new(['users'])
prefix = users.key
# Use prefix for low-level operations
begin_key = prefix
end_key = prefix + "\xFF"
all_users = tr.get_range(begin_key, end_key)import fdb
fdb.api_version(740)
from fdb.subspace_impl import Subspace
db = fdb.open()
# Create subspaces for different data types
users = Subspace(('users',))
products = Subspace(('products',))
@fdb.transactional
def store_user(tr, user_id, name, email):
# Store user data in users subspace
tr[users.pack((user_id, 'name'))] = name.encode()
tr[users.pack((user_id, 'email'))] = email.encode()
@fdb.transactional
def get_user(tr, user_id):
# Read user data
name = tr[users.pack((user_id, 'name'))]
email = tr[users.pack((user_id, 'email'))]
return {
'id': user_id,
'name': name.decode() if name else None,
'email': email.decode() if email else None
}
# Store and retrieve
db.transact(store_user, 123, "Alice", "alice@example.com")
user = db.transact(get_user, 123)
print(f"User: {user}")package main
import (
"fmt"
"github.com/apple/foundationdb/bindings/go/src/fdb"
"github.com/apple/foundationdb/bindings/go/src/fdb/subspace"
"github.com/apple/foundationdb/bindings/go/src/fdb/tuple"
)
func main() {
fdb.MustAPIVersion(740)
db := fdb.MustOpenDefault()
// Create subspaces
users := subspace.Sub("users")
products := subspace.Sub("products")
// Store user data
_, err := db.Transact(func(tr fdb.Transaction) (interface{}, error) {
userID := int64(123)
tr.Set(users.Pack(tuple.Tuple{userID, "name"}), []byte("Alice"))
tr.Set(users.Pack(tuple.Tuple{userID, "email"}), []byte("alice@example.com"))
return nil, nil
})
if err != nil {
panic(err)
}
// Read user data
result, err := db.Transact(func(tr fdb.Transaction) (interface{}, error) {
userID := int64(123)
name, err := tr.Get(users.Pack(tuple.Tuple{userID, "name"})).Get()
if err != nil {
return nil, err
}
email, err := tr.Get(users.Pack(tuple.Tuple{userID, "email"})).Get()
if err != nil {
return nil, err
}
return map[string]string{
"name": string(name),
"email": string(email),
}, nil
})
if err != nil {
panic(err)
}
user := result.(map[string]string)
fmt.Printf("User: %v\n", user)
}import com.apple.foundationdb.*;
import com.apple.foundationdb.subspace.Subspace;
import com.apple.foundationdb.tuple.Tuple;
public class SubspaceExample {
public static void main(String[] args) {
FDB fdb = FDB.selectAPIVersion(740);
try (Database db = fdb.open()) {
// Create subspaces
Subspace users = new Subspace(Tuple.from("users"));
Subspace products = new Subspace(Tuple.from("products"));
// Store user data
db.run(tr -> {
long userId = 123L;
tr.set(users.pack(Tuple.from(userId, "name")),
"Alice".getBytes());
tr.set(users.pack(Tuple.from(userId, "email")),
"alice@example.com".getBytes());
return null;
});
// Read user data
String name = db.read(tr -> {
long userId = 123L;
byte[] nameBytes = tr.get(users.pack(Tuple.from(userId, "name"))).join();
return new String(nameBytes);
});
System.out.println("User name: " + name);
}
}
}require 'fdb'
FDB.api_version(740)
db = FDB.open
# Create subspaces
users = FDB::Subspace.new(['users'])
products = FDB::Subspace.new(['products'])
# Store user data
db.transact do |tr|
user_id = 123
tr[users.pack([user_id, 'name'])] = 'Alice'
tr[users.pack([user_id, 'email'])] = 'alice@example.com'
end
# Read user data
user = db.transact do |tr|
user_id = 123
{
id: user_id,
name: tr[users.pack([user_id, 'name'])],
email: tr[users.pack([user_id, 'email'])]
}
end
puts "User: #{user}"Demonstrate how subspaces prevent key conflicts:
import fdb
fdb.api_version(740)
from fdb.subspace_impl import Subspace
db = fdb.open()
# Each feature gets its own subspace
auth = Subspace(('auth',))
cache = Subspace(('cache',))
sessions = Subspace(('sessions',))
@fdb.transactional
def demo_isolation(tr):
# Each can use the same logical key without conflict
session_id = "abc123"
# Store in different subspaces
tr[auth.pack((session_id,))] = b'user_credentials'
tr[cache.pack((session_id,))] = b'cached_data'
tr[sessions.pack((session_id,))] = b'session_info'
# Read from specific subspaces
auth_data = tr[auth.pack((session_id,))]
cache_data = tr[cache.pack((session_id,))]
session_data = tr[sessions.pack((session_id,))]
# All three values are different!
print(f"Auth: {auth_data}")
print(f"Cache: {cache_data}")
print(f"Sessions: {session_data}")
# Clear only cache without affecting others
begin, end = cache.range()
tr.clear_range(begin, end)
db.transact(demo_isolation)Build a complete hierarchical data model:
import fdb
fdb.api_version(740)
from fdb.subspace_impl import Subspace
db = fdb.open()
# Build application hierarchy
root = Subspace(('myapp',))
# Top-level modules
data_module = root.subspace(('data',))
config_module = root.subspace(('config',))
# Data submodules
users_space = data_module.subspace(('users',))
posts_space = data_module.subspace(('posts',))
# User data structure
user_profile = users_space.subspace(('profile',))
user_settings = users_space.subspace(('settings',))
user_activity = users_space.subspace(('activity',))
@fdb.transactional
def store_user_data(tr, user_id):
# Profile data
tr[user_profile.pack((user_id, 'name'))] = b'Alice'
tr[user_profile.pack((user_id, 'email'))] = b'alice@example.com'
# Settings data
tr[user_settings.pack((user_id, 'theme'))] = b'dark'
tr[user_settings.pack((user_id, 'language'))] = b'en'
# Activity data
tr[user_activity.pack((user_id, 'last_login'))] = b'2026-01-05'
tr[user_activity.pack((user_id, 'login_count'))] = b'42'
@fdb.transactional
def get_all_user_data(tr, user_id):
"""Get all data for a user across all subspaces"""
result = {}
# Use range queries on each subspace
for subspace, name in [
(user_profile, 'profile'),
(user_settings, 'settings'),
(user_activity, 'activity')
]:
begin, end = subspace.range((user_id,))
result[name] = {}
for kv in tr.get_range(begin, end):
# Unpack to get field name
_, field = subspace.unpack(kv.key)
result[name][field] = kv.value.decode()
return result
# Store and retrieve
db.transact(store_user_data, 123)
user_data = db.transact(get_all_user_data, 123)
print("Complete user data:")
for category, fields in user_data.items():
print(f" {category}:")
for field, value in fields.items():
print(f" {field}: {value}")Isolate data for different tenants using subspaces:
import fdb
fdb.api_version(740)
from fdb.subspace_impl import Subspace
db = fdb.open()
class TenantNamespace:
"""Manage multi-tenant data using subspaces"""
def __init__(self, root_prefix='tenants'):
self.root = Subspace((root_prefix,))
def get_tenant_space(self, tenant_id):
"""Get subspace for a specific tenant"""
return self.root.subspace((tenant_id,))
def get_tenant_table(self, tenant_id, table_name):
"""Get subspace for a tenant's table"""
tenant_space = self.get_tenant_space(tenant_id)
return tenant_space.subspace((table_name,))
# Create tenant namespace manager
tenants = TenantNamespace()
@fdb.transactional
def store_tenant_data(tr, tenant_id, table, record_id, data):
"""Store data for a specific tenant"""
table_space = tenants.get_tenant_table(tenant_id, table)
for field, value in data.items():
key = table_space.pack((record_id, field))
tr[key] = str(value).encode()
@fdb.transactional
def get_tenant_record(tr, tenant_id, table, record_id):
"""Retrieve a record for a specific tenant"""
table_space = tenants.get_tenant_table(tenant_id, table)
begin, end = table_space.range((record_id,))
result = {}
for kv in tr.get_range(begin, end):
_, field = table_space.unpack(kv.key)
result[field] = kv.value.decode()
return result
@fdb.transactional
def list_tenant_records(tr, tenant_id, table):
"""List all record IDs in a tenant's table"""
table_space = tenants.get_tenant_table(tenant_id, table)
begin, end = table_space.range()
record_ids = set()
for kv in tr.get_range(begin, end):
record_id, _ = table_space.unpack(kv.key)
record_ids.add(record_id)
return sorted(record_ids)
@fdb.transactional
def delete_tenant_data(tr, tenant_id):
"""Delete all data for a tenant"""
tenant_space = tenants.get_tenant_space(tenant_id)
begin, end = tenant_space.range()
tr.clear_range(begin, end)
# Use multi-tenant system
tenant_a = "company-a"
tenant_b = "company-b"
# Store data for different tenants
db.transact(store_tenant_data, tenant_a, "users", 1, {
"name": "Alice",
"email": "alice@company-a.com"
})
db.transact(store_tenant_data, tenant_b, "users", 1, {
"name": "Bob",
"email": "bob@company-b.com"
})
# Both tenants can use same IDs without conflict
user_a = db.transact(get_tenant_record, tenant_a, "users", 1)
user_b = db.transact(get_tenant_record, tenant_b, "users", 1)
print(f"Tenant A user 1: {user_a}")
print(f"Tenant B user 1: {user_b}")
# List records per tenant
records_a = db.transact(list_tenant_records, tenant_a, "users")
records_b = db.transact(list_tenant_records, tenant_b, "users")
print(f"Tenant A records: {records_a}")
print(f"Tenant B records: {records_b}")
# Delete all data for tenant A
db.transact(delete_tenant_data, tenant_a)
print("Tenant A data deleted")Combine subspaces with tuples for powerful data modeling:
import fdb
fdb.api_version(740)
from fdb.subspace_impl import Subspace
from fdb.tuple import Versionstamp
import struct
db = fdb.open()
# Subspaces for different entity types
users = Subspace(('users',))
posts = Subspace(('posts',))
comments = Subspace(('comments',))
# Indexes using subspaces and tuples
user_email_index = Subspace(('indexes', 'user_email'))
post_timestamp_index = Subspace(('indexes', 'post_timestamp'))
@fdb.transactional
def create_user(tr, user_id, name, email):
"""Create user with email index"""
# Primary data
tr[users.pack((user_id, 'name'))] = name.encode()
tr[users.pack((user_id, 'email'))] = email.encode()
# Secondary index: email -> user_id
tr[user_email_index.pack((email, user_id))] = b''
@fdb.transactional
def find_user_by_email(tr, email):
"""Look up user by email using index"""
# Scan index for this email
begin, end = user_email_index.range((email,))
for kv in tr.get_range(begin, end):
_, user_id = user_email_index.unpack(kv.key)
return user_id
return None
@fdb.transactional
def create_post(tr, post_id, user_id, content, timestamp):
"""Create post with timestamp index"""
# Primary data
tr[posts.pack((post_id, 'user_id'))] = str(user_id).encode()
tr[posts.pack((post_id, 'content'))] = content.encode()
tr[posts.pack((post_id, 'timestamp'))] = str(timestamp).encode()
# Time-based index: timestamp -> post_id
tr[post_timestamp_index.pack((timestamp, post_id))] = b''
@fdb.transactional
def get_recent_posts(tr, limit=10):
"""Get most recent posts using time index"""
# Scan index in reverse (most recent first)
begin, end = post_timestamp_index.range()
post_ids = []
for kv in tr.get_range(begin, end, limit=limit, reverse=True):
_, post_id = post_timestamp_index.unpack(kv.key)
post_ids.append(post_id)
return post_ids
# Use the system
user_id = db.transact(create_user, 123, "Alice", "alice@example.com")
# Create posts with timestamps
import time
for i in range(5):
timestamp = int(time.time() * 1000) + i
db.transact(create_post, i, 123, f"Post {i}", timestamp)
# Find user by email
found_user_id = db.transact(find_user_by_email, "alice@example.com")
print(f"User ID for alice@example.com: {found_user_id}")
# Get recent posts
recent = db.transact(get_recent_posts, 3)
print(f"Recent post IDs: {recent}")Choose descriptive prefixes that make your data model clear:
# Good: Clear hierarchy
users = Subspace(('app', 'users'))
products = Subspace(('app', 'products'))
orders = Subspace(('app', 'orders'))
# Bad: Cryptic prefixes
users = Subspace((1,))
products = Subspace((2,))Design your subspace hierarchy upfront:
# Well-planned hierarchy
root = Subspace(('myapp',))
data = root.subspace(('data',))
users = data.subspace(('users',))
products = data.subspace(('products',))
indexes = root.subspace(('indexes',))
metadata = root.subspace(('metadata',))Isolate different concerns into separate subspaces:
# Development vs Production
dev_space = Subspace(('dev',))
prod_space = Subspace(('prod',))
# Features
auth_space = Subspace(('auth',))
billing_space = Subspace(('billing',))
analytics_space = Subspace(('analytics',))Use nesting to create logical groupings:
users = Subspace(('users',))
# Group related data
profiles = users.subspace(('profiles',))
preferences = users.subspace(('preferences',))
activity = users.subspace(('activity',))
# Now you can query all profile data easily
begin, end = profiles.range((user_id,))
profile_data = tr.get_range(begin, end)Validate keys belong to expected subspaces:
def process_user_key(key):
if not users.contains(key):
raise ValueError("Expected user key")
user_id, field = users.unpack(key)
# Safe to process as user dataQuery entire subspaces with single range reads:
# Get all data for a user
user_space = users.subspace((user_id,))
begin, end = user_space.range()
all_user_data = list(tr.get_range(begin, end))
# More efficient than individual gets
# for each fieldBe mindful of subspace prefix overhead:
# Small prefix overhead
users = Subspace(('u',)) # 2-3 bytes
# Large prefix overhead
users = Subspace(('application', 'version-2', 'production', 'users'))
# 40+ bytes per keyMaintain documentation of your subspace structure:
"""
Subspace Layout:
('app',)
('app', 'users')
('app', 'users', 'profile')
('app', 'users', 'settings')
('app', 'products')
('app', 'orders')
('indexes',)
('indexes', 'user_email')
('indexes', 'product_sku')
"""Isolate test data with subspaces:
import uuid
# Each test gets unique subspace
test_id = str(uuid.uuid4())
test_space = Subspace(('tests', test_id))
# All test data in isolated namespace
users = test_space.subspace(('users',))
products = test_space.subspace(('products',))
# Cleanup: delete entire test subspace
begin, end = test_space.range()
tr.clear_range(begin, end)For dynamic prefix allocation, use Directory Layer with Subspace:
import fdb
db = fdb.open()
# Directories allocate short, unique prefixes
user_dir = fdb.directory.create_or_open(db, ('users',))
# Directory is also a Subspace
user_key = user_dir.pack((123, 'name'))
tr[user_key] = b'Alice'Subspaces make range queries extremely efficient:
# Without subspaces: must scan entire database
all_keys = tr.get_range(b'', b'\xff')
user_keys = [kv for kv in all_keys if is_user_key(kv.key)]
# With subspaces: only scan user range
begin, end = users.range()
user_keys = tr.get_range(begin, end) # Much faster!Each nesting level adds tuple encoding overhead:
# 3-level nesting
key = app.subspace(('users',)).subspace(('profiles',)).pack((123,))
# Prefix: <tuple: "app", "users", "profiles">
# Total: ~20-30 bytes prefix + data
# Flatter structure
key = users_profiles.pack((123,))
# Prefix: <tuple: "users_profiles">
# Total: ~15 bytes prefix + dataSubspace objects are lightweight - it's safe to create many:
# Creating thousands of subspaces is fine
user_spaces = {
user_id: users.subspace((user_id,))
for user_id in range(10000)
}Organize data like a traditional database:
# Each "table" is a subspace
users_table = Subspace(('tables', 'users'))
products_table = Subspace(('tables', 'products'))
orders_table = Subspace(('tables', 'orders'))
# Primary keys
user_key = users_table.pack((user_id,))
product_key = products_table.pack((product_id,))Create secondary indexes using subspaces:
# Primary data
users = Subspace(('users',))
# Secondary indexes
email_index = Subspace(('indexes', 'user_by_email'))
username_index = Subspace(('indexes', 'user_by_username'))
# Write to both
tr[users.pack((user_id, 'email'))] = email
tr[email_index.pack((email, user_id))] = b''Store multiple versions using subspaces:
current = Subspace(('data', 'current'))
history = Subspace(('data', 'history'))
# Update with history
old_value = tr[current.pack((key,))]
if old_value:
tr[history.pack((key, timestamp))] = old_value
tr[current.pack((key,))] = new_valueMulti-tenant applications:
def get_tenant_space(tenant_id):
return Subspace(('tenants', tenant_id))
# Each tenant operates in isolated namespace
tenant_a_users = get_tenant_space('tenant-a').subspace(('users',))
tenant_b_users = get_tenant_space('tenant-b').subspace(('users',))Store configuration per feature:
features = Subspace(('features',))
# Each feature has its own subspace
feature_a = features.subspace(('feature-a',))
feature_b = features.subspace(('feature-b',))
# Store feature config
tr[feature_a.pack(('enabled',))] = b'true'
tr[feature_a.pack(('config', 'timeout'))] = b'30'Safe to add new subspaces anytime:
# Original structure
users = Subspace(('users',))
# Add new subspace later
user_sessions = Subspace(('user_sessions',)) # Safe
# Doesn't affect existing users subspaceMoving data between subspaces requires migration:
@fdb.transactional
def migrate_subspace(tr, old_space, new_space):
"""Move all data from old_space to new_space"""
begin, end = old_space.range()
for kv in tr.get_range(begin, end):
# Unpack from old subspace
elements = old_space.unpack(kv.key)
# Pack into new subspace
new_key = new_space.pack(elements)
tr[new_key] = kv.value
# Clear old subspace
tr.clear_range(begin, end)
# Migrate
old = Subspace(('old_users',))
new = Subspace(('users',))
db.transact(migrate_subspace, old, new)Include version in subspace hierarchy:
# Version 1
v1_users = Subspace(('v1', 'users'))
# Version 2 with new structure
v2_users = Subspace(('v2', 'users'))
v2_profiles = Subspace(('v2', 'profiles'))
# Support both during migration
current_version = 2
users = Subspace((f'v{current_version}', 'users'))Problem: ValueError when unpacking key
key = users.pack((123,))
products.unpack(key) # Error!Solution: Verify key belongs to subspace first
if products.contains(key):
elements = products.unpack(key)
else:
# Handle invalid key
print("Key not from products subspace")Problem: Subspaces unintentionally overlap
# Bad: overlap possible
users = Subspace((b'\x01',))
user_data = Subspace((b'\x01\x00',)) # Starts with users prefix!Solution: Use tuple encoding for automatic separation
# Good: tuple encoding prevents overlap
users = Subspace(('users',))
user_data = Subspace(('user_data',)) # Guaranteed differentProblem: Data stored but not found
# Stored in one subspace
tr[users.pack((123,))] = b'Alice'
# Searching in wrong subspace
products_begin, products_end = products.range()
data = list(tr.get_range(products_begin, products_end)) # Empty!Solution: Verify you're querying correct subspace
# Query correct subspace
users_begin, users_end = users.range()
data = list(tr.get_range(users_begin, users_end)) # Found!The Subspace Layer provides essential namespace isolation for FoundationDB applications:
Subspaces are fundamental to organizing data in FoundationDB and should be used in virtually all applications for clean, maintainable data models.