Multi-tenancy support for secure key-space isolation in shared FoundationDB deployments. Tenants provide logical separation of data with independent key-spaces.
Open existing tenants for transaction execution.
/**
* Open an existing tenant using default executor.
* Opening does not validate tenant existence - errors occur on first use.
*
* Parameters:
* - tenantName: byte[] - Name of tenant to open
*
* Returns:
* Tenant - Tenant object for creating transactions
*/
Tenant Database.openTenant(byte[] tenantName);
/**
* Open tenant using Tuple name (convenience method).
* Tenant name is generated by packing the Tuple.
*
* Parameters:
* - tenantName: Tuple - Tenant name as Tuple
*
* Returns:
* Tenant - Tenant object for creating transactions
*/
Tenant Database.openTenant(Tuple tenantName);
/**
* Open tenant with custom executor for async operations.
*
* Parameters:
* - tenantName: byte[] - Name of tenant to open
* - e: Executor - Executor for asynchronous callbacks
*
* Returns:
* Tenant - Tenant object for creating transactions
*/
Tenant Database.openTenant(byte[] tenantName, Executor e);
/**
* Open tenant using Tuple name with custom executor.
*
* Parameters:
* - tenantName: Tuple - Tenant name as Tuple
* - e: Executor - Executor for asynchronous callbacks
*
* Returns:
* Tenant - Tenant object for creating transactions
*/
Tenant Database.openTenant(Tuple tenantName, Executor e);
/**
* Open tenant with custom executor and event tracking.
*
* Parameters:
* - tenantName: byte[] - Name of tenant to open
* - e: Executor - Executor for asynchronous callbacks
* - eventKeeper: EventKeeper - Instrumentation for tracking operations
*
* Returns:
* Tenant - Tenant object for creating transactions
*/
Tenant Database.openTenant(byte[] tenantName, Executor e, EventKeeper eventKeeper);
/**
* Open tenant using Tuple name with custom executor and event tracking.
*
* Parameters:
* - tenantName: Tuple - Tenant name as Tuple
* - e: Executor - Executor for asynchronous callbacks
* - eventKeeper: EventKeeper - Instrumentation for tracking operations
*
* Returns:
* Tenant - Tenant object for creating transactions
*/
Tenant Database.openTenant(Tuple tenantName, Executor e, EventKeeper eventKeeper);Usage examples:
import com.apple.foundationdb.*;
import com.apple.foundationdb.tuple.Tuple;
// Open tenant with byte array name
Tenant tenant1 = db.openTenant("app_tenant_1".getBytes());
// Open tenant with Tuple name
Tenant tenant2 = db.openTenant(Tuple.from("app", "tenant", 1));
// Open tenant with custom executor
Executor customExecutor = Executors.newFixedThreadPool(10);
Tenant tenant3 = db.openTenant("tenant3".getBytes(), customExecutor);
// Use tenant
try (Tenant tenant = db.openTenant("my_tenant".getBytes())) {
tenant.run(tr -> {
tr.set("key".getBytes(), "value".getBytes());
return null;
});
}Create new tenants for isolating data.
/**
* Create tenant within a transaction.
* Idempotent: completes successfully if tenant already exists.
* Transaction must be committed for creation to take effect.
*
* Parameters:
* - tr: Transaction - Transaction in which to create tenant
* - tenantName: byte[] - Name for new tenant
*
* Throws:
* FDBException - If creation fails on commit
*/
static void TenantManagement.createTenant(Transaction tr, byte[] tenantName);
/**
* Create tenant using Tuple name within a transaction.
* Idempotent: completes successfully if tenant already exists.
*
* Parameters:
* - tr: Transaction - Transaction in which to create tenant
* - tenantName: Tuple - Name for new tenant as Tuple
*/
static void TenantManagement.createTenant(Transaction tr, Tuple tenantName);
/**
* Create tenant with automatic retry loop.
* Checks existence first and throws tenant_already_exists error (2132) if tenant exists.
* Uses database's transaction retry mechanism.
*
* Parameters:
* - db: Database - Database in which to create tenant
* - tenantName: byte[] - Name for new tenant
*
* Returns:
* CompletableFuture<Void> - Completes when tenant is created
*
* Throws:
* FDBException - With code 2132 (tenant_already_exists) if tenant already exists
*/
static CompletableFuture<Void> TenantManagement.createTenant(Database db, byte[] tenantName);
/**
* Create tenant with Tuple name using automatic retry loop.
* Checks existence first and throws tenant_already_exists error (2132) if tenant exists.
*
* Parameters:
* - db: Database - Database in which to create tenant
* - tenantName: Tuple - Name for new tenant as Tuple
*
* Returns:
* CompletableFuture<Void> - Completes when tenant is created
*
* Throws:
* FDBException - With code 2132 (tenant_already_exists) if tenant already exists
*/
static CompletableFuture<Void> TenantManagement.createTenant(Database db, Tuple tenantName);Usage examples:
import com.apple.foundationdb.*;
import com.apple.foundationdb.tuple.Tuple;
// Create tenant with retry loop (recommended)
TenantManagement.createTenant(db, "new_tenant".getBytes()).join();
// Create tenant with Tuple name
TenantManagement.createTenant(db, Tuple.from("app", "user", 12345)).join();
// Create tenant within existing transaction
db.run(tr -> {
TenantManagement.createTenant(tr, "tenant1".getBytes());
TenantManagement.createTenant(tr, "tenant2".getBytes());
return null;
});
// Create multiple tenants
List<String> tenantNames = Arrays.asList("tenant_a", "tenant_b", "tenant_c");
List<CompletableFuture<Void>> futures = tenantNames.stream()
.map(name -> TenantManagement.createTenant(db, name.getBytes()))
.collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
System.out.println("All tenants created");Remove tenants and their metadata.
/**
* Delete tenant within a transaction.
* Idempotent: completes successfully if tenant does not exist.
* Tenant must be empty (contain no data).
* Transaction must be committed for deletion to take effect.
*
* Parameters:
* - tr: Transaction - Transaction in which to delete tenant
* - tenantName: byte[] - Name of tenant to delete
*
* Throws:
* FDBException - If tenant contains data or deletion fails on commit
*/
static void TenantManagement.deleteTenant(Transaction tr, byte[] tenantName);
/**
* Delete tenant using Tuple name within a transaction.
* Idempotent: completes successfully if tenant does not exist.
* Tenant must be empty (contain no data).
*
* Parameters:
* - tr: Transaction - Transaction in which to delete tenant
* - tenantName: Tuple - Name of tenant to delete as Tuple
*/
static void TenantManagement.deleteTenant(Transaction tr, Tuple tenantName);
/**
* Delete tenant with automatic retry loop.
* Checks existence first and throws tenant_not_found error (2131) if tenant does not exist.
* Tenant must be empty (contain no data).
*
* Parameters:
* - db: Database - Database containing tenant
* - tenantName: byte[] - Name of tenant to delete
*
* Returns:
* CompletableFuture<Void> - Completes when tenant is deleted
*
* Throws:
* FDBException - With code 2131 (tenant_not_found) if tenant does not exist
* or if tenant contains data
*/
static CompletableFuture<Void> TenantManagement.deleteTenant(Database db, byte[] tenantName);
/**
* Delete tenant with Tuple name using automatic retry loop.
* Checks existence first and throws tenant_not_found error (2131) if tenant does not exist.
* Tenant must be empty (contain no data).
*
* Parameters:
* - db: Database - Database containing tenant
* - tenantName: Tuple - Name of tenant to delete as Tuple
*
* Returns:
* CompletableFuture<Void> - Completes when tenant is deleted
*
* Throws:
* FDBException - With code 2131 (tenant_not_found) if tenant does not exist
* or if tenant contains data
*/
static CompletableFuture<Void> TenantManagement.deleteTenant(Database db, Tuple tenantName);Usage examples:
// Delete tenant (must be empty)
TenantManagement.deleteTenant(db, "old_tenant".getBytes()).join();
// Clear tenant data then delete
Tenant tenant = db.openTenant("tenant_to_delete".getBytes());
tenant.run(tr -> {
// Clear all data in tenant
tr.clear(new byte[0], new byte[]{(byte)0xFF});
return null;
});
// Now delete the empty tenant
TenantManagement.deleteTenant(db, "tenant_to_delete".getBytes()).join();Query available tenants in a database.
/**
* List tenants in specified range.
* Returns iterator over tenant metadata.
*
* Parameters:
* - db: Database - Database to query
* - begin: byte[] - Start of tenant name range (inclusive)
* - end: byte[] - End of tenant name range (exclusive)
* - limit: int - Maximum tenants to return
*
* Returns:
* CloseableAsyncIterator<KeyValue> - Iterator over tenant entries
* Keys are tenant names, values are tenant metadata (JSON)
*/
static CloseableAsyncIterator<KeyValue> TenantManagement.listTenants(
Database db, byte[] begin, byte[] end, int limit
);
/**
* List tenants using Tuple range.
*
* Parameters:
* - db: Database - Database to query
* - begin: Tuple - Start of tenant name range (inclusive)
* - end: Tuple - End of tenant name range (exclusive)
* - limit: int - Maximum tenants to return
*
* Returns:
* CloseableAsyncIterator<KeyValue> - Iterator over tenant entries
*/
static CloseableAsyncIterator<KeyValue> TenantManagement.listTenants(
Database db, Tuple begin, Tuple end, int limit
);Usage examples:
import com.apple.foundationdb.async.CloseableAsyncIterator;
// List all tenants
try (CloseableAsyncIterator<KeyValue> iter =
TenantManagement.listTenants(db, new byte[0], new byte[]{(byte)0xFF}, 1000)) {
while (iter.hasNext()) {
KeyValue kv = iter.next();
String tenantName = new String(kv.getKey());
String metadata = new String(kv.getValue());
System.out.println("Tenant: " + tenantName + ", Metadata: " + metadata);
}
}
// List tenants with prefix
byte[] prefix = "app_".getBytes();
byte[] prefixEnd = "app`".getBytes(); // Next prefix after "app_"
try (CloseableAsyncIterator<KeyValue> iter =
TenantManagement.listTenants(db, prefix, prefixEnd, 100)) {
List<String> tenantNames = new ArrayList<>();
while (iter.hasNext()) {
tenantNames.add(new String(iter.next().getKey()));
}
System.out.println("Found " + tenantNames.size() + " tenants");
}
// List tenants using Tuple range
Tuple begin = Tuple.from("org", "users");
Tuple end = Tuple.from("org", "users", 999999);
try (CloseableAsyncIterator<KeyValue> iter =
TenantManagement.listTenants(db, begin, end, 1000)) {
for (KeyValue kv : iter) {
System.out.println("Tenant: " + new String(kv.getKey()));
}
}Execute transactions and query tenant information.
/**
* Create transaction scoped to tenant key-space.
*
* Returns:
* Transaction - New transaction in tenant context
*/
Transaction Tenant.createTransaction();
/**
* Create transaction with custom executor.
*
* Parameters:
* - e: Executor - Executor for asynchronous operations
*
* Returns:
* Transaction - New transaction in tenant context
*/
Transaction Tenant.createTransaction(Executor e);
/**
* Create transaction with custom executor and event tracking.
*
* Parameters:
* - e: Executor - Executor for asynchronous operations
* - eventKeeper: EventKeeper - Instrumentation for tracking operations
*
* Returns:
* Transaction - New transaction in tenant context
*/
Transaction Tenant.createTransaction(Executor e, EventKeeper eventKeeper);
/**
* Run transactional function with automatic retry.
*
* Type Parameters:
* - T: Return type
*
* Parameters:
* - retryable: Function<? super Transaction, T> - Function to execute
*
* Returns:
* T - Result of function
*/
<T> T Tenant.run(Function<? super Transaction, T> retryable);
/**
* Run transactional function with custom executor.
*
* Type Parameters:
* - T: Return type
*
* Parameters:
* - retryable: Function<? super Transaction, T> - Function to execute
* - e: Executor - Executor for asynchronous operations
*
* Returns:
* T - Result of function
*/
<T> T Tenant.run(Function<? super Transaction, T> retryable, Executor e);
/**
* Run async transactional function with automatic retry.
*
* Type Parameters:
* - T: Return type
*
* Parameters:
* - retryable: Function<? super Transaction, ? extends CompletableFuture<T>>
*
* Returns:
* CompletableFuture<T> - Future completing with result
*/
<T> CompletableFuture<T> Tenant.runAsync(
Function<? super Transaction, ? extends CompletableFuture<T>> retryable
);
/**
* Run async transactional function with custom executor.
*
* Type Parameters:
* - T: Return type
*
* Parameters:
* - retryable: Function<? super Transaction, ? extends CompletableFuture<T>>
* - e: Executor - Executor for asynchronous operations
*
* Returns:
* CompletableFuture<T> - Future completing with result
*/
<T> CompletableFuture<T> Tenant.runAsync(
Function<? super Transaction, ? extends CompletableFuture<T>> retryable, Executor e
);
/**
* Run read-only function with automatic retry.
*
* Type Parameters:
* - T: Return type
*
* Parameters:
* - retryable: Function<? super ReadTransaction, T> - Function to execute
*
* Returns:
* T - Result of function
*/
<T> T Tenant.read(Function<? super ReadTransaction, T> retryable);
/**
* Run read-only function with custom executor.
*
* Type Parameters:
* - T: Return type
*
* Parameters:
* - retryable: Function<? super ReadTransaction, T> - Function to execute
* - e: Executor - Executor for asynchronous operations
*
* Returns:
* T - Result of function
*/
<T> T Tenant.read(Function<? super ReadTransaction, T> retryable, Executor e);
/**
* Run async read-only function with automatic retry.
*
* Type Parameters:
* - T: Return type
*
* Parameters:
* - retryable: Function<? super ReadTransaction, ? extends CompletableFuture<T>>
*
* Returns:
* CompletableFuture<T> - Future completing with result
*/
<T> CompletableFuture<T> Tenant.readAsync(
Function<? super ReadTransaction, ? extends CompletableFuture<T>> retryable
);
/**
* Run async read-only function with custom executor.
*
* Type Parameters:
* - T: Return type
*
* Parameters:
* - retryable: Function<? super ReadTransaction, ? extends CompletableFuture<T>>
* - e: Executor - Executor for asynchronous operations
*
* Returns:
* CompletableFuture<T> - Future completing with result
*/
<T> CompletableFuture<T> Tenant.readAsync(
Function<? super ReadTransaction, ? extends CompletableFuture<T>> retryable, Executor e
);
/**
* Get tenant name as byte array.
*
* Returns:
* byte[] - Tenant name
*/
byte[] Tenant.getName();
/**
* Get unique tenant ID assigned by FDB using default executor.
*
* Returns:
* CompletableFuture<Long> - Tenant ID
*/
CompletableFuture<Long> Tenant.getId();
/**
* Get unique tenant ID assigned by FDB with custom executor.
*
* Parameters:
* - e: Executor - Executor for asynchronous operation
*
* Returns:
* CompletableFuture<Long> - Tenant ID
*/
CompletableFuture<Long> Tenant.getId(Executor e);
/**
* Close tenant and release resources.
*/
void Tenant.close();Usage examples:
// Execute transaction in tenant
Tenant tenant = db.openTenant("user_1001".getBytes());
tenant.run(tr -> {
tr.set("profile".getBytes(), "user data".getBytes());
tr.set("settings".getBytes(), "preferences".getBytes());
return null;
});
// Read from tenant
String profile = tenant.read(tr -> {
byte[] value = tr.get("profile".getBytes()).join();
return value != null ? new String(value) : null;
});
// Get tenant information
byte[] name = tenant.getName();
long id = tenant.getId(tenant.getExecutor()).join();
System.out.println("Tenant " + new String(name) + " has ID: " + id);
// Async tenant operations
tenant.runAsync(tr -> {
return tr.get("key".getBytes())
.thenCompose(value -> {
if (value != null) {
tr.set("key_copy".getBytes(), value);
}
return CompletableFuture.completedFuture(null);
});
}).join();Manage blob storage for tenants (similar to Database operations).
/**
* Mark tenant range for blobbification using default executor.
*
* Parameters:
* - beginKey: byte[] - Start of range (inclusive)
* - endKey: byte[] - End of range (exclusive)
*
* Returns:
* CompletableFuture<Boolean> - True if operation succeeded
*/
CompletableFuture<Boolean> Tenant.blobbifyRange(byte[] beginKey, byte[] endKey);
/**
* Mark tenant range for blobbification.
*
* Parameters:
* - beginKey: byte[] - Start of range (inclusive)
* - endKey: byte[] - End of range (exclusive)
* - e: Executor - Executor for asynchronous operation
*
* Returns:
* CompletableFuture<Boolean> - True if operation succeeded
*/
CompletableFuture<Boolean> Tenant.blobbifyRange(byte[] beginKey, byte[] endKey, Executor e);
/**
* Mark tenant range for blobbification and wait for completion (blocking) using default executor.
*
* Parameters:
* - beginKey: byte[] - Start of range (inclusive)
* - endKey: byte[] - End of range (exclusive)
*
* Returns:
* CompletableFuture<Boolean> - True if operation succeeded
*/
CompletableFuture<Boolean> Tenant.blobbifyRangeBlocking(byte[] beginKey, byte[] endKey);
/**
* Mark tenant range for blobbification and wait for completion (blocking).
*
* Parameters:
* - beginKey: byte[] - Start of range (inclusive)
* - endKey: byte[] - End of range (exclusive)
* - e: Executor - Executor for asynchronous operation
*
* Returns:
* CompletableFuture<Boolean> - True if operation succeeded
*/
CompletableFuture<Boolean> Tenant.blobbifyRangeBlocking(byte[] beginKey, byte[] endKey, Executor e);
/**
* Remove blobbification from tenant range using default executor.
*
* Parameters:
* - beginKey: byte[] - Start of range (inclusive)
* - endKey: byte[] - End of range (exclusive)
*
* Returns:
* CompletableFuture<Boolean> - True if operation succeeded
*/
CompletableFuture<Boolean> Tenant.unblobbifyRange(byte[] beginKey, byte[] endKey);
/**
* Remove blobbification from tenant range.
*
* Parameters:
* - beginKey: byte[] - Start of range (inclusive)
* - endKey: byte[] - End of range (exclusive)
* - e: Executor - Executor for asynchronous operation
*
* Returns:
* CompletableFuture<Boolean> - True if operation succeeded
*/
CompletableFuture<Boolean> Tenant.unblobbifyRange(byte[] beginKey, byte[] endKey, Executor e);
/**
* List blobbified ranges in tenant using default executor.
*
* Parameters:
* - beginKey: byte[] - Start of query range (inclusive)
* - endKey: byte[] - End of query range (exclusive)
* - rangeLimit: int - Maximum number of ranges to return
*
* Returns:
* CompletableFuture<KeyRangeArrayResult> - Array of blobbified ranges
*/
CompletableFuture<KeyRangeArrayResult> Tenant.listBlobbifiedRanges(
byte[] beginKey, byte[] endKey, int rangeLimit
);
/**
* List blobbified ranges in tenant.
*
* Parameters:
* - beginKey: byte[] - Start of query range (inclusive)
* - endKey: byte[] - End of query range (exclusive)
* - rangeLimit: int - Maximum number of ranges to return
* - e: Executor - Executor for asynchronous operation
*
* Returns:
* CompletableFuture<KeyRangeArrayResult> - Array of blobbified ranges
*/
CompletableFuture<KeyRangeArrayResult> Tenant.listBlobbifiedRanges(
byte[] beginKey, byte[] endKey, int rangeLimit, Executor e
);
/**
* Purge blob granules in tenant range using default executor and default purgeVersion.
*
* Parameters:
* - beginKey: byte[] - Start of range (inclusive)
* - endKey: byte[] - End of range (exclusive)
* - force: boolean - Force purge even if data might be needed
*
* Returns:
* CompletableFuture<byte[]> - Purge key for tracking completion
*/
CompletableFuture<byte[]> Tenant.purgeBlobGranules(byte[] beginKey, byte[] endKey, boolean force);
/**
* Purge blob granules in tenant range using default executor.
*
* Parameters:
* - beginKey: byte[] - Start of range (inclusive)
* - endKey: byte[] - End of range (exclusive)
* - purgeVersion: long - Version up to which to purge (-2 for default)
* - force: boolean - Force purge even if data might be needed
*
* Returns:
* CompletableFuture<byte[]> - Purge key for tracking completion
*/
CompletableFuture<byte[]> Tenant.purgeBlobGranules(
byte[] beginKey, byte[] endKey, long purgeVersion, boolean force
);
/**
* Purge blob granules in tenant range.
*
* Parameters:
* - beginKey: byte[] - Start of range (inclusive)
* - endKey: byte[] - End of range (exclusive)
* - purgeVersion: long - Version up to which to purge (-2 for default)
* - force: boolean - Force purge even if data might be needed
* - e: Executor - Executor for asynchronous operation
*
* Returns:
* CompletableFuture<byte[]> - Purge key for tracking completion
*/
CompletableFuture<byte[]> Tenant.purgeBlobGranules(
byte[] beginKey, byte[] endKey, long purgeVersion, boolean force, Executor e
);
/**
* Wait for tenant purge operation to complete using default executor.
*
* Parameters:
* - purgeKey: byte[] - Key returned from purgeBlobGranules
*
* Returns:
* CompletableFuture<Void> - Completes when purge is done
*/
CompletableFuture<Void> Tenant.waitPurgeGranulesComplete(byte[] purgeKey);
/**
* Wait for tenant purge operation to complete.
*
* Parameters:
* - purgeKey: byte[] - Key returned from purgeBlobGranules
* - e: Executor - Executor for asynchronous operation
*
* Returns:
* CompletableFuture<Void> - Completes when purge is done
*/
CompletableFuture<Void> Tenant.waitPurgeGranulesComplete(byte[] purgeKey, Executor e);
/**
* Verify tenant blob range integrity using default executor at latest version.
*
* Parameters:
* - beginKey: byte[] - Start of range (inclusive)
* - endKey: byte[] - End of range (exclusive)
*
* Returns:
* CompletableFuture<Long> - Version verified, or error code
*/
CompletableFuture<Long> Tenant.verifyBlobRange(byte[] beginKey, byte[] endKey);
/**
* Verify tenant blob range integrity at specific version using default executor.
*
* Parameters:
* - beginKey: byte[] - Start of range (inclusive)
* - endKey: byte[] - End of range (exclusive)
* - version: long - Database version to verify at (-2 for latest)
*
* Returns:
* CompletableFuture<Long> - Version verified, or error code
*/
CompletableFuture<Long> Tenant.verifyBlobRange(byte[] beginKey, byte[] endKey, long version);
/**
* Verify tenant blob range integrity.
*
* Parameters:
* - beginKey: byte[] - Start of range (inclusive)
* - endKey: byte[] - End of range (exclusive)
* - version: long - Database version to verify at (-2 for latest)
* - e: Executor - Executor for asynchronous operation
*
* Returns:
* CompletableFuture<Long> - Version verified, or error code
*/
CompletableFuture<Long> Tenant.verifyBlobRange(
byte[] beginKey, byte[] endKey, long version, Executor e
);
/**
* Flush tenant blob range to storage using default executor at latest version.
*
* Parameters:
* - beginKey: byte[] - Start of range (inclusive)
* - endKey: byte[] - End of range (exclusive)
* - compact: boolean - Whether to compact after flushing
*
* Returns:
* CompletableFuture<Boolean> - True if flush succeeded
*/
CompletableFuture<Boolean> Tenant.flushBlobRange(byte[] beginKey, byte[] endKey, boolean compact);
/**
* Flush tenant blob range to storage at specific version using default executor.
*
* Parameters:
* - beginKey: byte[] - Start of range (inclusive)
* - endKey: byte[] - End of range (exclusive)
* - compact: boolean - Whether to compact after flushing
* - version: long - Database version to flush to (-2 for latest)
*
* Returns:
* CompletableFuture<Boolean> - True if flush succeeded
*/
CompletableFuture<Boolean> Tenant.flushBlobRange(
byte[] beginKey, byte[] endKey, boolean compact, long version
);
/**
* Flush tenant blob range to storage.
*
* Parameters:
* - beginKey: byte[] - Start of range (inclusive)
* - endKey: byte[] - End of range (exclusive)
* - compact: boolean - Whether to compact after flushing
* - version: long - Database version to flush to (-2 for latest)
* - e: Executor - Executor for asynchronous operation
*
* Returns:
* CompletableFuture<Boolean> - True if flush succeeded
*/
CompletableFuture<Boolean> Tenant.flushBlobRange(
byte[] beginKey, byte[] endKey, boolean compact, long version, Executor e
);interface Tenant extends AutoCloseable, TransactionContext {
// All methods documented above
}
class TenantManagement {
// All static methods documented above
}