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

directory-layer.mddocs/reference/

FoundationDB Directory Layer

The Directory Layer provides a hierarchical directory system with automatic prefix allocation, enabling clean namespace management and multi-application data isolation in FoundationDB.

Overview

The Directory Layer is a system for managing related subspaces with human-readable path names. Instead of manually managing byte prefixes for different parts of your application, the Directory Layer automatically allocates unique, non-overlapping prefixes for each directory. This provides several key benefits:

Benefits over raw subspaces:

  • Automatic prefix allocation: No manual coordination of byte prefixes between applications or modules
  • Hierarchical organization: Natural tree structure for organizing data with path-based naming
  • Relocatable prefixes: Move entire directory trees without rewriting data
  • Layer tagging: Identify directory types with application-specific layer identifiers
  • Multi-application isolation: Multiple applications can safely share a database
  • Human-readable paths: Use meaningful names instead of opaque byte sequences

When to use Directory Layer:

  • Multi-application or multi-tenant systems sharing a database
  • Applications with logical subsystems that need isolated namespaces
  • Systems requiring the ability to reorganize data without migration
  • Projects where manual prefix management becomes complex

When to use raw Subspaces:

  • Single-application systems with simple, fixed structure
  • Performance-critical paths where directory lookups add overhead
  • When you need complete control over key encoding

Package Information

Python

Package: fdb Module: fdb.directory_impl Import: import fdb (directory accessible via fdb.directory)

import fdb
fdb.api_version(740)
db = fdb.open()

# Use default directory instance
users = fdb.directory.create_or_open(db, ['users'])

Go

Package: github.com/apple/foundationdb/bindings/go/src/fdb/directory Import:

import (
    "github.com/apple/foundationdb/bindings/go/src/fdb"
    "github.com/apple/foundationdb/bindings/go/src/fdb/directory"
)
db := fdb.MustOpenDefault()
users, err := directory.Root().CreateOrOpen(db, []string{"users"}, nil)

Java

Package: com.apple.foundationdb.directory Import:

import com.apple.foundationdb.*;
import com.apple.foundationdb.directory.*;
Database db = FDB.selectAPIVersion(740).open();
DirectoryLayer dir = DirectoryLayer.getDefault();
DirectorySubspace users = dir.createOrOpen(db, Arrays.asList("users")).join();

Ruby

Module: FDB Class: FDB::DirectoryLayer Import: require 'fdb'

require 'fdb'
FDB.api_version(740)
db = FDB.open

# Use default directory instance
users = FDB.directory.create_or_open(db, ['users'])

Core Concepts

Directory Paths

Directories are identified by hierarchical paths, similar to filesystem paths. Paths are represented as arrays/lists of string components:

  • ['users'] - Top-level directory
  • ['app', 'users'] - Nested directory
  • ['app', 'users', 'profiles'] - Deeply nested directory

Each path component is a string that becomes part of the directory hierarchy.

Automatic Prefix Allocation

When you create a directory, the Directory Layer automatically allocates a unique byte prefix from a pool. This prefix:

  • Is guaranteed unique across all directories in the layer
  • Is compact (typically 2-3 bytes for early allocations)
  • Remains consistent once allocated
  • Is stored in directory metadata

This eliminates manual prefix coordination and collision risks.

DirectorySubspace

A DirectorySubspace combines the functionality of:

  • Directory: Hierarchical management operations (create, move, list, remove)
  • Subspace: Key encoding and prefixing operations (pack, unpack, range)

This dual nature allows you to manage directories hierarchically while using them as subspaces for data storage.

Layer Tags

Layer identifiers (layer tags) are application-specific byte strings that mark directory types. They enable:

  • Type enforcement: Ensure directories are used for their intended purpose
  • Application coexistence: Different applications can identify their directories
  • Version management: Different schema versions can use different layer tags

Common patterns:

  • b'' - Default/untyped directory
  • b'user_data' - Application-specific type
  • b'app_v2' - Version-tagged directory

Partitions

Partitions are special directories that create isolated directory namespaces. They allow:

  • Complete isolation between directory subtrees
  • Independent prefix allocation pools
  • Multi-tenant systems with guaranteed isolation

Directory Metadata

The Directory Layer stores metadata in a system subspace (by default \xFE):

  • Directory paths and their allocated prefixes
  • Layer identifiers
  • Subdirectory relationships
  • Prefix allocation state

This metadata enables directory operations without scanning user data.

Capabilities

Directory Creation

Create new directories with automatic prefix allocation.

Python API

DirectoryLayer.create_or_open(
    db_or_tr: Union[Database, Tenant, Transaction],
    path: Union[str, List[str]],
    layer: bytes = b'',
    prefix: Optional[bytes] = None,
    allow_create: bool = True,
    allow_open: bool = True
) -> DirectorySubspace

Create or open a directory. The most commonly used directory operation.

Parameters:

  • db_or_tr: Database, Tenant, or Transaction to use
  • path: Directory path as string or list of strings
  • layer: Layer identifier for type checking
  • prefix: Manual prefix (requires allow_manual_prefixes=True on DirectoryLayer)
  • allow_create: Allow creating if doesn't exist (default True)
  • allow_open: Allow opening if exists (default True)

Returns: DirectorySubspace object

Example:

import fdb
fdb.api_version(740)
db = fdb.open()

# Create or open directory (idempotent)
users = fdb.directory.create_or_open(db, ['users'])

# With layer tag
posts = fdb.directory.create_or_open(db, ['posts'], layer=b'post_data')

# Nested directory
profiles = fdb.directory.create_or_open(db, ['users', 'profiles'])

# Use as subspace
@fdb.transactional
def store_user(tr, user_id, name):
    key = users.pack((user_id, 'name'))
    tr[key] = name.encode()

store_user(db, 123, 'Alice')
DirectoryLayer.create(
    db_or_tr: Union[Database, Tenant, Transaction],
    path: Union[str, List[str]],
    layer: bytes = b'',
    prefix: Optional[bytes] = None
) -> DirectorySubspace

Create a new directory. Fails if directory already exists.

Parameters:

  • db_or_tr: Database, Tenant, or Transaction to use
  • path: Directory path
  • layer: Layer identifier
  • prefix: Manual prefix (requires allow_manual_prefixes=True)

Returns: DirectorySubspace object

Raises: Error if directory already exists

Example:

# Create new directory (fails if exists)
try:
    analytics = fdb.directory.create(db, ['analytics'])
except fdb.FDBError as e:
    print(f"Directory already exists: {e}")

Go API

func (d Directory) CreateOrOpen(
    t Transactor,
    path []string,
    layer []byte
) (DirectorySubspace, error)

Create or open a directory.

Parameters:

  • t: Database or Transaction implementing Transactor interface
  • path: Directory path as slice of strings
  • layer: Layer identifier (pass nil for default)

Returns: DirectorySubspace and error

Example:

import (
    "github.com/apple/foundationdb/bindings/go/src/fdb"
    "github.com/apple/foundationdb/bindings/go/src/fdb/directory"
)

fdb.MustAPIVersion(740)
db := fdb.MustOpenDefault()

// Create or open directory
users, err := directory.Root().CreateOrOpen(db, []string{"users"}, nil)
if err != nil {
    log.Fatal(err)
}

// With layer tag
posts, err := directory.Root().CreateOrOpen(db, []string{"posts"}, []byte("post_data"))

// Nested directory
profiles, err := directory.Root().CreateOrOpen(db, []string{"users", "profiles"}, nil)

// Use as subspace
_, err = db.Transact(func(tr fdb.Transaction) (interface{}, error) {
    key := users.Pack(tuple.Tuple{123, "name"})
    tr.Set(key, []byte("Alice"))
    return nil, nil
})
func (d Directory) Create(
    t Transactor,
    path []string,
    layer []byte
) (DirectorySubspace, error)

Create a new directory. Returns error if directory already exists.

Parameters:

  • t: Transactor (Database or Transaction)
  • path: Directory path
  • layer: Layer identifier

Returns: DirectorySubspace and error

Java API

CompletableFuture<DirectorySubspace> createOrOpen(
    TransactionContext tc,
    List<String> path,
    byte[] layer
)

Create or open a directory asynchronously.

Parameters:

  • tc: Transaction context (Database, Tenant, or Transaction)
  • path: Directory path as list of strings
  • layer: Layer identifier

Returns: CompletableFuture<DirectorySubspace>

Example:

import com.apple.foundationdb.*;
import com.apple.foundationdb.directory.*;
import com.apple.foundationdb.tuple.Tuple;
import java.util.*;

FDB fdb = FDB.selectAPIVersion(740);
Database db = fdb.open();

// Create or open directory
DirectorySubspace users = DirectoryLayer.getDefault()
    .createOrOpen(db, Arrays.asList("users"))
    .join();

// With layer tag
DirectorySubspace posts = DirectoryLayer.getDefault()
    .createOrOpen(db, Arrays.asList("posts"), "post_data".getBytes())
    .join();

// Nested directory
DirectorySubspace profiles = DirectoryLayer.getDefault()
    .createOrOpen(db, Arrays.asList("users", "profiles"))
    .join();

// Use as subspace
db.run(tr -> {
    byte[] key = users.pack(Tuple.from(123, "name"));
    tr.set(key, "Alice".getBytes());
    return null;
});
CompletableFuture<DirectorySubspace> create(
    TransactionContext tc,
    List<String> path,
    byte[] layer
)

Create a new directory. Fails if directory already exists.

Parameters:

  • tc: Transaction context
  • path: Directory path
  • layer: Layer identifier

Returns: CompletableFuture<DirectorySubspace>

Ruby API

create_or_open(
    db_or_tr,
    path,
    options = {}
) -> DirectorySubspace

Create or open a directory.

Parameters:

  • db_or_tr: Database, Tenant, or Transaction
  • path: Directory path as array of strings
  • options: Hash with :layer and :prefix keys

Returns: DirectorySubspace

Example:

require 'fdb'
FDB.api_version(740)
db = FDB.open

# Create or open directory
users = FDB.directory.create_or_open(db, ['users'])

# With layer tag
posts = FDB.directory.create_or_open(db, ['posts'], layer: 'post_data')

# Nested directory
profiles = FDB.directory.create_or_open(db, ['users', 'profiles'])

# Use as subspace
db.transact do |tr|
  key = users.pack([123, 'name'])
  tr[key] = 'Alice'
end
create(
    db_or_tr,
    path,
    options = {}
) -> DirectorySubspace

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

Parameters:

  • db_or_tr: Database, Tenant, or Transaction
  • path: Directory path
  • options: Hash with :layer and :prefix keys

Returns: DirectorySubspace

Directory Opening

Open existing directories and check for their existence.

Python API

DirectoryLayer.open(
    db_or_tr: Union[Database, Tenant, Transaction],
    path: Union[str, List[str]],
    layer: bytes = b''
) -> DirectorySubspace

Open an existing directory. Fails if directory doesn't exist.

Parameters:

  • db_or_tr: Database, Tenant, or Transaction to use
  • path: Directory path
  • layer: Expected layer identifier (validates if provided)

Returns: DirectorySubspace object

Raises: Error if directory doesn't exist or layer mismatch

Example:

# Open existing directory
try:
    users = fdb.directory.open(db, ['users'])
except fdb.FDBError:
    print("Directory doesn't exist")

# Open with layer validation
try:
    posts = fdb.directory.open(db, ['posts'], layer=b'post_data')
except fdb.FDBError as e:
    print(f"Directory not found or layer mismatch: {e}")
DirectoryLayer.exists(
    db_or_tr: Union[Database, Tenant, Transaction],
    path: Union[str, List[str]]
) -> bool

Check if a directory exists.

Parameters:

  • db_or_tr: Database, Tenant, or Transaction to use
  • path: Directory path to check

Returns: True if directory exists, False otherwise

Example:

# Check before opening
if fdb.directory.exists(db, ['users']):
    users = fdb.directory.open(db, ['users'])
else:
    users = fdb.directory.create(db, ['users'])

# Conditional operations
@fdb.transactional
def ensure_directory(tr, path):
    if not fdb.directory.exists(tr, path):
        fdb.directory.create(tr, path)

Go API

func (d Directory) Open(
    rt ReadTransactor,
    path []string,
    layer []byte
) (DirectorySubspace, error)

Open an existing directory.

Parameters:

  • rt: ReadTransactor (Database or Transaction)
  • path: Directory path
  • layer: Expected layer identifier

Returns: DirectorySubspace and error

Example:

// Open existing directory
users, err := directory.Root().Open(db, []string{"users"}, nil)
if err != nil {
    log.Printf("Directory doesn't exist: %v", err)
}

// Open with layer validation
posts, err := directory.Root().Open(db, []string{"posts"}, []byte("post_data"))
if err != nil {
    log.Printf("Directory not found or layer mismatch: %v", err)
}
func (d Directory) Exists(
    rt ReadTransactor,
    path []string
) (bool, error)

Check if a directory exists.

Parameters:

  • rt: ReadTransactor
  • path: Directory path

Returns: Boolean and error

Example:

exists, err := directory.Root().Exists(db, []string{"users"})
if err != nil {
    log.Fatal(err)
}
if exists {
    users, _ := directory.Root().Open(db, []string{"users"}, nil)
    // Use directory
}

Java API

CompletableFuture<DirectorySubspace> open(
    ReadTransactionContext rtc,
    List<String> path,
    byte[] layer
)

Open an existing directory asynchronously.

Parameters:

  • rtc: Read transaction context
  • path: Directory path
  • layer: Expected layer identifier

Returns: CompletableFuture<DirectorySubspace>

Example:

// Open existing directory
DirectorySubspace users = DirectoryLayer.getDefault()
    .open(db, Arrays.asList("users"))
    .join();

// Open with layer validation
try {
    DirectorySubspace posts = DirectoryLayer.getDefault()
        .open(db, Arrays.asList("posts"), "post_data".getBytes())
        .join();
} catch (Exception e) {
    System.out.println("Directory not found or layer mismatch: " + e);
}
CompletableFuture<Boolean> exists(
    ReadTransactionContext rtc,
    List<String> path
)

Check if a directory exists.

Parameters:

  • rtc: Read transaction context
  • path: Directory path

Returns: CompletableFuture<Boolean>

Example:

boolean exists = DirectoryLayer.getDefault()
    .exists(db, Arrays.asList("users"))
    .join();
if (exists) {
    DirectorySubspace users = DirectoryLayer.getDefault()
        .open(db, Arrays.asList("users"))
        .join();
}

Ruby API

open(
    db_or_tr,
    path,
    options = {}
) -> DirectorySubspace

Open an existing directory.

Parameters:

  • db_or_tr: Database, Tenant, or Transaction
  • path: Directory path
  • options: Hash with :layer key for validation

Returns: DirectorySubspace

Raises: Error if directory doesn't exist or layer mismatch

Example:

# Open existing directory
begin
  users = FDB.directory.open(db, ['users'])
rescue FDB::Error => e
  puts "Directory doesn't exist"
end

# Open with layer validation
begin
  posts = FDB.directory.open(db, ['posts'], layer: 'post_data')
rescue FDB::Error => e
  puts "Directory not found or layer mismatch: #{e}"
end
exists?(
    db_or_tr,
    path
) -> Boolean

Check if a directory exists.

Parameters:

  • db_or_tr: Database, Tenant, or Transaction
  • path: Directory path

Returns: true or false

Example:

# Check before opening
if FDB.directory.exists?(db, ['users'])
  users = FDB.directory.open(db, ['users'])
else
  users = FDB.directory.create(db, ['users'])
end

Directory Listing

List subdirectories within a directory.

Python API

DirectoryLayer.list(
    db_or_tr: Union[Database, Tenant, Transaction],
    path: Union[str, List[str]] = ()
) -> List[str]

List subdirectories at the given path.

Parameters:

  • db_or_tr: Database, Tenant, or Transaction to use
  • path: Directory path to list (empty tuple/list for root)

Returns: List of subdirectory name strings

Example:

import fdb
fdb.api_version(740)
db = fdb.open()

# Create directory structure
fdb.directory.create_or_open(db, ['app', 'users'])
fdb.directory.create_or_open(db, ['app', 'posts'])
fdb.directory.create_or_open(db, ['app', 'comments'])

# List subdirectories
subdirs = fdb.directory.list(db, ['app'])
# ['users', 'posts', 'comments']

# List root directories
root_dirs = fdb.directory.list(db)
# ['app']

# List in transaction
@fdb.transactional
def list_user_types(tr):
    return fdb.directory.list(tr, ['users'])

Go API

func (d Directory) List(
    rt ReadTransactor,
    path []string
) ([]string, error)

List subdirectories at the given path.

Parameters:

  • rt: ReadTransactor (Database or Transaction)
  • path: Directory path (empty slice for root)

Returns: Slice of subdirectory names and error

Example:

import (
    "github.com/apple/foundationdb/bindings/go/src/fdb"
    "github.com/apple/foundationdb/bindings/go/src/fdb/directory"
)

// Create directory structure
directory.Root().CreateOrOpen(db, []string{"app", "users"}, nil)
directory.Root().CreateOrOpen(db, []string{"app", "posts"}, nil)
directory.Root().CreateOrOpen(db, []string{"app", "comments"}, nil)

// List subdirectories
subdirs, err := directory.Root().List(db, []string{"app"})
if err != nil {
    log.Fatal(err)
}
// subdirs: ["users", "posts", "comments"]

// List root directories
rootDirs, err := directory.Root().List(db, []string{})
// rootDirs: ["app"]

Java API

CompletableFuture<List<String>> list(
    ReadTransactionContext rtc,
    List<String> path
)

List subdirectories at the given path asynchronously.

Parameters:

  • rtc: Read transaction context
  • path: Directory path (empty list for root)

Returns: CompletableFuture<List<String>> of subdirectory names

Example:

import com.apple.foundationdb.*;
import com.apple.foundationdb.directory.*;
import java.util.*;

// Create directory structure
DirectoryLayer dir = DirectoryLayer.getDefault();
dir.createOrOpen(db, Arrays.asList("app", "users")).join();
dir.createOrOpen(db, Arrays.asList("app", "posts")).join();
dir.createOrOpen(db, Arrays.asList("app", "comments")).join();

// List subdirectories
List<String> subdirs = dir.list(db, Arrays.asList("app")).join();
// ["users", "posts", "comments"]

// List root directories
List<String> rootDirs = dir.list(db, Collections.emptyList()).join();
// ["app"]

Ruby API

list(
    db_or_tr,
    path = []
) -> Array

List subdirectories at the given path.

Parameters:

  • db_or_tr: Database, Tenant, or Transaction
  • path: Directory path as array (empty for root)

Returns: Array of subdirectory name strings

Example:

require 'fdb'
FDB.api_version(740)
db = FDB.open

# Create directory structure
FDB.directory.create_or_open(db, ['app', 'users'])
FDB.directory.create_or_open(db, ['app', 'posts'])
FDB.directory.create_or_open(db, ['app', 'comments'])

# List subdirectories
subdirs = FDB.directory.list(db, ['app'])
# ['users', 'posts', 'comments']

# List root directories
root_dirs = FDB.directory.list(db)
# ['app']

# List in transaction
subdirs = db.transact do |tr|
  FDB.directory.list(tr, ['users'])
end

Directory Moving

Move or rename directories while preserving their contents and allocated prefix.

Python API

DirectoryLayer.move(
    db_or_tr: Union[Database, Tenant, Transaction],
    old_path: Union[str, List[str]],
    new_path: Union[str, List[str]]
) -> DirectorySubspace

Move a directory to a new path. The directory's allocated prefix remains unchanged, so all existing data keys remain valid. Only the directory metadata is updated.

Parameters:

  • db_or_tr: Database, Tenant, or Transaction to use
  • old_path: Current directory path
  • new_path: New directory path

Returns: DirectorySubspace object at new path

Raises: Error if old path doesn't exist or new path already exists

Example:

import fdb
fdb.api_version(740)
db = fdb.open()

# Create directory and add data
users = fdb.directory.create_or_open(db, ['users'])
with db.create_transaction() as tr:
    tr[users.pack((123,))] = b'Alice'
    tr.commit().wait()

# Rename directory
users_v2 = fdb.directory.move(db, ['users'], ['users_v2'])

# Data is still accessible with new directory object
with db.create_transaction() as tr:
    value = tr[users_v2.pack((123,))]
    print(value)  # b'Alice'

# Move to nested path
fdb.directory.move(db, ['users_v2'], ['app', 'users'])

# Cannot move to existing directory
try:
    fdb.directory.move(db, ['app', 'users'], ['posts'])
except fdb.FDBError:
    print("Destination already exists")

Go API

func (d Directory) Move(
    t Transactor,
    oldPath []string,
    newPath []string
) (DirectorySubspace, error)

Move a directory to a new path.

Parameters:

  • t: Transactor (Database or Transaction)
  • oldPath: Current directory path
  • newPath: New directory path

Returns: DirectorySubspace at new path and error

Example:

import (
    "github.com/apple/foundationdb/bindings/go/src/fdb"
    "github.com/apple/foundationdb/bindings/go/src/fdb/directory"
    "github.com/apple/foundationdb/bindings/go/src/fdb/tuple"
)

// Create directory and add data
users, _ := directory.Root().CreateOrOpen(db, []string{"users"}, nil)
db.Transact(func(tr fdb.Transaction) (interface{}, error) {
    key := users.Pack(tuple.Tuple{123})
    tr.Set(key, []byte("Alice"))
    return nil, nil
})

// Rename directory
usersV2, err := directory.Root().Move(db, []string{"users"}, []string{"users_v2"})
if err != nil {
    log.Fatal(err)
}

// Data is still accessible
db.ReadTransact(func(tr fdb.ReadTransaction) (interface{}, error) {
    key := usersV2.Pack(tuple.Tuple{123})
    value := tr.Get(key).MustGet()
    fmt.Printf("Value: %s\n", value) // Value: Alice
    return nil, nil
})

// Move to nested path
directory.Root().Move(db, []string{"users_v2"}, []string{"app", "users"})

Java API

CompletableFuture<DirectorySubspace> move(
    TransactionContext tc,
    List<String> oldPath,
    List<String> newPath
)

Move a directory to a new path asynchronously.

Parameters:

  • tc: Transaction context
  • oldPath: Current directory path
  • newPath: New directory path

Returns: CompletableFuture<DirectorySubspace> at new path

Example:

import com.apple.foundationdb.*;
import com.apple.foundationdb.directory.*;
import com.apple.foundationdb.tuple.Tuple;
import java.util.*;

DirectoryLayer dir = DirectoryLayer.getDefault();

// Create directory and add data
DirectorySubspace users = dir.createOrOpen(db, Arrays.asList("users")).join();
db.run(tr -> {
    byte[] key = users.pack(Tuple.from(123));
    tr.set(key, "Alice".getBytes());
    return null;
});

// Rename directory
DirectorySubspace usersV2 = dir.move(
    db,
    Arrays.asList("users"),
    Arrays.asList("users_v2")
).join();

// Data is still accessible
db.read(tr -> {
    byte[] key = usersV2.pack(Tuple.from(123));
    byte[] value = tr.get(key).join();
    System.out.println("Value: " + new String(value)); // Value: Alice
    return null;
});

// Move to nested path
dir.move(db, Arrays.asList("users_v2"), Arrays.asList("app", "users")).join();

Ruby API

move(
    db_or_tr,
    old_path,
    new_path
) -> DirectorySubspace

Move a directory to a new path.

Parameters:

  • db_or_tr: Database, Tenant, or Transaction
  • old_path: Current directory path as array
  • new_path: New directory path as array

Returns: DirectorySubspace at new path

Raises: Error if old path doesn't exist or new path already exists

Example:

require 'fdb'
FDB.api_version(740)
db = FDB.open

# Create directory and add data
users = FDB.directory.create_or_open(db, ['users'])
db.transact do |tr|
  tr[users.pack([123])] = 'Alice'
end

# Rename directory
users_v2 = FDB.directory.move(db, ['users'], ['users_v2'])

# Data is still accessible
value = db.transact do |tr|
  tr[users_v2.pack([123])]
end
puts value  # Alice

# Move to nested path
FDB.directory.move(db, ['users_v2'], ['app', 'users'])

Directory Removal

Remove directories and all their contents.

Python API

DirectoryLayer.remove(
    db_or_tr: Union[Database, Tenant, Transaction],
    path: Union[str, List[str]]
) -> bool

Remove a directory and all its contents. This operation:

  • Deletes all keys within the directory's prefix
  • Removes directory metadata
  • Recursively removes all subdirectories

Parameters:

  • db_or_tr: Database, Tenant, or Transaction to use
  • path: Directory path to remove

Returns: True if removed

Raises: Error if directory doesn't exist

Warning: This deletes all data stored in the directory!

Example:

import fdb
fdb.api_version(740)
db = fdb.open()

# Create directory with data
temp = fdb.directory.create_or_open(db, ['temp'])
with db.create_transaction() as tr:
    tr[temp.pack((1,))] = b'data1'
    tr[temp.pack((2,))] = b'data2'
    tr.commit().wait()

# Remove directory and all contents
fdb.directory.remove(db, ['temp'])

# Verify removal
exists = fdb.directory.exists(db, ['temp'])
# False

# Error if directory doesn't exist
try:
    fdb.directory.remove(db, ['nonexistent'])
except fdb.FDBError:
    print("Directory doesn't exist")
DirectoryLayer.remove_if_exists(
    db_or_tr: Union[Database, Tenant, Transaction],
    path: Union[str, List[str]]
) -> bool

Remove a directory if it exists. Safe version of remove() that doesn't raise an error for non-existent directories.

Parameters:

  • db_or_tr: Database, Tenant, or Transaction to use
  • path: Directory path to remove

Returns: True if directory existed and was removed, False if didn't exist

Example:

# Safe removal - no error if doesn't exist
removed = fdb.directory.remove_if_exists(db, ['temp'])
if removed:
    print("Directory removed")
else:
    print("Directory didn't exist")

# Cleanup pattern
def cleanup_temp_directories(db):
    temp_dirs = ['temp1', 'temp2', 'temp3']
    for name in temp_dirs:
        fdb.directory.remove_if_exists(db, ['temp', name])

Go API

func (d Directory) Remove(
    t Transactor,
    path []string
) (bool, error)

Remove a directory and all its contents.

Parameters:

  • t: Transactor (Database or Transaction)
  • path: Directory path to remove

Returns: Boolean indicating removal and error

Example:

import (
    "github.com/apple/foundationdb/bindings/go/src/fdb"
    "github.com/apple/foundationdb/bindings/go/src/fdb/directory"
    "github.com/apple/foundationdb/bindings/go/src/fdb/tuple"
)

// Create directory with data
temp, _ := directory.Root().CreateOrOpen(db, []string{"temp"}, nil)
db.Transact(func(tr fdb.Transaction) (interface{}, error) {
    tr.Set(temp.Pack(tuple.Tuple{1}), []byte("data1"))
    tr.Set(temp.Pack(tuple.Tuple{2}), []byte("data2"))
    return nil, nil
})

// Remove directory and all contents
removed, err := directory.Root().Remove(db, []string{"temp"})
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Removed: %v\n", removed)

// Verify removal
exists, _ := directory.Root().Exists(db, []string{"temp"})
// exists: false
func (d Directory) RemoveIfExists(
    t Transactor,
    path []string
) (bool, error)

Remove a directory if it exists.

Parameters:

  • t: Transactor
  • path: Directory path

Returns: Boolean indicating if directory existed and was removed, and error

Example:

// Safe removal
removed, err := directory.Root().RemoveIfExists(db, []string{"temp"})
if err != nil {
    log.Fatal(err)
}
if removed {
    fmt.Println("Directory removed")
} else {
    fmt.Println("Directory didn't exist")
}

Java API

CompletableFuture<Boolean> remove(
    TransactionContext tc,
    List<String> path
)

Remove a directory and all its contents asynchronously.

Parameters:

  • tc: Transaction context
  • path: Directory path to remove

Returns: CompletableFuture<Boolean>

Example:

import com.apple.foundationdb.*;
import com.apple.foundationdb.directory.*;
import com.apple.foundationdb.tuple.Tuple;
import java.util.*;

DirectoryLayer dir = DirectoryLayer.getDefault();

// Create directory with data
DirectorySubspace temp = dir.createOrOpen(db, Arrays.asList("temp")).join();
db.run(tr -> {
    tr.set(temp.pack(Tuple.from(1)), "data1".getBytes());
    tr.set(temp.pack(Tuple.from(2)), "data2".getBytes());
    return null;
});

// Remove directory and all contents
boolean removed = dir.remove(db, Arrays.asList("temp")).join();
System.out.println("Removed: " + removed);

// Verify removal
boolean exists = dir.exists(db, Arrays.asList("temp")).join();
// exists: false
CompletableFuture<Boolean> removeIfExists(
    TransactionContext tc,
    List<String> path
)

Remove a directory if it exists.

Parameters:

  • tc: Transaction context
  • path: Directory path

Returns: CompletableFuture<Boolean> indicating if removed

Example:

// Safe removal
boolean removed = dir.removeIfExists(db, Arrays.asList("temp")).join();
if (removed) {
    System.out.println("Directory removed");
} else {
    System.out.println("Directory didn't exist");
}

Ruby API

remove(
    db_or_tr,
    path
) -> Boolean

Remove a directory and all its contents.

Parameters:

  • db_or_tr: Database, Tenant, or Transaction
  • path: Directory path as array

Returns: true

Raises: Error if directory doesn't exist

Example:

require 'fdb'
FDB.api_version(740)
db = FDB.open

# Create directory with data
temp = FDB.directory.create_or_open(db, ['temp'])
db.transact do |tr|
  tr[temp.pack([1])] = 'data1'
  tr[temp.pack([2])] = 'data2'
end

# Remove directory and all contents
FDB.directory.remove(db, ['temp'])

# Verify removal
exists = FDB.directory.exists?(db, ['temp'])
# false

DirectorySubspace

The DirectorySubspace class combines directory management with subspace functionality, allowing you to both manage the directory hierarchically and use it for data storage.

Python API

class DirectorySubspace

A directory that is also a Subspace. Inherits all methods from both Directory and Subspace.

Inherited from Subspace:

  • key() - Get raw prefix bytes
  • pack(items) - Encode tuple with directory prefix
  • unpack(key) - Decode key to tuple
  • range(items) - Get key range for tuple prefix
  • contains(key) - Check if key is in directory
  • subspace(items) - Create nested subspace

Directory-specific properties:

DirectorySubspace.get_layer() -> bytes

Get the layer identifier for the directory.

Returns: Layer bytes

DirectorySubspace.get_path() -> List[str]

Get the directory path.

Returns: List of path components

Example:

import fdb
fdb.api_version(740)
db = fdb.open()

# Create directory with layer tag
users = fdb.directory.create_or_open(db, ['users'], layer=b'user_data_v2')

# Use as subspace
@fdb.transactional
def store_user(tr, user_id, name, email):
    # Pack data with tuple encoding
    name_key = users.pack((user_id, 'name'))
    email_key = users.pack((user_id, 'email'))

    tr[name_key] = name.encode()
    tr[email_key] = email.encode()

store_user(db, 123, 'Alice', 'alice@example.com')

# Read with range query
@fdb.transactional
def get_user_data(tr, user_id):
    begin, end = users.range((user_id,))
    data = {}
    for kv in tr.get_range(begin, end):
        field = users.unpack(kv.key)[-1]
        data[field] = kv.value.decode()
    return data

user_data = get_user_data(db, 123)
# {'name': 'Alice', 'email': 'alice@example.com'}

# Directory properties
print(f"Path: {users.get_path()}")  # ['users']
print(f"Layer: {users.get_layer()}")  # b'user_data_v2'
print(f"Prefix: {users.key().hex()}")  # Allocated prefix bytes

# Create nested subspace
profiles = users.subspace(('profiles',))
profile_key = profiles.pack((123,))

Go API

type DirectorySubspace interface {
    Directory
    subspace.Subspace
    GetLayer() []byte
    GetPath() []string
}

A directory that is also a Subspace. Provides all methods from both interfaces.

Subspace methods:

  • Bytes() - Get raw prefix bytes
  • Pack(t tuple.Tuple) - Encode tuple with directory prefix
  • Unpack(k fdb.KeyConvertible) - Decode key to tuple
  • Contains(k fdb.KeyConvertible) - Check if key is in directory
  • Sub(el ...tuple.TupleElement) - Create nested subspace

Example:

import (
    "github.com/apple/foundationdb/bindings/go/src/fdb"
    "github.com/apple/foundationdb/bindings/go/src/fdb/directory"
    "github.com/apple/foundationdb/bindings/go/src/fdb/subspace"
    "github.com/apple/foundationdb/bindings/go/src/fdb/tuple"
)

// Create directory with layer tag
users, err := directory.Root().CreateOrOpen(
    db,
    []string{"users"},
    []byte("user_data_v2"),
)
if err != nil {
    log.Fatal(err)
}

// Use as subspace
_, err = db.Transact(func(tr fdb.Transaction) (interface{}, error) {
    // Pack data with tuple encoding
    nameKey := users.Pack(tuple.Tuple{123, "name"})
    emailKey := users.Pack(tuple.Tuple{123, "email"})

    tr.Set(nameKey, []byte("Alice"))
    tr.Set(emailKey, []byte("alice@example.com"))
    return nil, nil
})

// Read with range query
userData, _ := db.ReadTransact(func(tr fdb.ReadTransaction) (interface{}, error) {
    begin, end := users.FDBRangeKeys()
    ri := tr.GetRange(fdb.KeyRange{Begin: begin, End: end}, fdb.RangeOptions{}).Iterator()

    data := make(map[string]string)
    for ri.Advance() {
        kv, err := ri.Get()
        if err != nil {
            return nil, err
        }
        t, _ := users.Unpack(kv.Key)
        field := t[len(t)-1].(string)
        data[field] = string(kv.Value)
    }
    return data, nil
})

// Directory properties
fmt.Printf("Path: %v\n", users.GetPath())     // [users]
fmt.Printf("Layer: %s\n", users.GetLayer())   // user_data_v2
fmt.Printf("Prefix: %x\n", users.Bytes())     // Allocated prefix bytes

// Create nested subspace
profiles := users.Sub("profiles")
profileKey := profiles.Pack(tuple.Tuple{123})

Java API

interface DirectorySubspace extends Directory, Subspace {
    byte[] getLayer();
    List<String> getPath();
}

A directory that is also a Subspace. Implements both interfaces.

Subspace methods:

  • getKey() - Get raw prefix bytes
  • pack(Tuple tuple) - Encode tuple with directory prefix
  • unpack(byte[] key) - Decode key to tuple
  • contains(byte[] key) - Check if key is in directory
  • get(Object... items) - Create nested subspace

Example:

import com.apple.foundationdb.*;
import com.apple.foundationdb.directory.*;
import com.apple.foundationdb.subspace.Subspace;
import com.apple.foundationdb.tuple.Tuple;
import java.util.*;

// Create directory with layer tag
DirectorySubspace users = DirectoryLayer.getDefault()
    .createOrOpen(db, Arrays.asList("users"), "user_data_v2".getBytes())
    .join();

// Use as subspace
db.run(tr -> {
    // Pack data with tuple encoding
    byte[] nameKey = users.pack(Tuple.from(123, "name"));
    byte[] emailKey = users.pack(Tuple.from(123, "email"));

    tr.set(nameKey, "Alice".getBytes());
    tr.set(emailKey, "alice@example.com".getBytes());
    return null;
});

// Read with range query
db.read(tr -> {
    Range range = users.range(Tuple.from(123));
    Map<String, String> data = new HashMap<>();

    for (KeyValue kv : tr.getRange(range)) {
        Tuple t = users.unpack(kv.getKey());
        String field = t.getString(t.size() - 1);
        data.put(field, new String(kv.getValue()));
    }

    System.out.println(data);
    // {name=Alice, email=alice@example.com}
    return null;
});

// Directory properties
System.out.println("Path: " + users.getPath());          // [users]
System.out.println("Layer: " + new String(users.getLayer())); // user_data_v2
System.out.println("Prefix: " + bytesToHex(users.getKey()));  // Allocated prefix

// Create nested subspace
Subspace profiles = users.get("profiles");
byte[] profileKey = profiles.pack(Tuple.from(123));

Ruby API

class DirectorySubspace < Subspace

A directory that is also a Subspace. Inherits from Subspace and includes directory methods.

Subspace methods:

  • key() - Get raw prefix bytes
  • pack(items) - Encode tuple with directory prefix
  • unpack(key) - Decode key to tuple
  • range(items) - Get key range for tuple prefix
  • contains?(key) - Check if key is in directory
  • subspace(items) - Create nested subspace

Directory properties:

DirectorySubspace#layer -> String

Get the layer identifier for the directory.

Returns: Layer bytes

DirectorySubspace#path -> Array

Get the directory path.

Returns: Array of path components

Example:

require 'fdb'
FDB.api_version(740)
db = FDB.open

# Create directory with layer tag
users = FDB.directory.create_or_open(db, ['users'], layer: 'user_data_v2')

# Use as subspace
db.transact do |tr|
  # Pack data with tuple encoding
  name_key = users.pack([123, 'name'])
  email_key = users.pack([123, 'email'])

  tr[name_key] = 'Alice'
  tr[email_key] = 'alice@example.com'
end

# Read with range query
user_data = db.transact do |tr|
  begin_key, end_key = users.range([123])
  data = {}

  tr.get_range(begin_key, end_key).each do |kv|
    field = users.unpack(kv.key).last
    data[field] = kv.value
  end

  data
end
# {'name' => 'Alice', 'email' => 'alice@example.com'}

# Directory properties
puts "Path: #{users.path.inspect}"        # ["users"]
puts "Layer: #{users.layer}"              # user_data_v2
puts "Prefix: #{users.key.unpack('H*')}"  # Allocated prefix bytes

# Create nested subspace
profiles = users.subspace(['profiles'])
profile_key = profiles.pack([123])

Layer Tags

Layer tags are application-specific identifiers that mark directory types and enable type checking.

Concept

Layer tags allow you to:

  • Mark directories with their intended purpose
  • Prevent accidental misuse of directories
  • Enable multiple applications to coexist
  • Version your data schemas

Common patterns:

  • Empty layer b'' - Default, untyped directory
  • Application prefix: b'myapp:' - Application namespace
  • Schema version: b'users_v2' - Versioned schema
  • Type marker: b'timeseries' - Data type indicator

Python Example

import fdb
fdb.api_version(740)
db = fdb.open()

# Create directories with different layer tags
users_v1 = fdb.directory.create_or_open(db, ['users'], layer=b'user_schema_v1')
users_v2 = fdb.directory.create_or_open(db, ['users_new'], layer=b'user_schema_v2')
analytics = fdb.directory.create_or_open(db, ['analytics'], layer=b'timeseries')

# Layer validation on open
try:
    # This succeeds - layer matches
    users_dir = fdb.directory.open(db, ['users'], layer=b'user_schema_v1')
except fdb.FDBError:
    print("Layer mismatch")

try:
    # This fails - wrong layer
    users_dir = fdb.directory.open(db, ['users'], layer=b'user_schema_v2')
except fdb.FDBError:
    print("Layer mismatch detected!")

# Migration pattern
def migrate_users_v1_to_v2(db):
    """Migrate from v1 to v2 schema"""
    old_dir = fdb.directory.open(db, ['users'], layer=b'user_schema_v1')
    new_dir = fdb.directory.create_or_open(db, ['users_v2'], layer=b'user_schema_v2')

    @fdb.transactional
    def copy_and_transform(tr):
        # Read from old directory
        begin, end = old_dir.range()
        for kv in tr.get_range(begin, end):
            # Transform data for v2 schema
            old_data = old_dir.unpack(kv.key)
            new_data = transform_to_v2(old_data, kv.value)

            # Write to new directory
            new_key = new_dir.pack(new_data[0])
            tr[new_key] = new_data[1]

    copy_and_transform(db)

    # After migration, remove old directory
    fdb.directory.remove(db, ['users'])
    fdb.directory.move(db, ['users_v2'], ['users'])

# Application-specific layers for coexistence
APP_A_LAYER = b'application_a:'
APP_B_LAYER = b'application_b:'

app_a_users = fdb.directory.create_or_open(
    db, ['shared', 'users'],
    layer=APP_A_LAYER
)

app_b_users = fdb.directory.create_or_open(
    db, ['shared', 'users_b'],
    layer=APP_B_LAYER
)

Go Example

import (
    "github.com/apple/foundationdb/bindings/go/src/fdb"
    "github.com/apple/foundationdb/bindings/go/src/fdb/directory"
)

// Create directories with different layer tags
usersV1, _ := directory.Root().CreateOrOpen(
    db,
    []string{"users"},
    []byte("user_schema_v1"),
)

usersV2, _ := directory.Root().CreateOrOpen(
    db,
    []string{"users_new"},
    []byte("user_schema_v2"),
)

analytics, _ := directory.Root().CreateOrOpen(
    db,
    []string{"analytics"},
    []byte("timeseries"),
)

// Layer validation on open
usersDir, err := directory.Root().Open(
    db,
    []string{"users"},
    []byte("user_schema_v1"),
)
// Success - layer matches

_, err = directory.Root().Open(
    db,
    []string{"users"},
    []byte("user_schema_v2"),
)
// Error - layer mismatch

// Application-specific layers
const (
    AppALayer = "application_a:"
    AppBLayer = "application_b:"
)

appAUsers, _ := directory.Root().CreateOrOpen(
    db,
    []string{"shared", "users"},
    []byte(AppALayer),
)

appBUsers, _ := directory.Root().CreateOrOpen(
    db,
    []string{"shared", "users_b"},
    []byte(AppBLayer),
)

Java Example

import com.apple.foundationdb.*;
import com.apple.foundationdb.directory.*;
import java.util.*;

DirectoryLayer dir = DirectoryLayer.getDefault();

// Create directories with different layer tags
DirectorySubspace usersV1 = dir.createOrOpen(
    db,
    Arrays.asList("users"),
    "user_schema_v1".getBytes()
).join();

DirectorySubspace usersV2 = dir.createOrOpen(
    db,
    Arrays.asList("users_new"),
    "user_schema_v2".getBytes()
).join();

DirectorySubspace analytics = dir.createOrOpen(
    db,
    Arrays.asList("analytics"),
    "timeseries".getBytes()
).join();

// Layer validation on open
try {
    DirectorySubspace usersDir = dir.open(
        db,
        Arrays.asList("users"),
        "user_schema_v1".getBytes()
    ).join();
    // Success - layer matches
} catch (Exception e) {
    System.out.println("Layer mismatch");
}

try {
    DirectorySubspace usersDir = dir.open(
        db,
        Arrays.asList("users"),
        "user_schema_v2".getBytes()
    ).join();
} catch (Exception e) {
    System.out.println("Layer mismatch detected!");
}

// Application-specific layers
final byte[] APP_A_LAYER = "application_a:".getBytes();
final byte[] APP_B_LAYER = "application_b:".getBytes();

DirectorySubspace appAUsers = dir.createOrOpen(
    db,
    Arrays.asList("shared", "users"),
    APP_A_LAYER
).join();

DirectorySubspace appBUsers = dir.createOrOpen(
    db,
    Arrays.asList("shared", "users_b"),
    APP_B_LAYER
).join();

Ruby Example

require 'fdb'
FDB.api_version(740)
db = FDB.open

# Create directories with different layer tags
users_v1 = FDB.directory.create_or_open(db, ['users'], layer: 'user_schema_v1')
users_v2 = FDB.directory.create_or_open(db, ['users_new'], layer: 'user_schema_v2')
analytics = FDB.directory.create_or_open(db, ['analytics'], layer: 'timeseries')

# Layer validation on open
begin
  # This succeeds - layer matches
  users_dir = FDB.directory.open(db, ['users'], layer: 'user_schema_v1')
rescue FDB::Error
  puts "Layer mismatch"
end

begin
  # This fails - wrong layer
  users_dir = FDB.directory.open(db, ['users'], layer: 'user_schema_v2')
rescue FDB::Error
  puts "Layer mismatch detected!"
end

# Application-specific layers
APP_A_LAYER = 'application_a:'
APP_B_LAYER = 'application_b:'

app_a_users = FDB.directory.create_or_open(
  db,
  ['shared', 'users'],
  layer: APP_A_LAYER
)

app_b_users = FDB.directory.create_or_open(
  db,
  ['shared', 'users_b'],
  layer: APP_B_LAYER
)

Custom Prefixes

By default, the Directory Layer automatically allocates prefixes. For special cases, you can manually specify prefixes.

Enabling Manual Prefixes

Manual prefix specification must be explicitly enabled when creating a DirectoryLayer instance.

Python Example

import fdb
from fdb import directory_impl
fdb.api_version(740)
db = fdb.open()

# Create DirectoryLayer that allows manual prefixes
custom_dir_layer = directory_impl.DirectoryLayer(
    allow_manual_prefixes=True
)

# Specify manual prefix
system_dir = custom_dir_layer.create_or_open(
    db,
    ['system'],
    prefix=b'\x00\x00'
)

# Use specified prefix
print(system_dir.key().hex())  # 0000

# Note: Be very careful with manual prefixes
# - Ensure no collisions with other manual prefixes
# - Don't overlap with automatically allocated prefixes
# - Manual prefixes cannot be moved

Go Example

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

// Create DirectoryLayer that allows manual prefixes
nodeSubspace := subspace.FromBytes([]byte("\xFE"))
contentSubspace := subspace.FromBytes([]byte{})
customDirLayer := directory.NewDirectoryLayer(
    nodeSubspace,
    contentSubspace,
    true, // allowManualPrefixes
)

// Note: Manual prefix specification in Go requires
// using the appropriate Create method variant
// (exact API may vary by version)

Java Example

import com.apple.foundationdb.*;
import com.apple.foundationdb.directory.*;
import com.apple.foundationdb.subspace.Subspace;

// Create DirectoryLayer that allows manual prefixes
Subspace nodeSubspace = new Subspace(new byte[]{(byte)0xFE});
Subspace contentSubspace = new Subspace();
DirectoryLayer customDirLayer = new DirectoryLayer(
    nodeSubspace,
    contentSubspace,
    true  // allowManualPrefixes
);

// Specify manual prefix
DirectorySubspace systemDir = customDirLayer.create(
    db,
    Arrays.asList("system"),
    null,  // layer
    new byte[]{0x00, 0x00}  // manual prefix
).join();

System.out.println(bytesToHex(systemDir.getKey()));  // 0000

Ruby Example

require 'fdb'
FDB.api_version(740)
db = FDB.open

# Create DirectoryLayer that allows manual prefixes
custom_dir_layer = FDB::DirectoryLayer.new(
  allow_manual_prefixes: true
)

# Specify manual prefix
system_dir = custom_dir_layer.create_or_open(
  db,
  ['system'],
  prefix: "\x00\x00"
)

puts system_dir.key.unpack('H*')  # 0000

Important considerations for manual prefixes:

  • Only use when absolutely necessary (system directories, compatibility)
  • Manual prefixes cannot be moved with move()
  • Risk of collision with auto-allocated prefixes
  • Document manual prefix allocations carefully
  • Consider using high bytes (e.g., \xFE, \xFF) to avoid conflicts

Comprehensive Examples

Basic Directory Usage

Fundamental patterns for working with directories.

Python

import fdb
fdb.api_version(740)
db = fdb.open()

# Basic directory creation and usage
users = fdb.directory.create_or_open(db, ['users'])
posts = fdb.directory.create_or_open(db, ['posts'])

@fdb.transactional
def create_user(tr, user_id, name, email):
    """Store user data in users directory"""
    tr[users.pack((user_id, 'name'))] = name.encode()
    tr[users.pack((user_id, 'email'))] = email.encode()

@fdb.transactional
def get_user(tr, user_id):
    """Retrieve all data for a user"""
    begin, end = users.range((user_id,))
    user_data = {}
    for kv in tr.get_range(begin, end):
        field = users.unpack(kv.key)[-1]
        user_data[field] = kv.value.decode()
    return user_data

@fdb.transactional
def create_post(tr, post_id, author_id, content):
    """Store post data in posts directory"""
    tr[posts.pack((post_id, 'author'))] = str(author_id).encode()
    tr[posts.pack((post_id, 'content'))] = content.encode()

# Use the functions
create_user(db, 123, 'Alice', 'alice@example.com')
create_user(db, 456, 'Bob', 'bob@example.com')

create_post(db, 1, 123, 'Hello from Alice!')
create_post(db, 2, 456, 'Hello from Bob!')

# Retrieve data
user = get_user(db, 123)
print(f"User: {user}")
# User: {'name': 'Alice', 'email': 'alice@example.com'}

# List all users
@fdb.transactional
def list_all_users(tr):
    begin, end = users.range()
    user_ids = set()
    for kv in tr.get_range(begin, end):
        user_id = users.unpack(kv.key)[0]
        user_ids.add(user_id)
    return list(user_ids)

all_users = list_all_users(db)
print(f"All users: {all_users}")
# All users: [123, 456]

Go

import (
    "fmt"
    "github.com/apple/foundationdb/bindings/go/src/fdb"
    "github.com/apple/foundationdb/bindings/go/src/fdb/directory"
    "github.com/apple/foundationdb/bindings/go/src/fdb/tuple"
)

fdb.MustAPIVersion(740)
db := fdb.MustOpenDefault()

// Basic directory creation and usage
users, _ := directory.Root().CreateOrOpen(db, []string{"users"}, nil)
posts, _ := directory.Root().CreateOrOpen(db, []string{"posts"}, nil)

// Create user
createUser := func(userID int, name, email string) error {
    _, err := db.Transact(func(tr fdb.Transaction) (interface{}, error) {
        tr.Set(users.Pack(tuple.Tuple{userID, "name"}), []byte(name))
        tr.Set(users.Pack(tuple.Tuple{userID, "email"}), []byte(email))
        return nil, nil
    })
    return err
}

// Get user
getUser := func(userID int) (map[string]string, error) {
    return db.ReadTransact(func(tr fdb.ReadTransaction) (interface{}, error) {
        begin, end := users.FDBRangeKeys()
        ri := tr.GetRange(fdb.KeyRange{Begin: begin, End: end}, fdb.RangeOptions{}).Iterator()

        userData := make(map[string]string)
        for ri.Advance() {
            kv, err := ri.Get()
            if err != nil {
                return nil, err
            }
            t, _ := users.Unpack(kv.Key)
            if t[0].(int64) == int64(userID) {
                field := t[1].(string)
                userData[field] = string(kv.Value)
            }
        }
        return userData, nil
    })
}

// Create post
createPost := func(postID, authorID int, content string) error {
    _, err := db.Transact(func(tr fdb.Transaction) (interface{}, error) {
        tr.Set(posts.Pack(tuple.Tuple{postID, "author"}), []byte(fmt.Sprintf("%d", authorID)))
        tr.Set(posts.Pack(tuple.Tuple{postID, "content"}), []byte(content))
        return nil, nil
    })
    return err
}

// Use the functions
createUser(123, "Alice", "alice@example.com")
createUser(456, "Bob", "bob@example.com")

createPost(1, 123, "Hello from Alice!")
createPost(2, 456, "Hello from Bob!")

// Retrieve data
userData, _ := getUser(123)
fmt.Printf("User: %v\n", userData)
// User: map[email:alice@example.com name:Alice]

Hierarchical Organization Patterns

Organizing data with nested directory structures.

Python

import fdb
fdb.api_version(740)
db = fdb.open()

# Create hierarchical directory structure
app_dir = fdb.directory.create_or_open(db, ['myapp'])
users_dir = fdb.directory.create_or_open(db, ['myapp', 'users'])
profiles_dir = fdb.directory.create_or_open(db, ['myapp', 'users', 'profiles'])
settings_dir = fdb.directory.create_or_open(db, ['myapp', 'users', 'settings'])
posts_dir = fdb.directory.create_or_open(db, ['myapp', 'posts'])
comments_dir = fdb.directory.create_or_open(db, ['myapp', 'posts', 'comments'])

# Store data in hierarchical structure
@fdb.transactional
def create_user_with_profile(tr, user_id, name, bio, theme):
    # Basic user data
    tr[users_dir.pack((user_id,))] = name.encode()

    # Profile information
    tr[profiles_dir.pack((user_id, 'bio'))] = bio.encode()

    # User settings
    tr[settings_dir.pack((user_id, 'theme'))] = theme.encode()

create_user_with_profile(db, 123, 'Alice', 'Software developer', 'dark')

# List directory structure
def show_directory_tree(db, path=(), indent=0):
    """Recursively display directory structure"""
    subdirs = fdb.directory.list(db, path)
    for name in subdirs:
        print('  ' * indent + name)
        subpath = list(path) + [name]
        show_directory_tree(db, subpath, indent + 1)

print("Directory structure:")
show_directory_tree(db)
# Directory structure:
# myapp
#   users
#     profiles
#     settings
#   posts
#     comments

# Retrieve data from hierarchy
@fdb.transactional
def get_complete_user_data(tr, user_id):
    """Get all user data from multiple directory levels"""
    data = {}

    # Basic user data
    name = tr[users_dir.pack((user_id,))]
    data['name'] = name.decode() if name else None

    # Profile data
    bio_key = profiles_dir.pack((user_id, 'bio'))
    bio = tr[bio_key]
    data['bio'] = bio.decode() if bio else None

    # Settings data
    theme_key = settings_dir.pack((user_id, 'theme'))
    theme = tr[theme_key]
    data['theme'] = theme.decode() if theme else None

    return data

user_data = get_complete_user_data(db, 123)
print(user_data)
# {'name': 'Alice', 'bio': 'Software developer', 'theme': 'dark'}

Java

import com.apple.foundationdb.*;
import com.apple.foundationdb.directory.*;
import com.apple.foundationdb.tuple.Tuple;
import java.util.*;

FDB fdb = FDB.selectAPIVersion(740);
Database db = fdb.open();
DirectoryLayer dir = DirectoryLayer.getDefault();

// Create hierarchical directory structure
DirectorySubspace appDir = dir.createOrOpen(
    db, Arrays.asList("myapp")
).join();

DirectorySubspace usersDir = dir.createOrOpen(
    db, Arrays.asList("myapp", "users")
).join();

DirectorySubspace profilesDir = dir.createOrOpen(
    db, Arrays.asList("myapp", "users", "profiles")
).join();

DirectorySubspace settingsDir = dir.createOrOpen(
    db, Arrays.asList("myapp", "users", "settings")
).join();

DirectorySubspace postsDir = dir.createOrOpen(
    db, Arrays.asList("myapp", "posts")
).join();

// Store hierarchical data
db.run(tr -> {
    int userId = 123;

    // Basic user data
    tr.set(usersDir.pack(Tuple.from(userId)), "Alice".getBytes());

    // Profile information
    tr.set(profilesDir.pack(Tuple.from(userId, "bio")),
           "Software developer".getBytes());

    // User settings
    tr.set(settingsDir.pack(Tuple.from(userId, "theme")),
           "dark".getBytes());

    return null;
});

// List directory structure
void showDirectoryTree(Database db, List<String> path, int indent) {
    List<String> subdirs = dir.list(db, path).join();
    for (String name : subdirs) {
        System.out.println("  ".repeat(indent) + name);
        List<String> subpath = new ArrayList<>(path);
        subpath.add(name);
        showDirectoryTree(db, subpath, indent + 1);
    }
}

System.out.println("Directory structure:");
showDirectoryTree(db, Collections.emptyList(), 0);

Layer Tags for Type Separation

Using layer tags to separate different data types and versions.

Python

import fdb
fdb.api_version(740)
db = fdb.open()

# Define layer tags for different purposes
LAYER_USER_V1 = b'user_v1'
LAYER_USER_V2 = b'user_v2'
LAYER_TIMESERIES = b'timeseries'
LAYER_CACHE = b'cache'

# Create directories with specific layers
users_v1 = fdb.directory.create_or_open(
    db, ['users_v1'],
    layer=LAYER_USER_V1
)

users_v2 = fdb.directory.create_or_open(
    db, ['users_v2'],
    layer=LAYER_USER_V2
)

metrics = fdb.directory.create_or_open(
    db, ['metrics'],
    layer=LAYER_TIMESERIES
)

cache = fdb.directory.create_or_open(
    db, ['cache'],
    layer=LAYER_CACHE
)

# Type-safe directory access
class DirectoryManager:
    """Manage directories with layer validation"""

    def __init__(self, db):
        self.db = db

    def get_user_v1_dir(self):
        """Get user v1 directory with layer validation"""
        return fdb.directory.open(self.db, ['users_v1'], layer=LAYER_USER_V1)

    def get_user_v2_dir(self):
        """Get user v2 directory with layer validation"""
        return fdb.directory.open(self.db, ['users_v2'], layer=LAYER_USER_V2)

    def get_metrics_dir(self):
        """Get timeseries metrics directory"""
        return fdb.directory.open(self.db, ['metrics'], layer=LAYER_TIMESERIES)

    def get_cache_dir(self):
        """Get cache directory"""
        return fdb.directory.open(self.db, ['cache'], layer=LAYER_CACHE)

dm = DirectoryManager(db)

# Store data with different schemas
@fdb.transactional
def store_user_v1(tr, user_id, name):
    """Store user with v1 schema (simple)"""
    dir = dm.get_user_v1_dir()
    tr[dir.pack((user_id,))] = name.encode()

@fdb.transactional
def store_user_v2(tr, user_id, name, email, created_at):
    """Store user with v2 schema (enhanced)"""
    dir = dm.get_user_v2_dir()
    tr[dir.pack((user_id, 'name'))] = name.encode()
    tr[dir.pack((user_id, 'email'))] = email.encode()
    tr[dir.pack((user_id, 'created_at'))] = str(created_at).encode()

@fdb.transactional
def store_metric(tr, metric_name, timestamp, value):
    """Store timeseries metric"""
    dir = dm.get_metrics_dir()
    tr[dir.pack((metric_name, timestamp))] = str(value).encode()

# Migrate from v1 to v2
@fdb.transactional
def migrate_user_v1_to_v2(tr, user_id):
    """Migrate a user from v1 to v2 schema"""
    v1_dir = dm.get_user_v1_dir()
    v2_dir = dm.get_user_v2_dir()

    # Read v1 data
    name = tr[v1_dir.pack((user_id,))]
    if not name:
        return False

    # Write to v2 with additional fields
    tr[v2_dir.pack((user_id, 'name'))] = name
    tr[v2_dir.pack((user_id, 'email'))] = b'migrated@example.com'
    tr[v2_dir.pack((user_id, 'created_at'))] = b'0'

    # Remove from v1
    del tr[v1_dir.pack((user_id,))]

    return True

# Use the functions
store_user_v1(db, 100, 'Old User')
store_user_v2(db, 200, 'New User', 'new@example.com', 1234567890)

# Migrate user
migrate_user_v1_to_v2(db, 100)

# Type safety: This would fail if wrong layer
try:
    wrong_dir = fdb.directory.open(db, ['metrics'], layer=LAYER_USER_V1)
except fdb.FDBError:
    print("Correctly rejected wrong layer type!")

Moving and Reorganizing Directories

Demonstrate directory moving and reorganization without data migration.

Python

import fdb
fdb.api_version(740)
db = fdb.open()

# Initial directory structure
users_old = fdb.directory.create_or_open(db, ['users'])
posts_old = fdb.directory.create_or_open(db, ['posts'])

# Add some data
@fdb.transactional
def populate_data(tr):
    tr[users_old.pack((1,))] = b'Alice'
    tr[users_old.pack((2,))] = b'Bob'
    tr[posts_old.pack((101,))] = b'Post 1'
    tr[posts_old.pack((102,))] = b'Post 2'

populate_data(db)

# Reorganize into new structure
print("Original directories:", fdb.directory.list(db))
# ['users', 'posts']

# Create new app namespace
app_dir = fdb.directory.create_or_open(db, ['myapp'])

# Move directories under app namespace
users_new = fdb.directory.move(db, ['users'], ['myapp', 'users'])
posts_new = fdb.directory.move(db, ['posts'], ['myapp', 'posts'])

print("After reorganization:", fdb.directory.list(db))
# ['myapp']
print("Under myapp:", fdb.directory.list(db, ['myapp']))
# ['users', 'posts']

# Data is still accessible with new directory objects
@fdb.transactional
def verify_data(tr):
    # Read with new directory objects
    user1 = tr[users_new.pack((1,))]
    user2 = tr[users_new.pack((2,))]
    post1 = tr[posts_new.pack((101,))]
    post2 = tr[posts_new.pack((102,))]

    print(f"User 1: {user1}")  # b'Alice'
    print(f"User 2: {user2}")  # b'Bob'
    print(f"Post 1: {post1}")  # b'Post 1'
    print(f"Post 2: {post2}")  # b'Post 2'

verify_data(db)

# Rename a directory
fdb.directory.move(db, ['myapp', 'posts'], ['myapp', 'articles'])

print("After rename:", fdb.directory.list(db, ['myapp']))
# ['users', 'articles']

# Complex reorganization example
def reorganize_by_type(db):
    """Reorganize directories by data type"""

    # Create type-based directories
    fdb.directory.create_or_open(db, ['data', 'persistent'])
    fdb.directory.create_or_open(db, ['data', 'temporary'])

    # Move existing directories
    fdb.directory.move(db, ['myapp', 'users'], ['data', 'persistent', 'users'])
    fdb.directory.move(db, ['myapp', 'articles'], ['data', 'persistent', 'articles'])

    # Clean up old structure
    subdirs = fdb.directory.list(db, ['myapp'])
    if not subdirs:
        fdb.directory.remove(db, ['myapp'])

reorganize_by_type(db)

print("Final structure:")
def show_tree(path=(), indent=0):
    subdirs = fdb.directory.list(db, path)
    for name in subdirs:
        print('  ' * indent + name)
        show_tree(tuple(list(path) + [name]), indent + 1)

show_tree()
# data
#   persistent
#     users
#     articles
#   temporary

Integration with Subspace and Tuple Layers

Combining Directory Layer with Subspace and Tuple encoding.

Python

import fdb
import fdb.tuple
fdb.api_version(740)
db = fdb.open()

# Create directory structure
users = fdb.directory.create_or_open(db, ['users'])
sessions = fdb.directory.create_or_open(db, ['sessions'])

# DirectorySubspace inherits Subspace methods
# Use tuple encoding for complex keys

@fdb.transactional
def create_user_profile(tr, user_id, fields):
    """Store user profile with tuple-encoded keys"""
    # Tuple encoding handles different data types
    for field_name, value in fields.items():
        key = users.pack((user_id, 'profile', field_name))
        tr[key] = str(value).encode()

@fdb.transactional
def create_session(tr, user_id, session_id, ip_address, timestamp):
    """Store session with complex tuple key"""
    # Tuples can contain mixed types
    key = sessions.pack((user_id, timestamp, session_id))
    value = fdb.tuple.pack((ip_address, 'active'))
    tr[key] = value

# Create test data
create_user_profile(db, 123, {
    'name': 'Alice',
    'age': 30,
    'country': 'US'
})

import time
now = int(time.time())
create_session(db, 123, 'sess_abc', '192.168.1.1', now)
create_session(db, 123, 'sess_def', '192.168.1.2', now + 100)

# Query with range operations
@fdb.transactional
def get_user_profile(tr, user_id):
    """Retrieve all profile fields"""
    # Use range to get all profile fields
    begin, end = users.range((user_id, 'profile'))

    profile = {}
    for kv in tr.get_range(begin, end):
        # Unpack tuple to get field name
        *_, field_name = users.unpack(kv.key)
        profile[field_name] = kv.value.decode()

    return profile

profile = get_user_profile(db, 123)
print(f"Profile: {profile}")
# Profile: {'name': 'Alice', 'age': '30', 'country': 'US'}

@fdb.transactional
def get_user_sessions(tr, user_id, start_time=None, end_time=None):
    """Get user sessions in time range"""
    # Use tuple range with time boundaries
    if start_time is None:
        start_time = 0
    if end_time is None:
        end_time = 2**63 - 1

    begin = sessions.pack((user_id, start_time))
    end = sessions.pack((user_id, end_time))

    sessions_list = []
    for kv in tr.get_range(begin, end):
        # Unpack key and value
        user_id_key, timestamp, session_id = sessions.unpack(kv.key)
        ip_address, status = fdb.tuple.unpack(kv.value)

        sessions_list.append({
            'session_id': session_id,
            'timestamp': timestamp,
            'ip_address': ip_address,
            'status': status
        })

    return sessions_list

user_sessions = get_user_sessions(db, 123)
print(f"Sessions: {user_sessions}")

# Nested subspaces for organization
@fdb.transactional
def organize_with_nested_subspaces(tr):
    """Use nested subspaces for fine-grained organization"""
    # Create nested subspace for user 123
    user_123_space = users.subspace((123,))

    # Create further nested subspaces
    profile_space = user_123_space.subspace(('profile',))
    settings_space = user_123_space.subspace(('settings',))
    activity_space = user_123_space.subspace(('activity',))

    # Store data in nested subspaces
    tr[profile_space.pack(('name',))] = b'Alice'
    tr[settings_space.pack(('theme',))] = b'dark'
    tr[activity_space.pack((now, 'login'))] = b'success'

    # Each subspace has its own prefix
    print(f"User subspace: {user_123_space.key().hex()}")
    print(f"Profile subspace: {profile_space.key().hex()}")
    print(f"Settings subspace: {settings_space.key().hex()}")

organize_with_nested_subspaces(db)

Multi-Application Data Isolation

Using directories to isolate data between multiple applications sharing a database.

Python

import fdb
fdb.api_version(740)
db = fdb.open()

# Application 1: E-commerce platform
class EcommerceApp:
    def __init__(self, db):
        self.db = db
        self.root = fdb.directory.create_or_open(db, ['ecommerce'])
        self.users = fdb.directory.create_or_open(db, ['ecommerce', 'users'])
        self.products = fdb.directory.create_or_open(db, ['ecommerce', 'products'])
        self.orders = fdb.directory.create_or_open(db, ['ecommerce', 'orders'])

    @fdb.transactional
    def create_order(self, tr, order_id, user_id, product_id, quantity):
        tr[self.orders.pack((order_id, 'user'))] = str(user_id).encode()
        tr[self.orders.pack((order_id, 'product'))] = str(product_id).encode()
        tr[self.orders.pack((order_id, 'quantity'))] = str(quantity).encode()

    @fdb.transactional
    def get_order(self, tr, order_id):
        begin, end = self.orders.range((order_id,))
        order = {}
        for kv in tr.get_range(begin, end):
            field = self.orders.unpack(kv.key)[-1]
            order[field] = kv.value.decode()
        return order

# Application 2: Analytics platform
class AnalyticsApp:
    def __init__(self, db):
        self.db = db
        self.root = fdb.directory.create_or_open(db, ['analytics'])
        self.events = fdb.directory.create_or_open(db, ['analytics', 'events'])
        self.metrics = fdb.directory.create_or_open(db, ['analytics', 'metrics'])
        self.reports = fdb.directory.create_or_open(db, ['analytics', 'reports'])

    @fdb.transactional
    def log_event(self, tr, event_type, user_id, timestamp, data):
        key = self.events.pack((event_type, timestamp, user_id))
        tr[key] = data.encode()

    @fdb.transactional
    def get_events_by_type(self, tr, event_type, limit=100):
        begin, end = self.events.range((event_type,))
        events = []
        for kv in tr.get_range(begin, end, limit=limit):
            _, timestamp, user_id = self.events.unpack(kv.key)
            events.append({
                'timestamp': timestamp,
                'user_id': user_id,
                'data': kv.value.decode()
            })
        return events

# Application 3: Cache service
class CacheService:
    def __init__(self, db):
        self.db = db
        self.root = fdb.directory.create_or_open(db, ['cache'])
        self.data = fdb.directory.create_or_open(db, ['cache', 'data'])

    @fdb.transactional
    def set(self, tr, key, value, ttl):
        import time
        expires_at = int(time.time()) + ttl
        cache_key = self.data.pack((key, expires_at))
        tr[cache_key] = value.encode()

    @fdb.transactional
    def get(self, tr, key):
        import time
        now = int(time.time())

        # Search for non-expired entries
        begin, end = self.data.range((key,))
        for kv in tr.get_range(begin, end):
            cache_key, expires_at = self.data.unpack(kv.key)
            if expires_at > now:
                return kv.value.decode()
        return None

# Initialize all applications
ecommerce = EcommerceApp(db)
analytics = AnalyticsApp(db)
cache = CacheService(db)

# Each application works in its own namespace
ecommerce.create_order(db, 1001, 123, 456, 2)
analytics.log_event(db, 'purchase', 123, 1234567890, 'order_1001')
cache.set(db, 'user_123_profile', 'Alice', ttl=3600)

# Verify isolation
print("All top-level directories:", fdb.directory.list(db))
# ['ecommerce', 'analytics', 'cache']

print("Ecommerce subdirs:", fdb.directory.list(db, ['ecommerce']))
# ['users', 'products', 'orders']

print("Analytics subdirs:", fdb.directory.list(db, ['analytics']))
# ['events', 'metrics', 'reports']

# Applications can be removed independently
def remove_application(db, app_name):
    """Completely remove an application's data"""
    if fdb.directory.exists(db, [app_name]):
        fdb.directory.remove(db, [app_name])
        print(f"Removed {app_name}")

# Remove cache application (doesn't affect others)
remove_application(db, 'cache')

print("After removing cache:", fdb.directory.list(db))
# ['ecommerce', 'analytics']

# Cross-application access pattern
class ApplicationRegistry:
    """Central registry for multi-application access"""

    def __init__(self, db):
        self.db = db
        self.apps = {}

    def register(self, name, app):
        self.apps[name] = app

    def get_app(self, name):
        return self.apps.get(name)

    def list_apps(self):
        """List all registered applications"""
        return fdb.directory.list(self.db)

    def get_app_stats(self, app_name):
        """Get statistics about an application's data"""
        if not fdb.directory.exists(self.db, [app_name]):
            return None

        @fdb.transactional
        def count_keys(tr):
            app_dir = fdb.directory.open(tr, [app_name])
            begin, end = app_dir.range()
            count = 0
            for _ in tr.get_range(begin, end):
                count += 1
            return count

        return {
            'name': app_name,
            'subdirectories': fdb.directory.list(self.db, [app_name]),
            'key_count': count_keys(self.db)
        }

registry = ApplicationRegistry(db)
registry.register('ecommerce', ecommerce)
registry.register('analytics', analytics)

# Get statistics
for app_name in registry.list_apps():
    stats = registry.get_app_stats(app_name)
    print(f"App: {stats['name']}, Subdirs: {stats['subdirectories']}, Keys: {stats['key_count']}")

Best Practices

Directory Design

  1. Plan your hierarchy carefully

    • Use meaningful, descriptive names
    • Group related data logically
    • Consider future growth and reorganization needs
    • Keep hierarchy depth reasonable (3-5 levels typically)
  2. Use consistent naming conventions

    # Good: Consistent, clear naming
    fdb.directory.create_or_open(db, ['app', 'users', 'profiles'])
    fdb.directory.create_or_open(db, ['app', 'users', 'settings'])
    fdb.directory.create_or_open(db, ['app', 'posts', 'content'])
    
    # Avoid: Inconsistent naming
    fdb.directory.create_or_open(db, ['app', 'Users', 'PROFILES'])
    fdb.directory.create_or_open(db, ['app', 'userSettings'])
  3. Separate applications and versions

    # Application separation
    app1_users = fdb.directory.create_or_open(db, ['app1', 'users'])
    app2_users = fdb.directory.create_or_open(db, ['app2', 'users'])
    
    # Version separation with layer tags
    users_v1 = fdb.directory.create_or_open(db, ['users'], layer=b'v1')
    users_v2 = fdb.directory.create_or_open(db, ['users_v2'], layer=b'v2')

Directory Management

  1. Use create_or_open for idempotent operations

    # Safe for repeated calls
    users = fdb.directory.create_or_open(db, ['users'])
    
    # Only when you need strict creation
    try:
        new_dir = fdb.directory.create(db, ['temp'])
    except fdb.FDBError:
        pass  # Already exists
  2. Check existence before expensive operations

    # Check before removal
    if fdb.directory.exists(db, ['temp']):
        fdb.directory.remove(db, ['temp'])
    
    # Or use remove_if_exists
    fdb.directory.remove_if_exists(db, ['temp'])
  3. Move directories instead of migrating data

    # Efficient: Just updates metadata
    fdb.directory.move(db, ['old_path'], ['new_path'])
    
    # Inefficient: Don't do this unless schema changes
    # old_dir = fdb.directory.open(db, ['old'])
    # new_dir = fdb.directory.create(db, ['new'])
    # for kv in tr.get_range(old_dir.range()):
    #     new_key = transform_key(kv.key)
    #     tr[new_key] = kv.value
    # fdb.directory.remove(db, ['old'])
  4. Use layer tags for type safety

    # Define layer constants
    LAYER_USER = b'user_data_v2'
    LAYER_POST = b'post_data_v1'
    
    # Create with layer tags
    users = fdb.directory.create_or_open(db, ['users'], layer=LAYER_USER)
    posts = fdb.directory.create_or_open(db, ['posts'], layer=LAYER_POST)
    
    # Validate on open
    users = fdb.directory.open(db, ['users'], layer=LAYER_USER)

Performance Considerations

  1. Cache directory objects

    # Good: Open once, reuse
    class DataManager:
        def __init__(self, db):
            self.db = db
            self.users_dir = fdb.directory.create_or_open(db, ['users'])
            self.posts_dir = fdb.directory.create_or_open(db, ['posts'])
    
        def store_user(self, tr, user_id, data):
            tr[self.users_dir.pack((user_id,))] = data
    
    manager = DataManager(db)
    
    # Avoid: Opening in every operation
    def store_user_bad(tr, user_id, data):
        users = fdb.directory.open(db, ['users'])  # Inefficient
        tr[users.pack((user_id,))] = data
  2. Batch directory operations

    # Efficient: Single transaction
    @fdb.transactional
    def initialize_app_structure(tr):
        dirs = [
            ['app', 'users'],
            ['app', 'posts'],
            ['app', 'comments'],
            ['app', 'sessions']
        ]
        for path in dirs:
            fdb.directory.create_or_open(tr, path)
    
    initialize_app_structure(db)
  3. Use transactions for directory operations

    # Atomic directory operations
    @fdb.transactional
    def reorganize_atomically(tr):
        # All operations succeed or all fail
        fdb.directory.create(tr, ['new_structure'])
        fdb.directory.move(tr, ['old'], ['new_structure', 'old'])
        fdb.directory.remove_if_exists(tr, ['temp'])

Error Handling

  1. Handle common errors gracefully

    # Directory already exists
    try:
        dir = fdb.directory.create(db, ['users'])
    except fdb.FDBError as e:
        if e.code == 1020:  # directory_already_exists
            dir = fdb.directory.open(db, ['users'])
        else:
            raise
    
    # Directory doesn't exist
    try:
        dir = fdb.directory.open(db, ['users'])
    except fdb.FDBError as e:
        if e.code == 2100:  # path doesn't exist
            dir = fdb.directory.create(db, ['users'])
        else:
            raise
    
    # Or use exists() to check
    if not fdb.directory.exists(db, ['users']):
        fdb.directory.create(db, ['users'])
  2. Validate layer tags

    def open_with_version_check(db, path, expected_layer):
        """Open directory with version validation"""
        try:
            return fdb.directory.open(db, path, layer=expected_layer)
        except fdb.FDBError:
            # Layer mismatch or doesn't exist
            actual_layer = None
            if fdb.directory.exists(db, path):
                dir = fdb.directory.open(db, path)
                actual_layer = dir.get_layer()
    
            raise ValueError(
                f"Directory {path} has wrong layer. "
                f"Expected {expected_layer}, got {actual_layer}"
            )

Migration and Versioning

  1. Schema migration pattern

    def migrate_schema_v1_to_v2(db):
        """Migrate from schema v1 to v2"""
        LAYER_V1 = b'schema_v1'
        LAYER_V2 = b'schema_v2'
    
        # Check if migration needed
        if not fdb.directory.exists(db, ['data']):
            return
    
        data_dir = fdb.directory.open(db, ['data'])
        if data_dir.get_layer() == LAYER_V2:
            return  # Already migrated
    
        # Create new v2 directory
        data_v2 = fdb.directory.create_or_open(
            db, ['data_v2'],
            layer=LAYER_V2
        )
    
        # Migrate data
        @fdb.transactional
        def migrate_data(tr):
            begin, end = data_dir.range()
            for kv in tr.get_range(begin, end):
                # Transform data for v2
                old_tuple = data_dir.unpack(kv.key)
                new_tuple = transform_for_v2(old_tuple)
                new_key = data_v2.pack(new_tuple)
                tr[new_key] = transform_value_v2(kv.value)
    
        migrate_data(db)
    
        # Swap directories
        fdb.directory.move(db, ['data'], ['data_v1_backup'])
        fdb.directory.move(db, ['data_v2'], ['data'])
  2. Maintain migration history

    def record_migration(db, from_version, to_version):
        """Record schema migration in directory metadata"""
        import time
    
        migrations_dir = fdb.directory.create_or_open(db, ['_migrations'])
    
        @fdb.transactional
        def record(tr):
            timestamp = int(time.time())
            key = migrations_dir.pack((timestamp, from_version, to_version))
            tr[key] = b'completed'
    
        record(db)

Security and Access Control

  1. Isolate sensitive data

    # Use separate directories for different security levels
    public_data = fdb.directory.create_or_open(db, ['public'])
    private_data = fdb.directory.create_or_open(db, ['private'])
    admin_data = fdb.directory.create_or_open(db, ['admin'])
  2. Use tenants for multi-tenant applications

    # Tenant-based isolation (FoundationDB 7.1+)
    tenant_a = db.open_tenant(b'tenant_a')
    tenant_b = db.open_tenant(b'tenant_b')
    
    # Each tenant has isolated directory namespace
    tenant_a_users = fdb.directory.create_or_open(tenant_a, ['users'])
    tenant_b_users = fdb.directory.create_or_open(tenant_b, ['users'])

Summary

The Directory Layer provides powerful hierarchical namespace management for FoundationDB:

  • Automatic prefix allocation eliminates manual coordination
  • Hierarchical organization enables natural data structuring
  • Directory operations (move, rename) work without data migration
  • Layer tags provide type safety and versioning
  • DirectorySubspace combines directory management with subspace functionality
  • Multi-language support with consistent APIs across Python, Go, Java, and Ruby

Use the Directory Layer when building multi-application systems, when you need relocatable data namespaces, or when you want to organize data hierarchically without manual prefix management.