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
```