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.mddocs/reference/

Subspace Operations

Organize keyspace with tuple-based prefixes for logical partitioning and namespace management. Subspaces provide a convenient way to create namespaces without manual prefix management.

Capabilities

Subspace Creation

Create subspaces with various prefix types.

/**
 * Create subspace with empty prefix.
 */
class Subspace {
    Subspace();
}

/**
 * Create subspace with tuple prefix.
 * 
 * Parameters:
 * - prefix: Tuple - Tuple used as prefix
 */
class Subspace {
    Subspace(Tuple prefix);
}

/**
 * Create subspace with raw byte prefix.
 * 
 * Parameters:
 * - rawPrefix: byte[] - Raw bytes used as prefix
 */
class Subspace {
    Subspace(byte[] rawPrefix);
}

/**
 * Create subspace with both tuple and raw prefix.
 * Final prefix is tuple bytes + raw bytes.
 * 
 * Parameters:
 * - prefix: Tuple - Tuple prefix
 * - rawPrefix: byte[] - Additional raw byte prefix
 */
class Subspace {
    Subspace(Tuple prefix, byte[] rawPrefix);
}

Usage examples:

import com.apple.foundationdb.subspace.Subspace;
import com.apple.foundationdb.tuple.Tuple;

// Empty subspace (no prefix)
Subspace root = new Subspace();

// Tuple prefix
Subspace users = new Subspace(Tuple.from("users"));
Subspace userProfiles = new Subspace(Tuple.from("users", "profiles"));

// Raw byte prefix
byte[] rawPrefix = new byte[]{0x01, 0x02, 0x03};
Subspace custom = new Subspace(rawPrefix);

// Combined tuple + raw prefix
Subspace combined = new Subspace(
    Tuple.from("app"),
    "custom".getBytes()
);

Creating Child Subspaces

Create nested subspaces by extending prefix.

/**
 * Create child subspace by extending with object.
 * Object is packed as single-element tuple.
 * 
 * Parameters:
 * - obj: Object - Object to append to prefix
 * 
 * Returns:
 * Subspace - New subspace with extended prefix
 */
Subspace Subspace.get(Object obj);

/**
 * Create child subspace by extending with tuple.
 * 
 * Parameters:
 * - tuple: Tuple - Tuple to append to prefix
 * 
 * Returns:
 * Subspace - New subspace with extended prefix
 */
Subspace Subspace.get(Tuple tuple);

/**
 * Create child subspace (alias for get(tuple)).
 * 
 * Parameters:
 * - tuple: Tuple - Tuple to append to prefix
 * 
 * Returns:
 * Subspace - New subspace with extended prefix
 */
Subspace Subspace.subspace(Tuple tuple);

Usage examples:

// Create hierarchy
Subspace users = new Subspace(Tuple.from("users"));

// Extend with single object
Subspace user1001 = users.get(1001L);
Subspace user1002 = users.get(1002L);

// Extend with tuple
Subspace profile = user1001.get(Tuple.from("profile", "email"));

// Using subspace method
Subspace settings = user1001.subspace(Tuple.from("settings"));

// Multi-level hierarchy
Subspace app = new Subspace(Tuple.from("myapp"));
Subspace userSpace = app.get("users");
Subspace userInstance = userSpace.get(1001L);
Subspace userData = userInstance.get("data");

Packing Keys

Convert tuples and objects to prefixed byte arrays.

/**
 * Pack empty tuple (returns prefix).
 * 
 * Returns:
 * byte[] - Subspace prefix
 */
byte[] Subspace.pack();

/**
 * Pack single object in this subspace.
 * Object is packed as single-element tuple.
 * 
 * Parameters:
 * - obj: Object - Object to pack
 * 
 * Returns:
 * byte[] - Prefix + packed object
 */
byte[] Subspace.pack(Object obj);

/**
 * Pack tuple in this subspace.
 * 
 * Parameters:
 * - tuple: Tuple - Tuple to pack
 * 
 * Returns:
 * byte[] - Prefix + packed tuple
 */
byte[] Subspace.pack(Tuple tuple);

/**
 * Pack tuple with incomplete versionstamp.
 * 
 * Parameters:
 * - tuple: Tuple - Tuple containing incomplete versionstamp
 * 
 * Returns:
 * byte[] - Prefix + packed tuple with versionstamp placeholder
 * 
 * Throws:
 * IllegalArgumentException - If tuple has no incomplete versionstamp
 */
byte[] Subspace.packWithVersionstamp(Tuple tuple);

Usage examples:

Subspace users = new Subspace(Tuple.from("users"));

// Pack prefix alone
byte[] prefix = users.pack();

// Pack single value
byte[] key1 = users.pack(1001L);
byte[] key2 = users.pack("alice");

// Pack tuple
byte[] key3 = users.pack(Tuple.from(1001L, "profile"));
byte[] key4 = users.pack(Tuple.from(1001L, "email", "primary"));

// Use as FDB key
db.run(tr -> {
    tr.set(key3, "profile data".getBytes());
    tr.set(key4, "alice@example.com".getBytes());
    return null;
});

// Pack with versionstamp
Subspace logs = new Subspace(Tuple.from("logs"));
byte[] timestampedKey = logs.packWithVersionstamp(
    Tuple.from("event", Versionstamp.incomplete())
);

db.run(tr -> {
    tr.mutate(MutationType.SET_VERSIONSTAMPED_KEY, 
              timestampedKey, "log entry".getBytes());
    return null;
});

Unpacking Keys

Extract tuples from prefixed keys.

/**
 * Unpack key to tuple, removing prefix.
 * 
 * Parameters:
 * - key: byte[] - Packed key with this subspace's prefix
 * 
 * Returns:
 * Tuple - Unpacked tuple (without prefix)
 * 
 * Throws:
 * IllegalArgumentException - If key doesn't start with prefix
 */
Tuple Subspace.unpack(byte[] key);

/**
 * Unpack key and convert to desired type.
 * 
 * Type Parameters:
 * - T: Result type
 * 
 * Parameters:
 * - key: byte[] - Packed key with this subspace's prefix
 * - conversion: Function<Tuple, T> - Function to convert tuple to desired type
 * 
 * Returns:
 * T - Converted value
 */
<T> T Subspace.unpack(byte[] key, Function<Tuple, T> conversion);

Usage examples:

Subspace users = new Subspace(Tuple.from("users"));

// Pack and unpack
byte[] key = users.pack(Tuple.from(1001L, "profile"));
Tuple tuple = users.unpack(key);

long userId = tuple.getLong(0);     // 1001
String section = tuple.getString(1); // "profile"

// Unpack with conversion
byte[] emailKey = users.pack(Tuple.from(1001L, "email"));
String userId = users.unpack(emailKey, t -> 
    "user_" + t.getLong(0)
);

// Unpack range query results
db.read(tr -> {
    Range range = users.range();
    for (KeyValue kv : tr.getRange(range)) {
        Tuple key = users.unpack(kv.getKey());
        // Process unpacked key
        System.out.println("User: " + key.getLong(0));
    }
    return null;
});

Range Operations

Get ranges covering keys in subspace.

/**
 * Get range covering all keys in this subspace.
 * 
 * Returns:
 * Range - Range from prefix to end of prefix range
 */
Range Subspace.range();

/**
 * Get range covering keys with additional tuple suffix.
 * 
 * Parameters:
 * - tuple: Tuple - Tuple suffix to narrow range
 * 
 * Returns:
 * Range - Range for prefix + tuple
 */
Range Subspace.range(Tuple tuple);

Usage examples:

Subspace users = new Subspace(Tuple.from("users"));

// Get all keys in subspace
Range allUsers = users.range();

db.read(tr -> {
    for (KeyValue kv : tr.getRange(allUsers)) {
        Tuple key = users.unpack(kv.getKey());
        // Process all user keys
    }
    return null;
});

// Get range for specific user
Subspace user1001 = users.get(1001L);
Range user1001Range = user1001.range();

db.read(tr -> {
    for (KeyValue kv : tr.getRange(user1001Range)) {
        Tuple key = users.unpack(kv.getKey());
        // Process keys for user 1001
    }
    return null;
});

// Get range with tuple suffix
Range profileRange = users.range(Tuple.from(1001L, "profile"));

// Clear all keys in subspace
db.run(tr -> {
    tr.clear(users.range());
    return null;
});

Subspace Properties

Query subspace properties and validate keys.

/**
 * Check if key belongs to this subspace.
 * 
 * Parameters:
 * - key: byte[] - Key to check
 * 
 * Returns:
 * boolean - True if key starts with this subspace's prefix
 */
boolean Subspace.contains(byte[] key);

/**
 * Get subspace prefix key.
 * 
 * Returns:
 * byte[] - Prefix bytes for this subspace
 */
byte[] Subspace.getKey();

/**
 * Get raw prefix bytes.
 * Alias for getKey().
 * 
 * Returns:
 * byte[] - Prefix bytes
 */
byte[] Subspace.getRawPrefix();

/**
 * Check equality with another object.
 * 
 * Parameters:
 * - rhs: Object - Object to compare
 * 
 * Returns:
 * boolean - True if equal
 */
boolean Subspace.equals(Object rhs);

/**
 * Get hash code.
 * 
 * Returns:
 * int - Hash code
 */
int Subspace.hashCode();

/**
 * Get string representation.
 * 
 * Returns:
 * String - Human-readable representation
 */
String Subspace.toString();

Usage examples:

Subspace users = new Subspace(Tuple.from("users"));
Subspace products = new Subspace(Tuple.from("products"));

// Check key membership
byte[] userKey = users.pack(Tuple.from(1001L));
byte[] productKey = products.pack(Tuple.from("SKU123"));

boolean inUsers = users.contains(userKey);     // true
boolean inProducts = users.contains(productKey); // false

// Get prefix
byte[] prefix = users.getKey();
// Same as users.pack()

// Compare subspaces
Subspace users2 = new Subspace(Tuple.from("users"));
boolean same = users.equals(users2);  // true

// Use in collections
Set<Subspace> subspaces = new HashSet<>();
subspaces.add(users);
subspaces.add(products);

// Validate keys before unpacking
db.read(tr -> {
    byte[] someKey = "random_key".getBytes();
    
    if (users.contains(someKey)) {
        Tuple tuple = users.unpack(someKey);
        // Process user key
    } else {
        // Not a user key
    }
    return null;
});

Types

class Subspace {
    Subspace();
    Subspace(Tuple prefix);
    Subspace(byte[] rawPrefix);
    Subspace(Tuple prefix, byte[] rawPrefix);
    
    // All methods documented above
}

class DirectorySubspace extends Subspace implements Directory {
    // Combines Subspace with Directory functionality
    long getDirectoryID();
}

Usage Patterns

Organizing Data

// Application structure
Subspace app = new Subspace(Tuple.from("myapp"));

// Entity types
Subspace users = app.get("users");
Subspace products = app.get("products");
Subspace orders = app.get("orders");

// Per-user data
Subspace user1001 = users.get(1001L);
byte[] profileKey = user1001.pack(Tuple.from("profile"));
byte[] settingsKey = user1001.pack(Tuple.from("settings"));

// Per-product data
Subspace product = products.get("SKU123");
byte[] nameKey = product.pack(Tuple.from("name"));
byte[] priceKey = product.pack(Tuple.from("price"));

Secondary Indexes

// Primary data
Subspace users = new Subspace(Tuple.from("users"));
byte[] userKey = users.pack(Tuple.from(1001L));

// Secondary index by email
Subspace emailIndex = new Subspace(Tuple.from("email_index"));
byte[] indexKey = emailIndex.pack(Tuple.from("alice@example.com", 1001L));

db.run(tr -> {
    // Write primary data
    tr.set(userKey, "Alice".getBytes());
    
    // Write index entry
    tr.set(indexKey, new byte[0]);
    
    return null;
});

// Query by email
db.read(tr -> {
    Range emailRange = emailIndex.range(Tuple.from("alice@example.com"));
    
    for (KeyValue kv : tr.getRange(emailRange)) {
        Tuple indexEntry = emailIndex.unpack(kv.getKey());
        long userId = indexEntry.getLong(1);
        
        // Fetch user data
        byte[] userData = tr.get(users.pack(Tuple.from(userId))).join();
    }
    return null;
});

Versioned Data

// Store multiple versions
Subspace documents = new Subspace(Tuple.from("documents"));

long docId = 12345L;
long version1 = 1L;
long version2 = 2L;

byte[] v1Key = documents.pack(Tuple.from(docId, version1));
byte[] v2Key = documents.pack(Tuple.from(docId, version2));

db.run(tr -> {
    tr.set(v1Key, "Version 1 content".getBytes());
    tr.set(v2Key, "Version 2 content".getBytes());
    return null;
});

// Query all versions of document
db.read(tr -> {
    Range docRange = documents.range(Tuple.from(docId));
    
    for (KeyValue kv : tr.getRange(docRange)) {
        Tuple key = documents.unpack(kv.getKey());
        long version = key.getLong(1);
        System.out.println("Version " + version + ": " + 
                          new String(kv.getValue()));
    }
    return null;
});

Important Notes

Prefix Management

  • Subspaces automate prefix handling
  • Eliminates manual byte array concatenation
  • Ensures proper tuple encoding
  • Prevents prefix conflicts

Key Organization

  • Use subspaces for logical partitioning
  • Create hierarchy with get() method
  • Each level of hierarchy is a subspace
  • Clear entire subspace with range()

Tuple Integration

  • Subspace prefixes use tuple encoding
  • Preserves lexicographic ordering
  • Supports all tuple types
  • Safe for structured keys

Performance

  • Prefix lookup is O(1)
  • Pack/unpack is efficient
  • Subspace objects are lightweight
  • Reuse subspace objects

Immutability

  • Subspace objects are immutable
  • get() returns new subspace
  • Safe to share across threads
  • Can cache subspace instances

Comparison with Directory Layer

  • Subspaces: Simple prefix management
  • Directory Layer: Dynamic prefix allocation
  • Use subspaces for static hierarchies
  • Use directories for dynamic namespaces