or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

atomic-operations.mdconfiguration.mdcore-operations.mddirectory-layer.mdindex.mdkey-value-ops.mdsubspaces.mdtransactions.md

subspaces.mddocs/

0

# Subspace Operations

1

2

Key space partitioning with tuple encoding for structured data organization and automatic prefix management. Subspaces provide a clean abstraction for organizing related data and avoiding key conflicts.

3

4

## Capabilities

5

6

### Subspace Creation and Management

7

8

Create and manage subspaces for key organization and namespace isolation.

9

10

```python { .api }

11

class Subspace:

12

def __init__(self, prefix: bytes = b'', tuple: tuple = ()):

13

"""

14

Create a subspace with optional prefix and tuple

15

16

Args:

17

prefix: Raw byte prefix for this subspace

18

tuple: Tuple elements to encode as prefix

19

"""

20

21

def key(self) -> bytes:

22

"""

23

Get the raw prefix bytes for this subspace

24

25

Returns:

26

Raw prefix bytes

27

"""

28

29

def pack(self, tuple: tuple) -> bytes:

30

"""

31

Pack a tuple into a key within this subspace

32

33

Args:

34

tuple: Tuple to encode as key suffix

35

36

Returns:

37

Complete key bytes (prefix + encoded tuple)

38

"""

39

40

def unpack(self, key: bytes) -> tuple:

41

"""

42

Unpack a key from this subspace back to tuple

43

44

Args:

45

key: Key bytes to unpack

46

47

Returns:

48

Tuple representing the key suffix

49

50

Raises:

51

ValueError: If key doesn't belong to this subspace

52

"""

53

54

def contains(self, key: bytes) -> bool:

55

"""

56

Check if a key belongs to this subspace

57

58

Args:

59

key: Key bytes to check

60

61

Returns:

62

True if key starts with this subspace's prefix

63

"""

64

65

def subspace(self, tuple: tuple) -> 'Subspace':

66

"""

67

Create a nested subspace within this subspace

68

69

Args:

70

tuple: Tuple elements to append to current prefix

71

72

Returns:

73

New Subspace with extended prefix

74

"""

75

76

def range(self, tuple: tuple = ()) -> Range:

77

"""

78

Get key range for this subspace or tuple within it

79

80

Args:

81

tuple: Optional tuple to create range for specific subtree

82

83

Returns:

84

Range object representing the key range

85

"""

86

87

def __getitem__(self, tuple: tuple) -> bytes:

88

"""Convenience method for pack()"""

89

90

def __contains__(self, key: bytes) -> bool:

91

"""Convenience method for contains()"""

92

```

93

94

```java { .api }

95

// Java API

96

public class Subspace {

97

public Subspace():

98

/**

99

* Create subspace with empty prefix

100

*/

101

102

public Subspace(byte[] prefix):

103

/**

104

* Create subspace with raw byte prefix

105

* @param prefix Raw prefix bytes

106

*/

107

108

public Subspace(byte[] prefix, Tuple tuple):

109

/**

110

* Create subspace with prefix and tuple

111

* @param prefix Raw prefix bytes

112

* @param tuple Tuple to encode and append to prefix

113

*/

114

115

public byte[] getKey():

116

/**

117

* Get the raw prefix bytes

118

* @return Prefix bytes for this subspace

119

*/

120

121

public byte[] pack(Tuple tuple):

122

/**

123

* Pack tuple into key within this subspace

124

* @param tuple Tuple to encode

125

* @return Complete key bytes

126

*/

127

128

public Tuple unpack(byte[] key):

129

/**

130

* Unpack key back to tuple

131

* @param key Key bytes from this subspace

132

* @return Tuple representing the key suffix

133

*/

134

135

public boolean contains(byte[] key):

136

/**

137

* Check if key belongs to this subspace

138

* @param key Key bytes to check

139

* @return True if key starts with subspace prefix

140

*/

141

142

public Subspace subspace(Tuple tuple):

143

/**

144

* Create nested subspace

145

* @param tuple Tuple elements to append to prefix

146

* @return New Subspace with extended prefix

147

*/

148

149

public Range range():

150

/**

151

* Get range covering entire subspace

152

* @return Range for all keys in this subspace

153

*/

154

155

public Range range(Tuple tuple):

156

/**

157

* Get range for tuple within this subspace

158

* @param tuple Tuple to create range for

159

* @return Range for the specified tuple subtree

160

*/

161

}

162

```

163

164

```go { .api }

165

// Go API

166

type Subspace interface {

167

Bytes() []byte

168

Pack(tuple tuple.Tuple) Key

169

Unpack(key Key) (tuple.Tuple, error)

170

Contains(key Key) bool

171

Sub(tuple tuple.Tuple) Subspace

172

FDBRangeKeys() (Key, Key)

173

FDBRangeKeySelectors() (KeySelector, KeySelector)

174

}

175

176

func NewSubspace(prefix []byte) Subspace: ...

177

func NewSubspaceFromTuple(prefix tuple.Tuple) Subspace: ...

178

```

179

180

### Tuple Encoding

181

182

Encode structured data as sortable binary keys with proper type ordering.

183

184

```python { .api }

185

import fdb.tuple as tuple

186

187

def pack(items: tuple) -> bytes:

188

"""

189

Pack tuple into binary key with proper ordering

190

191

Args:

192

items: Tuple of values to encode

193

194

Returns:

195

Binary key bytes that maintain tuple ordering

196

197

Supported types:

198

- None: encoded as null

199

- bool: encoded as boolean

200

- int: encoded as signed integer (arbitrary precision)

201

- float: encoded as IEEE 754 double

202

- str: encoded as UTF-8 bytes

203

- bytes: encoded as raw bytes

204

- uuid.UUID: encoded as 16-byte UUID

205

- tuple: encoded as nested tuple

206

"""

207

208

def unpack(data: bytes) -> tuple:

209

"""

210

Unpack binary key back to tuple

211

212

Args:

213

data: Binary key data to decode

214

215

Returns:

216

Tuple of decoded values

217

218

Raises:

219

ValueError: If data is not valid tuple encoding

220

"""

221

222

def range(items: tuple) -> tuple:

223

"""

224

Get key range for tuple prefix

225

226

Args:

227

items: Tuple prefix to create range for

228

229

Returns:

230

(start_key, end_key) tuple for range queries

231

"""

232

233

# Utility functions

234

def has_incomplete_versionstamp(t: tuple) -> bool:

235

"""Check if tuple contains incomplete versionstamp"""

236

237

def pack_with_versionstamp(t: tuple) -> bytes:

238

"""Pack tuple containing incomplete versionstamp"""

239

```

240

241

```java { .api }

242

// Java API

243

public class Tuple {

244

public static Tuple from(Object... items):

245

/**

246

* Create tuple from items

247

* @param items Items to include in tuple

248

* @return New Tuple instance

249

*/

250

251

public static Tuple fromBytes(byte[] bytes):

252

/**

253

* Unpack tuple from binary data

254

* @param bytes Binary tuple data

255

* @return Decoded Tuple

256

*/

257

258

public byte[] pack():

259

/**

260

* Pack tuple to binary key

261

* @return Binary key bytes

262

*/

263

264

public Tuple add(Object item):

265

/**

266

* Create new tuple with additional item

267

* @param item Item to add

268

* @return New Tuple with added item

269

*/

270

271

public Object get(int index):

272

/**

273

* Get item at index

274

* @param index Item index

275

* @return Item at specified index

276

*/

277

278

public int size():

279

/**

280

* Get tuple size

281

* @return Number of items in tuple

282

*/

283

284

public Range range():

285

/**

286

* Get range for this tuple prefix

287

* @return Range covering all keys with this tuple prefix

288

*/

289

}

290

```

291

292

```go { .api }

293

// Go API

294

package tuple

295

296

type Tuple []TupleElement

297

298

func (t Tuple) Pack() []byte: ...

299

func Unpack(b []byte) (Tuple, error): ...

300

301

type TupleElement interface {

302

// Various types implement this interface

303

}

304

305

// Supported tuple element types

306

func Int(i int64) TupleElement: ...

307

func String(s string) TupleElement: ...

308

func Bytes(b []byte) TupleElement: ...

309

func Bool(b bool) TupleElement: ...

310

func Float(f float64) TupleElement: ...

311

func UUID(u [16]byte) TupleElement: ...

312

func Nil() TupleElement: ...

313

```

314

315

### Range Operations with Subspaces

316

317

Perform range queries within subspace boundaries.

318

319

```python { .api }

320

class Subspace:

321

def get_range(self, tr: Transaction, begin: tuple = (), end: tuple = (), **kwargs) -> FDBRange:

322

"""

323

Get range of key-value pairs within this subspace

324

325

Args:

326

tr: Transaction to use

327

begin: Starting tuple (empty for beginning of subspace)

328

end: Ending tuple (empty for end of subspace)

329

**kwargs: Additional arguments passed to Transaction.get_range

330

331

Returns:

332

FDBRange iterable of KeyValue pairs

333

"""

334

335

def clear_range(self, tr: Transaction, begin: tuple = (), end: tuple = ()) -> None:

336

"""

337

Clear range of keys within this subspace

338

339

Args:

340

tr: Transaction to use

341

begin: Starting tuple (empty for beginning of subspace)

342

end: Ending tuple (empty for end of subspace)

343

"""

344

345

def get_key(self, tr: Transaction, selector: KeySelector, **kwargs) -> Future:

346

"""

347

Get key within this subspace using key selector

348

349

Args:

350

tr: Transaction to use

351

selector: Key selector relative to this subspace

352

**kwargs: Additional arguments

353

354

Returns:

355

Future containing the selected key

356

"""

357

358

class Range:

359

def __init__(self, begin: bytes, end: bytes):

360

"""

361

Create a key range

362

363

Args:

364

begin: Start key (inclusive)

365

end: End key (exclusive)

366

"""

367

368

@staticmethod

369

def starts_with(prefix: bytes) -> 'Range':

370

"""

371

Create range for all keys starting with prefix

372

373

Args:

374

prefix: Key prefix

375

376

Returns:

377

Range covering all keys with the prefix

378

"""

379

```

380

381

### Data Organization Patterns

382

383

Common patterns for organizing structured data using subspaces.

384

385

```python { .api }

386

# User data organization example

387

class UserData:

388

def __init__(self, subspace: Subspace):

389

self.users = subspace.subspace(('users',))

390

self.profiles = subspace.subspace(('profiles',))

391

self.sessions = subspace.subspace(('sessions',))

392

self.preferences = subspace.subspace(('preferences',))

393

394

def user_key(self, user_id: str) -> bytes:

395

"""Get key for user record"""

396

return self.users.pack((user_id,))

397

398

def profile_key(self, user_id: str) -> bytes:

399

"""Get key for user profile"""

400

return self.profiles.pack((user_id,))

401

402

def session_key(self, user_id: str, session_id: str) -> bytes:

403

"""Get key for user session"""

404

return self.sessions.pack((user_id, session_id))

405

406

def preference_key(self, user_id: str, pref_name: str) -> bytes:

407

"""Get key for user preference"""

408

return self.preferences.pack((user_id, pref_name))

409

410

# Time-series data organization

411

class TimeSeriesData:

412

def __init__(self, subspace: Subspace):

413

self.metrics = subspace.subspace(('metrics',))

414

415

def metric_key(self, metric_name: str, timestamp: int, tags: dict) -> bytes:

416

"""Get key for time-series metric"""

417

# Sort tags for consistent key ordering

418

sorted_tags = tuple(sorted(tags.items()))

419

return self.metrics.pack((metric_name, timestamp, sorted_tags))

420

421

def metric_range(self, metric_name: str, start_time: int = None, end_time: int = None) -> Range:

422

"""Get range for metric queries"""

423

if start_time is None and end_time is None:

424

return self.metrics.range((metric_name,))

425

elif end_time is None:

426

begin = self.metrics.pack((metric_name, start_time))

427

end = self.metrics.pack((metric_name + '\x00',))

428

else:

429

begin = self.metrics.pack((metric_name, start_time or 0))

430

end = self.metrics.pack((metric_name, end_time))

431

return Range(begin, end)

432

```

433

434

**Usage Examples:**

435

436

**Basic Subspace Operations (Python):**

437

```python

438

import fdb

439

import fdb.tuple as tuple

440

441

fdb.api_version(630)

442

db = fdb.open()

443

444

# Create application subspaces

445

app_subspace = fdb.Subspace((b'myapp',))

446

users_subspace = app_subspace.subspace(('users',))

447

orders_subspace = app_subspace.subspace(('orders',))

448

449

@fdb.transactional

450

def subspace_example(tr):

451

# Store user data

452

user_key = users_subspace.pack(('alice',))

453

tr.set(user_key, b'{"name": "Alice", "email": "alice@example.com"}')

454

455

# Store nested user data

456

profile_subspace = users_subspace.subspace(('alice', 'profile'))

457

tr.set(profile_subspace.pack(('settings',)), b'{"theme": "dark"}')

458

tr.set(profile_subspace.pack(('avatar',)), b'avatar_data_here')

459

460

# Store order data

461

order_key = orders_subspace.pack(('alice', 12345))

462

tr.set(order_key, b'{"total": 99.99, "items": ["laptop", "mouse"]}')

463

464

# Query all user data

465

user_range = users_subspace.range(('alice',))

466

for kv in tr.get_range(user_range.begin, user_range.end):

467

# Unpack the key to understand structure

468

unpacked = users_subspace.unpack(kv.key)

469

print(f"User data: {unpacked} = {kv.value}")

470

471

# Query specific subspace

472

for kv in profile_subspace.get_range(tr):

473

setting_name = profile_subspace.unpack(kv.key)[0]

474

print(f"Profile {setting_name}: {kv.value}")

475

476

subspace_example(db)

477

```

478

479

**Structured Data Storage (Java):**

480

```java

481

import com.apple.foundationdb.*;

482

import com.apple.foundationdb.subspace.Subspace;

483

import com.apple.foundationdb.tuple.Tuple;

484

485

FDB fdb = FDB.selectAPIVersion(630);

486

487

try (Database db = fdb.open()) {

488

// Create hierarchical subspaces

489

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

490

Subspace products = ecommerce.subspace(Tuple.from("products"));

491

Subspace inventory = ecommerce.subspace(Tuple.from("inventory"));

492

Subspace orders = ecommerce.subspace(Tuple.from("orders"));

493

494

db.run(tr -> {

495

// Store product information

496

byte[] productKey = products.pack(Tuple.from("electronics", "laptop", "model123"));

497

String productData = "{'name': 'Gaming Laptop', 'price': 1299.99}";

498

tr.set(productKey, productData.getBytes());

499

500

// Store inventory

501

byte[] inventoryKey = inventory.pack(Tuple.from("model123"));

502

tr.set(inventoryKey, Tuple.from(50).pack()); // 50 units in stock

503

504

// Store customer order

505

byte[] orderKey = orders.pack(Tuple.from("customer456", "order789"));

506

String orderData = "{'items': [{'product': 'model123', 'quantity': 1}], 'total': 1299.99}";

507

tr.set(orderKey, orderData.getBytes());

508

509

// Query all products in electronics category

510

Range electronicsRange = products.range(Tuple.from("electronics"));

511

512

for (KeyValue kv : tr.getRange(electronicsRange)) {

513

Tuple productTuple = products.unpack(kv.getKey());

514

System.out.println("Product: " + productTuple + " -> " + new String(kv.getValue()));

515

}

516

517

return null;

518

});

519

}

520

```

521

522

**Time-Series Data (Go):**

523

```go

524

package main

525

526

import (

527

"encoding/json"

528

"fmt"

529

"time"

530

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

531

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

532

"github.com/apple/foundationdb/bindings/go/src/fdb/tuple"

533

)

534

535

type MetricPoint struct {

536

Value float64 `json:"value"`

537

Tags map[string]string `json:"tags"`

538

Timestamp int64 `json:"timestamp"`

539

}

540

541

func main() {

542

fdb.MustAPIVersion(630)

543

db := fdb.MustOpenDefault()

544

545

// Create time-series subspaces

546

metricsSubspace := subspace.Sub(tuple.Tuple{"metrics"})

547

548

db.Transact(func(tr fdb.Transaction) (interface{}, error) {

549

// Store CPU usage metrics

550

now := time.Now().Unix()

551

552

cpuMetrics := []MetricPoint{

553

{Value: 45.2, Tags: map[string]string{"host": "web1"}, Timestamp: now},

554

{Value: 62.1, Tags: map[string]string{"host": "web2"}, Timestamp: now},

555

{Value: 38.7, Tags: map[string]string{"host": "db1"}, Timestamp: now},

556

}

557

558

for _, metric := range cpuMetrics {

559

// Create hierarchical key: metrics/cpu_usage/timestamp/host

560

key := metricsSubspace.Pack(tuple.Tuple{

561

"cpu_usage",

562

metric.Timestamp,

563

metric.Tags["host"],

564

})

565

566

data, _ := json.Marshal(metric)

567

tr.Set(key, data)

568

}

569

570

// Query CPU metrics for specific time range

571

start := now - 3600 // Last hour

572

end := now + 1

573

574

beginKey := metricsSubspace.Pack(tuple.Tuple{"cpu_usage", start})

575

endKey := metricsSubspace.Pack(tuple.Tuple{"cpu_usage", end})

576

577

ri := tr.GetRange(fdb.KeyRange{Begin: beginKey, End: endKey}, fdb.RangeOptions{})

578

for i := ri.Iterator(); i.Advance(); {

579

kv := i.MustGet()

580

581

// Unpack key to extract components

582

keyTuple, _ := metricsSubspace.Unpack(kv.Key)

583

metricName := keyTuple[0].(string)

584

timestamp := keyTuple[1].(int64)

585

host := keyTuple[2].(string)

586

587

var metric MetricPoint

588

json.Unmarshal(kv.Value, &metric)

589

590

fmt.Printf("Metric: %s, Host: %s, Time: %d, Value: %.2f\n",

591

metricName, host, timestamp, metric.Value)

592

}

593

594

return nil, nil

595

})

596

}

597

```

598

599

**Multi-Tenant Application (Python):**

600

```python

601

import fdb

602

import json

603

604

fdb.api_version(630)

605

db = fdb.open()

606

607

class MultiTenantApp:

608

def __init__(self, base_subspace):

609

self.base = base_subspace

610

611

def tenant_subspace(self, tenant_id):

612

"""Get subspace for specific tenant"""

613

return self.base.subspace(('tenant', tenant_id))

614

615

def user_subspace(self, tenant_id):

616

"""Get user subspace for tenant"""

617

return self.tenant_subspace(tenant_id).subspace(('users',))

618

619

def data_subspace(self, tenant_id):

620

"""Get data subspace for tenant"""

621

return self.tenant_subspace(tenant_id).subspace(('data',))

622

623

# Create multi-tenant application

624

app_subspace = fdb.Subspace((b'saas_app',))

625

app = MultiTenantApp(app_subspace)

626

627

@fdb.transactional

628

def multi_tenant_example(tr):

629

# Tenant 1 data

630

tenant1_users = app.user_subspace('tenant1')

631

tenant1_data = app.data_subspace('tenant1')

632

633

# Store tenant 1 users

634

tr.set(tenant1_users.pack(('user123',)),

635

json.dumps({"name": "Alice", "role": "admin"}).encode())

636

tr.set(tenant1_users.pack(('user456',)),

637

json.dumps({"name": "Bob", "role": "user"}).encode())

638

639

# Store tenant 1 application data

640

tr.set(tenant1_data.pack(('documents', 'doc1')),

641

b"Document content for tenant 1")

642

tr.set(tenant1_data.pack(('settings', 'theme')),

643

b"dark")

644

645

# Tenant 2 data (completely isolated)

646

tenant2_users = app.user_subspace('tenant2')

647

tenant2_data = app.data_subspace('tenant2')

648

649

tr.set(tenant2_users.pack(('user123',)), # Same user ID, different tenant

650

json.dumps({"name": "Charlie", "role": "admin"}).encode())

651

tr.set(tenant2_data.pack(('documents', 'doc1')), # Same doc ID, different tenant

652

b"Document content for tenant 2")

653

654

# Query tenant 1 data only

655

print("Tenant 1 users:")

656

for kv in tenant1_users.get_range(tr):

657

user_id = tenant1_users.unpack(kv.key)[0]

658

user_data = json.loads(kv.value.decode())

659

print(f" {user_id}: {user_data}")

660

661

# Verify isolation - tenant 2 can't see tenant 1 data

662

print("\nTenant 2 users:")

663

for kv in tenant2_users.get_range(tr):

664

user_id = tenant2_users.unpack(kv.key)[0]

665

user_data = json.loads(kv.value.decode())

666

print(f" {user_id}: {user_data}")

667

668

multi_tenant_example(db)

669

```

670

671

**Complex Tuple Encoding (Python):**

672

```python

673

import fdb

674

import fdb.tuple as tuple

675

import uuid

676

from datetime import datetime

677

678

fdb.api_version(630)

679

db = fdb.open()

680

681

# Create complex subspace structure

682

events_subspace = fdb.Subspace((b'events',))

683

684

@fdb.transactional

685

def complex_tuples_example(tr):

686

# Store events with complex tuple keys

687

event_id = uuid.uuid4()

688

timestamp = int(datetime.now().timestamp())

689

690

# Tuple with mixed types: (event_type, priority, timestamp, uuid, metadata)

691

event_key = events_subspace.pack((

692

'user_action', # string

693

1, # integer (priority)

694

timestamp, # integer (timestamp)

695

event_id, # UUID

696

('login', 'web'), # nested tuple (action, source)

697

42.5, # float (duration)

698

True, # boolean (success)

699

None # null (error)

700

))

701

702

event_data = {

703

'user_id': 'user123',

704

'ip_address': '192.168.1.1',

705

'user_agent': 'Mozilla/5.0...'

706

}

707

708

tr.set(event_key, json.dumps(event_data).encode())

709

710

# Create range query for user_action events in time range

711

start_time = timestamp - 3600 # Last hour

712

713

# Range from (user_action, any_priority, start_time) to (user_action, any_priority, end_time)

714

range_start = events_subspace.pack(('user_action', 0, start_time))

715

range_end = events_subspace.pack(('user_action', 999, timestamp + 1))

716

717

print("Events in time range:")

718

for kv in tr.get_range(range_start, range_end):

719

# Unpack complex tuple

720

unpacked = events_subspace.unpack(kv.key)

721

event_type, priority, ts, event_uuid, metadata, duration, success, error = unpacked

722

723

print(f"Event: {event_type}, Priority: {priority}, Time: {ts}")

724

print(f" UUID: {event_uuid}")

725

print(f" Metadata: {metadata}")

726

print(f" Duration: {duration}s, Success: {success}, Error: {error}")

727

print(f" Data: {json.loads(kv.value.decode())}")

728

729

complex_tuples_example(db)

730

```