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

subspace-layer.mddocs/reference/

FoundationDB Subspace Layer Reference

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.

What are Subspaces?

A Subspace is a logical namespace within FoundationDB that automatically prefixes all keys with a specific byte string. This provides:

  • Namespace Isolation: Different parts of your application can use the same logical keys without conflicts
  • Hierarchical Organization: Create nested subspaces for multi-level data structures
  • Integration with Tuples: Seamlessly combine with the Tuple Layer for structured keys
  • Efficient Range Operations: Query all keys within a subspace using a single range read
  • Multi-Tenant Support: Isolate data for different tenants using separate subspaces

Use Cases

  • Multi-tenant applications: Each tenant gets their own subspace
  • Feature isolation: Different features use separate subspaces
  • Schema organization: Tables/collections in separate subspaces
  • Hierarchical data: Nested subspaces for tree structures
  • Testing: Use subspace per test for isolation

Package Information

Python { .api }

Package: fdb Module: fdb.subspace_impl Class: Subspace

from fdb.subspace_impl import Subspace
# Or via main module
from fdb import Subspace

Go { .api }

Package: github.com/apple/foundationdb/bindings/go/src/fdb/subspace Type: Subspace (interface)

import "github.com/apple/foundationdb/bindings/go/src/fdb/subspace"

Java { .api }

Package: com.apple.foundationdb.subspace Class: Subspace

import com.apple.foundationdb.subspace.Subspace;

Ruby { .api }

Module: FDB Class: FDB::Subspace

require 'fdb'
# Subspace available as FDB::Subspace

Core Concepts

Key Prefixing

Every 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>

Tuple Integration

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>

Nested Subspaces

Create hierarchical structures by nesting subspaces:

Root subspace: ("app",)
  ├─ Subspace: ("app", "users")
  │    ├─ ("app", "users", "profile")
  │    └─ ("app", "users", "settings")
  └─ Subspace: ("app", "products")

Range Containment

Subspaces define contiguous key ranges. All keys in a subspace are consecutive in the database's sorted order, enabling efficient range operations.

Capabilities

Subspace Creation

Creating from Raw Bytes

Python { .api }

Subspace(prefixTuple: tuple = (), rawPrefix: Optional[bytes] = None) -> Subspace

Create 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',))

Go { .api }

subspace.FromBytes(b []byte) Subspace

Create a subspace from raw bytes.

Parameters:

  • b: Raw byte prefix

Returns: 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{})

Java { .api }

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});

Ruby { .api }

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

Create 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 arguments

Creating from Tuples

Python { .api }

Subspace(prefix: tuple = ()) -> Subspace

Create 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'))

Go { .api }

subspace.Sub(el ...tuple.TupleElement) Subspace

Create a subspace from tuple elements (package-level function).

Parameters:

  • el: Variable number of tuple elements

Returns: 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")

Java { .api }

Subspace(Tuple prefix)

Create a subspace from a tuple.

Parameters:

  • prefix: Tuple to use as prefix

Returns: 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"));

Ruby { .api }

FDB::Subspace.new(prefix = []) -> Subspace

Create 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'])

Key Packing

Add the subspace prefix to tuple elements to create fully-qualified keys.

Python { .api }

Subspace.pack(items: tuple = ()) -> bytes

Pack 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">

Go { .api }

Subspace.Pack(t tuple.Tuple) fdb.Key

Pack a tuple with the subspace prefix.

Parameters:

  • t: Tuple to pack

Returns: 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{})

Java { .api }

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();

Ruby { .api }

Subspace.pack(items = []) -> String

Pack 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.pack

Key Unpacking

Remove the subspace prefix and decode the remaining tuple.

Python { .api }

Subspace.unpack(key: bytes) -> tuple

Unpack a key, removing the subspace prefix.

Parameters:

  • key: Key bytes to unpack

Returns: Tuple of unpacked elements (without prefix)

Raises:

  • ValueError: If key doesn't start with subspace prefix

Example:

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")

Go { .api }

Subspace.Unpack(k fdb.KeyConvertible) (tuple.Tuple, error)

Unpack a key, removing the subspace prefix.

Parameters:

  • k: Key to unpack

Returns:

  • tuple.Tuple: Unpacked tuple
  • error: Error if key doesn't have subspace prefix

Example:

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")
}

Java { .api }

Tuple unpack(byte[] key)

Unpack a key, removing the subspace prefix.

Parameters:

  • key: Key bytes to unpack

Returns: Unpacked Tuple (without prefix)

Throws:

  • IllegalArgumentException: If key doesn't start with subspace prefix

Example:

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");
}

Ruby { .api }

Subspace.unpack(key) -> Array

Unpack a key, removing the subspace prefix.

Parameters:

  • key: Key bytes to unpack

Returns: Array of unpacked elements (without prefix)

Raises:

  • ArgumentError: If key doesn't start with subspace prefix

Example:

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"
end

Range Generation

Get the key range that encompasses all keys in a subspace.

Python { .api }

Subspace.range(items: tuple = ()) -> tuple

Get 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))

Go { .api }

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()

Java { .api }

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"));

Ruby { .api }

Subspace.range(items = []) -> Array

Get 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)

Containment Checking

Test whether a key belongs to a subspace.

Python { .api }

Subspace.contains(key: bytes) -> bool

Check if a key belongs to this subspace.

Parameters:

  • key: Key bytes to test

Returns: 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 data

Go { .api }

Subspace.Contains(k fdb.KeyConvertible) bool

Check if a key belongs to this subspace.

Parameters:

  • k: Key to test

Returns: 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
}

Java { .api }

boolean contains(byte[] key)

Check if a key belongs to this subspace.

Parameters:

  • key: Key bytes to test

Returns: 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
}

Ruby { .api }

Subspace.contains?(key) -> Boolean

Check if a key belongs to this subspace.

Parameters:

  • key: Key bytes to test

Returns: 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
end

Nested Subspaces

Create hierarchical namespace structures by nesting subspaces.

Python { .api }

Subspace.subspace(items: tuple) -> Subspace

Create a nested subspace by extending the prefix.

Parameters:

  • items: Tuple of additional elements to append to prefix

Returns: 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))   # False

Go { .api }

Subspace.Sub(el ...tuple.TupleElement) Subspace

Create a nested subspace by extending the prefix.

Parameters:

  • el: Variable number of tuple elements to append

Returns: 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))    // false

Java { .api }

Subspace get(Tuple tuple)

Create a nested subspace by extending the prefix.

Parameters:

  • tuple: Tuple of additional elements to append

Returns: 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));    // false

Ruby { .api }

Subspace.subspace(items) -> Subspace
Subspace[item] -> Subspace

Create 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)    # false

Getting Raw Prefix

Access the underlying byte prefix of a subspace.

Python { .api }

Subspace.key() -> bytes

Get 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))

Go { .api }

Subspace.Bytes() []byte
Subspace.FDBKey() fdb.Key

Get 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()

Java { .api }

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();

Ruby { .api }

Subspace.key() -> String

Get 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)

Comprehensive Examples

Basic Subspace Usage

Python

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}")

Go

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)
}

Java

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);
        }
    }
}

Ruby

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}"

Namespace Isolation Pattern

Demonstrate how subspaces prevent key conflicts:

Python

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)

Nested Subspace Hierarchy

Build a complete hierarchical data model:

Python

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}")

Multi-Tenant Data Organization

Isolate data for different tenants using subspaces:

Python

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")

Integration with Tuple Layer

Combine subspaces with tuples for powerful data modeling:

Python

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}")

Best Practices

1. Use Meaningful Prefixes

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,))

2. Plan Your Hierarchy

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',))

3. Use Subspaces for Isolation

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',))

4. Leverage Nested Subspaces for Structure

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)

5. Check Containment for Validation

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 data

6. Use Range Queries Efficiently

Query 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 field

7. Consider Subspace Size

Be 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 key

8. Document Your Subspace Layout

Maintain 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')
"""

9. Use Subspaces for Testing

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)

10. Combine with Directory Layer

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'

Performance Considerations

Prefix Length Impact

  • Shorter prefixes: Less overhead per key, faster serialization
  • Longer prefixes: More descriptive, better organization
  • Recommendation: Balance clarity with efficiency (typically 5-20 bytes)

Range Query Efficiency

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!

Nesting Overhead

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 + data

Memory Efficiency

Subspace 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)
}

Common Patterns

Pattern: Table-Per-Subspace

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,))

Pattern: Index-Per-Subspace

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''

Pattern: Versioned Data

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_value

Pattern: Tenant Isolation

Multi-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',))

Pattern: Feature Flags

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'

Migration and Evolution

Adding New Subspaces

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 subspace

Reorganizing Subspaces

Moving 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)

Versioning Subspace Layout

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'))

Troubleshooting

Key Not In Subspace Error

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")

Overlapping Subspaces

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 different

Missing Data

Problem: 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!

Related Documentation

  • Tuple Layer: For structured key encoding (works with Subspace)
  • Directory Layer: For dynamic prefix allocation with automatic short prefixes
  • Transactions: Understanding transactional context for subspace operations
  • Range Queries: Efficient scanning within subspaces

Summary

The Subspace Layer provides essential namespace isolation for FoundationDB applications:

  • Simple API: Create, pack, unpack, range, contains, and nest subspaces
  • Automatic Prefixing: All keys automatically namespaced
  • Hierarchical Structure: Build complex data models with nested subspaces
  • Efficient Queries: Range operations query entire subspaces
  • Multi-Language Support: Consistent API across Python, Go, Java, and Ruby

Subspaces are fundamental to organizing data in FoundationDB and should be used in virtually all applications for clean, maintainable data models.