0
# Atomic Operations
1
2
Atomic read-modify-write operations for counters, bit manipulation, and other compound operations that execute atomically at the storage server level without conflicts.
3
4
## Capabilities
5
6
### Arithmetic Operations
7
8
Atomic arithmetic operations on 64-bit little-endian integers.
9
10
```c { .api }
11
/**
12
* Perform atomic operation on a key
13
* @param tr Transaction handle
14
* @param key_name Key to modify
15
* @param key_name_length Length of key
16
* @param param Parameter bytes for operation
17
* @param param_length Length of parameter
18
* @param operation_type Type of atomic operation
19
*/
20
void fdb_transaction_atomic_op(FDBTransaction* tr, uint8_t const* key_name,
21
int key_name_length, uint8_t const* param,
22
int param_length, FDBMutationType operation_type);
23
24
// Atomic operation types for arithmetic
25
typedef enum {
26
FDB_MUTATION_TYPE_ADD = 2, // Add signed integer
27
FDB_MUTATION_TYPE_AND = 6, // Bitwise AND
28
FDB_MUTATION_TYPE_OR = 7, // Bitwise OR
29
FDB_MUTATION_TYPE_XOR = 8, // Bitwise XOR
30
FDB_MUTATION_TYPE_APPEND_IF_FITS = 9, // Append if result fits
31
FDB_MUTATION_TYPE_MAX = 12, // Maximum value
32
FDB_MUTATION_TYPE_MIN = 13, // Minimum value
33
FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_KEY = 14, // Set with versionstamp in key
34
FDB_MUTATION_TYPE_SET_VERSIONSTAMPED_VALUE = 15, // Set with versionstamp in value
35
FDB_MUTATION_TYPE_BYTE_MIN = 16, // Byte-wise minimum
36
FDB_MUTATION_TYPE_BYTE_MAX = 17, // Byte-wise maximum
37
FDB_MUTATION_TYPE_COMPARE_AND_CLEAR = 18 // Compare and clear
38
} FDBMutationType;
39
```
40
41
```python { .api }
42
class Transaction:
43
def atomic_op(self, key: bytes, param: bytes, mutation_type: int) -> None:
44
"""
45
Perform atomic operation on a key
46
47
Args:
48
key: Key to modify
49
param: Parameter bytes for the operation
50
mutation_type: Type of atomic operation (from MutationType)
51
"""
52
53
def add(self, key: bytes, value: bytes) -> None:
54
"""
55
Atomically add a signed 64-bit integer to key's value
56
57
Args:
58
key: Key to modify
59
value: 8-byte little-endian signed integer to add
60
"""
61
62
def bit_and(self, key: bytes, value: bytes) -> None:
63
"""
64
Atomically perform bitwise AND on key's value
65
66
Args:
67
key: Key to modify
68
value: Bytes to AND with existing value
69
"""
70
71
def bit_or(self, key: bytes, value: bytes) -> None:
72
"""
73
Atomically perform bitwise OR on key's value
74
75
Args:
76
key: Key to modify
77
value: Bytes to OR with existing value
78
"""
79
80
def bit_xor(self, key: bytes, value: bytes) -> None:
81
"""
82
Atomically perform bitwise XOR on key's value
83
84
Args:
85
key: Key to modify
86
value: Bytes to XOR with existing value
87
"""
88
89
def max(self, key: bytes, value: bytes) -> None:
90
"""
91
Atomically store maximum of existing and new values
92
93
Args:
94
key: Key to modify
95
value: Value to compare (as signed 64-bit integer)
96
"""
97
98
def min(self, key: bytes, value: bytes) -> None:
99
"""
100
Atomically store minimum of existing and new values
101
102
Args:
103
key: Key to modify
104
value: Value to compare (as signed 64-bit integer)
105
"""
106
107
class MutationType:
108
ADD = 2
109
AND = 6
110
OR = 7
111
XOR = 8
112
APPEND_IF_FITS = 9
113
MAX = 12
114
MIN = 13
115
SET_VERSIONSTAMPED_KEY = 14
116
SET_VERSIONSTAMPED_VALUE = 15
117
BYTE_MIN = 16
118
BYTE_MAX = 17
119
COMPARE_AND_CLEAR = 18
120
```
121
122
```java { .api }
123
// Java API
124
public interface Transaction {
125
public void mutate(MutationType optype, byte[] key, byte[] param):
126
/**
127
* Perform atomic operation on a key
128
* @param optype Type of mutation operation
129
* @param key Key to modify
130
* @param param Parameter for the operation
131
*/
132
}
133
134
public enum MutationType {
135
ADD(2), // Add signed integer
136
BIT_AND(6), // Bitwise AND
137
BIT_OR(7), // Bitwise OR
138
BIT_XOR(8), // Bitwise XOR
139
APPEND_IF_FITS(9), // Append if result fits
140
MAX(12), // Maximum value
141
MIN(13), // Minimum value
142
SET_VERSIONSTAMPED_KEY(14), // Set with versionstamp in key
143
SET_VERSIONSTAMPED_VALUE(15), // Set with versionstamp in value
144
BYTE_MIN(16), // Byte-wise minimum
145
BYTE_MAX(17), // Byte-wise maximum
146
COMPARE_AND_CLEAR(18); // Compare and clear
147
}
148
```
149
150
```go { .api }
151
// Go API
152
type Transaction interface {
153
AtomicOp(key Key, param []byte, op MutationType):
154
/**
155
* Perform atomic operation on a key
156
* @param key Key to modify
157
* @param param Parameter bytes for operation
158
* @param op Type of atomic operation
159
*/
160
}
161
162
type MutationType int
163
164
const (
165
MutationTypeAdd MutationType = 2 // Add signed integer
166
MutationTypeAnd MutationType = 6 // Bitwise AND
167
MutationTypeOr MutationType = 7 // Bitwise OR
168
MutationTypeXor MutationType = 8 // Bitwise XOR
169
MutationTypeAppendIfFits MutationType = 9 // Append if result fits
170
MutationTypeMax MutationType = 12 // Maximum value
171
MutationTypeMin MutationType = 13 // Minimum value
172
MutationTypeSetVersionstampedKey MutationType = 14 // Set with versionstamp in key
173
MutationTypeSetVersionstampedValue MutationType = 15 // Set with versionstamp in value
174
MutationTypeByteMin MutationType = 16 // Byte-wise minimum
175
MutationTypeByteMax MutationType = 17 // Byte-wise maximum
176
MutationTypeCompareAndClear MutationType = 18 // Compare and clear
177
)
178
```
179
180
### String Operations
181
182
Atomic operations on string/byte values.
183
184
```python { .api }
185
class Transaction:
186
def append_if_fits(self, key: bytes, value: bytes) -> None:
187
"""
188
Atomically append bytes to existing value if result fits in value size limit
189
190
Args:
191
key: Key to modify
192
value: Bytes to append
193
"""
194
195
def byte_min(self, key: bytes, value: bytes) -> None:
196
"""
197
Atomically store byte-wise minimum of existing and new values
198
199
Args:
200
key: Key to modify
201
value: Value to compare byte-by-byte
202
"""
203
204
def byte_max(self, key: bytes, value: bytes) -> None:
205
"""
206
Atomically store byte-wise maximum of existing and new values
207
208
Args:
209
key: Key to modify
210
value: Value to compare byte-by-byte
211
"""
212
213
def compare_and_clear(self, key: bytes, value: bytes) -> None:
214
"""
215
Atomically clear key if current value equals comparison value
216
217
Args:
218
key: Key to potentially clear
219
value: Value to compare against
220
"""
221
```
222
223
### Versionstamp Operations
224
225
Operations that incorporate database commit versions for unique timestamps.
226
227
```python { .api }
228
class Transaction:
229
def set_versionstamped_key(self, key: bytes, value: bytes) -> None:
230
"""
231
Set key-value pair where key contains incomplete versionstamp
232
233
Args:
234
key: Key with 14-byte incomplete versionstamp (10 bytes version + 4 bytes user)
235
value: Value to store
236
237
Note:
238
Key must contain exactly one 14-byte sequence where first 10 bytes are 0xFF
239
"""
240
241
def set_versionstamped_value(self, key: bytes, value: bytes) -> None:
242
"""
243
Set key-value pair where value contains incomplete versionstamp
244
245
Args:
246
key: Key to set
247
value: Value with 14-byte incomplete versionstamp (10 bytes version + 4 bytes user)
248
249
Note:
250
Value must contain exactly one 14-byte sequence where first 10 bytes are 0xFF
251
"""
252
```
253
254
```java { .api }
255
// Java API
256
public interface Transaction {
257
public void mutate(MutationType.SET_VERSIONSTAMPED_KEY, byte[] key, byte[] param):
258
/**
259
* Set key-value pair with versionstamp in key
260
* @param key Key containing incomplete versionstamp
261
* @param param Value to store
262
*/
263
264
public void mutate(MutationType.SET_VERSIONSTAMPED_VALUE, byte[] key, byte[] param):
265
/**
266
* Set key-value pair with versionstamp in value
267
* @param key Key to set
268
* @param param Value containing incomplete versionstamp
269
*/
270
}
271
```
272
273
```go { .api }
274
// Go API - versionstamp operations use AtomicOp with appropriate mutation types
275
```
276
277
### Counter Patterns
278
279
Common patterns for implementing distributed counters.
280
281
```python { .api }
282
# Counter implementation example - not part of core API but common pattern
283
def increment_counter(tr: Transaction, key: bytes, amount: int = 1) -> None:
284
"""
285
Increment a counter atomically
286
287
Args:
288
tr: Transaction to use
289
key: Counter key
290
amount: Amount to increment (default 1)
291
"""
292
# Convert amount to 8-byte little-endian
293
param = amount.to_bytes(8, byteorder='little', signed=True)
294
tr.add(key, param)
295
296
def decrement_counter(tr: Transaction, key: bytes, amount: int = 1) -> None:
297
"""
298
Decrement a counter atomically
299
300
Args:
301
tr: Transaction to use
302
key: Counter key
303
amount: Amount to decrement (default 1)
304
"""
305
# Convert negative amount to 8-byte little-endian
306
param = (-amount).to_bytes(8, byteorder='little', signed=True)
307
tr.add(key, param)
308
309
def get_counter_value(tr: Transaction, key: bytes) -> int:
310
"""
311
Read current counter value
312
313
Args:
314
tr: Transaction to use
315
key: Counter key
316
317
Returns:
318
Current counter value (0 if key doesn't exist)
319
"""
320
value = tr.get(key).wait()
321
if value is None:
322
return 0
323
return int.from_bytes(value, byteorder='little', signed=True)
324
```
325
326
**Usage Examples:**
327
328
**Atomic Counter (Python):**
329
```python
330
import fdb
331
import struct
332
333
fdb.api_version(630)
334
db = fdb.open()
335
336
@fdb.transactional
337
def increment_counter(tr, counter_key, amount=1):
338
# Atomic increment - no conflicts!
339
param = struct.pack('<q', amount) # 8-byte little-endian signed int
340
tr.add(counter_key, param)
341
342
@fdb.transactional
343
def get_counter(tr, counter_key):
344
value = tr.get(counter_key).wait()
345
if value is None:
346
return 0
347
return struct.unpack('<q', value)[0]
348
349
# Usage
350
counter_key = b"page_views"
351
352
# Increment counter (can be done concurrently without conflicts)
353
increment_counter(db, counter_key, 1)
354
increment_counter(db, counter_key, 5)
355
356
# Read current value
357
current = get_counter(db, counter_key)
358
print(f"Current count: {current}") # Current count: 6
359
```
360
361
**Bitwise Operations (Java):**
362
```java
363
import com.apple.foundationdb.*;
364
365
FDB fdb = FDB.selectAPIVersion(630);
366
367
try (Database db = fdb.open()) {
368
db.run(tr -> {
369
byte[] key = "flags".getBytes();
370
371
// Set some bits using OR
372
byte[] setBits = new byte[]{0x0F}; // Set lower 4 bits
373
tr.mutate(MutationType.BIT_OR, key, setBits);
374
375
// Clear some bits using AND
376
byte[] clearMask = new byte[]{(byte)0xF0}; // Clear lower 4 bits
377
tr.mutate(MutationType.BIT_AND, key, clearMask);
378
379
// Toggle some bits using XOR
380
byte[] toggleBits = new byte[]{0x55}; // Toggle alternating bits
381
tr.mutate(MutationType.BIT_XOR, key, toggleBits);
382
383
return null;
384
});
385
}
386
```
387
388
**Min/Max Operations (Go):**
389
```go
390
package main
391
392
import (
393
"encoding/binary"
394
"fmt"
395
"github.com/apple/foundationdb/bindings/go/src/fdb"
396
)
397
398
func main() {
399
fdb.MustAPIVersion(630)
400
db := fdb.MustOpenDefault()
401
402
db.Transact(func(tr fdb.Transaction) (interface{}, error) {
403
key := fdb.Key("high_score")
404
405
// Try to set new high score (will only update if higher)
406
newScore := int64(95000)
407
scoreBytes := make([]byte, 8)
408
binary.LittleEndian.PutUint64(scoreBytes, uint64(newScore))
409
410
tr.AtomicOp(key, scoreBytes, fdb.MutationTypeMax)
411
412
// Later, read the high score
413
scoreValue := tr.Get(key).MustGet()
414
if scoreValue != nil {
415
score := int64(binary.LittleEndian.Uint64(scoreValue))
416
fmt.Printf("High score: %d\n", score)
417
}
418
419
return nil, nil
420
})
421
}
422
```
423
424
**Versionstamp Operations (Python):**
425
```python
426
import fdb
427
428
fdb.api_version(630)
429
db = fdb.open()
430
431
@fdb.transactional
432
def create_unique_id(tr):
433
# Create a key with versionstamp for unique IDs
434
prefix = b"event:"
435
incomplete_vs = b"\xFF" * 10 + b"\x00\x00\x00\x00" # 10 bytes version + 4 bytes user
436
user_data = b":data"
437
438
# Key will be: b"event:" + versionstamp + b":data"
439
versionstamped_key = prefix + incomplete_vs + user_data
440
value = b"event_data"
441
442
tr.set_versionstamped_key(versionstamped_key, value)
443
444
# Also get the versionstamp for this transaction
445
return tr.get_versionstamp()
446
447
# Create unique event ID
448
vs_future = create_unique_id(db)
449
versionstamp = vs_future.wait()
450
print(f"Versionstamp: {versionstamp.hex()}")
451
452
# The key will be created with actual versionstamp replacing the 0xFF bytes
453
```
454
455
**Append Operations (Python):**
456
```python
457
import fdb
458
459
fdb.api_version(630)
460
db = fdb.open()
461
462
@fdb.transactional
463
def append_log_entry(tr, log_key, entry):
464
# Atomically append to log (if it fits in 100KB value limit)
465
separator = b"\n"
466
tr.append_if_fits(log_key, separator + entry)
467
468
@fdb.transactional
469
def get_log(tr, log_key):
470
return tr.get(log_key).wait() or b""
471
472
# Usage
473
log_key = b"system_log"
474
475
# Append entries (these can be done concurrently)
476
append_log_entry(db, log_key, b"User login: alice")
477
append_log_entry(db, log_key, b"User logout: alice")
478
append_log_entry(db, log_key, b"User login: bob")
479
480
# Read full log
481
full_log = get_log(db, log_key)
482
print(full_log.decode())
483
```
484
485
**Compare and Clear (Python):**
486
```python
487
import fdb
488
489
fdb.api_version(630)
490
db = fdb.open()
491
492
@fdb.transactional
493
def conditional_clear(tr, key, expected_value):
494
# Clear key only if current value matches expected
495
tr.compare_and_clear(key, expected_value)
496
497
# Usage
498
key = b"temp_flag"
499
500
# Set a temporary flag
501
@fdb.transactional
502
def set_flag(tr):
503
tr.set(key, b"processing")
504
505
set_flag(db)
506
507
# Later, clear only if still set to "processing"
508
conditional_clear(db, key, b"processing")
509
```