Organize keyspace with tuple-based prefixes for logical partitioning and namespace management. Subspaces provide a convenient way to create namespaces without manual prefix management.
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()
);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");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;
});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;
});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;
});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;
});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();
}// 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"));// 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;
});// 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;
});