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

atomic-operations.mddocs/

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

```