Skip to content

Basics of Transactions

In this section, we will go through the fundamentals of transactions in NimbusDB. We’ll use this schema and the items model and catalog as an example:

var schema = {
id: {
type: NIMBUSDB_DATA_TYPE.INTEGER,
const: NIMBUSDB_CONSTRAINT.PRIMARY_KEY
},
name: NIMBUSDB_DATA_TYPE.STRING,
price: {
type: NIMBUSDB_DATA_TYPE.NUMBER,
validator: function(data, value) {
return value >= 0;
},
default_value: 0
},
is_locked: {
type: NIMBUSDB_DATA_TYPE.BOOLEAN,
const: NIMBUSDB_CONSTRAINT.OPTIONAL,
default_value: false
}
};
// `items` model
items = new NimbusDBModel("global", "items", schema, [
{ id: 1, name: "Apple", price: 5 },
{ id: 2, name: "Banana", price: 7.2 },
{ id: 3, name: "Cherry", price: 15 },
{ id: 4, name: "Date", price: 12.5 },
{ id: 5, name: "Elderberry", price: 8 },
{ id: 6, name: "Fig", price: 10 },
{ id: 7, name: "Grape", price: 6 },
{ id: 8, name: "Honeydew", price: 9 },
{ id: 9, name: "Kiwi", price: 4 },
{ id: 10, name: "Lemon", price: 3 }
]);
// `items` catalog
ctg_items = new NimbusDBCatalog("items", {
model: items
});

ACID (Atomicity, Consistency, Isolation, Durability) properties are mandatory for transactions in traditional relational databases to ensure data integrity and reliability. But, transactions in NimbusDB are not the same as ACID in relational databases due its design philosophy (in-memory and client-side first).

Atomicity means that all operations in a transaction are treated as a single unit of work. If any operation fails, the entire transaction will be rolled back, and the original model will remain unchanged.

NimbusDB transactions support atomicity by default, so if any operation fails during the transaction, it will automatically rollback to the original state.

Consistency means that a transaction must bring the database from one valid state to another valid state, maintaining the integrity of the data.

In NimbusDB, the schema supports constraint and validator for each column/field. Data entering the model must satisfy defined rules, or it gets rejected. This ensures that the data remains consistent and valid according to the defined schema.

Isolation Not Supported

Section titled “Isolation ”

Isolation means that the operations in a transaction are isolated from other transactions, preventing concurrent transactions from interfering with each other.

But, since NimbusDB is currently designed to be single-threaded and client-side first, it does not support true isolation between transactions.

Durability Partially Supported

Section titled “Durability ”

Durability means that once a transaction is committed, its changes are permanent and will survive any subsequent failures.

But, since NimbusDB is an in-memory database, it does not provide durability by default. You need to manually save and load your data back from persistent storage to achieve data persistence (e.g using Model.export()/Catalog.backup() and Model.import()/Catalog.restore() methods).

Transaction means to create a perform isolated operations on a database (model or catalog) with the partial ACID properties.

Use .transaction() method on model or catalog instance to create and begin a transaction.

// start a transaction on `items` model
items.transaction(function(tx) {
// inside the transaction context, use `tx` instead of `items`
// all CRUD operations are done through `tx` object
// for example:
// tx.insert({ /* some data here */ });
});
// you can also start a transaction on catalog
ctg_items.transaction("items", function(tx) { // first parameter is the model name or custom id
// transaction operations here
// ...
});

By default, NimbusDB performs operations in a buffered mode, meaning that all operations are performed on temporary model copy first before applying it to the original model once committed.

items.transaction(function(tx) {
// transaction operations here
// ...
}, {
buffered: false,
// ... other options
});
ctg_items.transaction("items", function(tx) {
// transaction operations here
// ...
}, {
buffered: false,
// ... other options
});
  • On buffered transaction:

    • All operations are performed on the copied model, so it’s safer to use.
    • The original model will remain unchanged until the transaction is committed:
      • On success, it will apply the operations to the original model.
      • On failure, nothing happens to the original model.
    • It is the default behavior.
  • On unbuffered transaction:

    • All operations are performed directly on the original model.
    • The original model will be modified immediately after each operation:
      • On success, nothing happens because the original model is already updated.
      • On failure, the original model will be rolled back one-by-one in reverse order of the performed operations.
    • It is only available if you set buffered: false in the options.

The .commit() method can only be used once per transaction, and it will commit all the operations performed during the transaction. But you can change this behavior, so .commit() can be used multiple times.

items.transaction(function(tx) {
// transaction operations here
// ...
}, {
staged_commit: true,
// ... other options
});
ctg_items.transaction("items", function(tx) {
// transaction operations here
// ...
}, {
staged_commit: true,
// ... other options
});

By default, the transaction will automatically rollback if it’s discarded (not calling any .commit() before the end of the callback function).

items.transaction(function(tx) {
// transaction operations here, without calling any `.commit()`
// ...
});
// `items` rolled back automatically here

So, don’t forget to call .commit() before the end of the callback function if you want to commit the transaction.

items.transaction(function(tx) {
// transaction operations here, without calling any `.commit()`
// ...
tx.commit();
});
// `items` committed

You can also set the auto_rollback option to false to prevent the transaction from being automatically rolled back when it’s discarded.

items.transaction(function(tx) {
// transaction operations here, without calling any `.commit()`
// ...
}, {
auto_rollback: false
});
// `items` committed automatically here

You can also call .commit() method before the end of the callback function to commit the transaction early.

The remaining operations after .commit() will be ignored.

items.transaction(function(tx) {
// transaction operations here
// ...
tx.commit(); // <-- early commit here
// ... other tx operations
// these operations will be skipped because the transaction is already committed
});
// `items` committed

If staged_commit is true, you can call .commit() multiple times. Each call will commit all the operations performed since the last commit.

items.transaction(function(tx) {
// first batch operations
// ...
tx.commit(); // <-- first commit here, apply operations to the original model
// ... other tx operations (second batch)
tx.commit(); // <-- second commit here, apply second batch operations to the original model
// ... and so on
// and if the transaction fail, it will rollback to the previous committed state
// so, if it's fail here (after second commit), it will commit from the first batch until second batch
tx.commit();
}, {
staged_commit: true
});
// `items` committed

Used to rollback the transaction manually, like the return keyword in a normal function.

items.transaction(function(tx) {
// transaction operations here
// ...
tx.rollback(); // <-- rollback here
// ... other tx operations
// these operations will be skipped because the transaction is already rolled back
});
// `items` rolled back

NimbusDBTransaction Internal

Section titled “NimbusDBTransaction ”

Represents an isolated transaction context over a NimbusDB model, supporting atomic CRUD operations with commit and rollback capabilities.

transaction.d.ts
class NimbusDBTransaction {
constructor(
_original_model: NimbusDBModel,
_options?: NimbusDBTransactionOptions
);
id: int;
static __id: int;
__active: boolean; // whether this transaction is valid or not
__auto_rollback: boolean;
__buffered: boolean;
__is_commited: boolean;
__is_rolled_back: boolean;
__model: NimbusDBModel; // temp model if buffered
__original_model: NimbusDBModel;
__operations: NimbusDBTransactionOps[]; // return result for each operation
__staged_commit: boolean;
__sys_temp: Struct | null;
__temp_model: boolean;
// ...public and internal methods
}

Runs a transaction against the model.

model.d.ts
class NimbusDBModel {
// ... other methods and properties ...
static transaction(
_func: (tx: NimbusDBTransaction) => void,
_options?: NimbusDBTransactionOptions
): void;
}
  • Type: (tx: NimbusDBTransaction) => void
  • Callback function that performs operations on the transaction.
  • Type: NimbusDBTransactionOptions
  • Default: undefined
  • Optional configuration for the transaction.

Runs a transaction against the catalog.

catalog.d.ts
class NimbusDBCatalog {
// ... other methods and properties ...
static transaction(
_name: string,
_func: (tx: NimbusDBTransaction) => void,
_options?: NimbusDBTransactionOptions
): void;
}
  • Type: string
  • The name of the model to run the transaction against.
  • Type: (tx: NimbusDBTransaction) => void
  • Callback function that performs operations on the transaction.
  • Type: NimbusDBTransactionOptions
  • Default: undefined
  • Optional configuration for the transaction.

Manually commits the transaction early (when staged_commit is false), or applies all operations performed before this .commit() is called (when staged_commit is true).

transaction.d.ts
class NimbusDBTransaction {
// ... other methods and properties ...
commit(): void;
}

Prints the current state of the transaction model’s data to the debug console.

transaction.d.ts
class NimbusDBTransaction {
// ... other methods and properties ...
print(): void;
}

Rolls back all changes made during this transaction, restoring the model to its pre-transaction state.

transaction.d.ts
class NimbusDBTransaction {
// ... other methods and properties ...
rollback(): void;
}

Optional configuration for creating a transaction.

transaction.d.ts
type NimbusDBTransactionOptions = Partial<{
auto_rollback: boolean; // rollback automatically if the transaction is discarded (default = true)
buffered: boolean; // isolate the transaction from the original model (default = true)
staged_commit: boolean; // enable `.commit` to be called multiple times (default = false)
temp_model: NimbusDBModel; // [INTERNAL] temp model if buffered
}>;

NimbusDBTransactionOps Internal

Section titled “NimbusDBTransactionOps ”

Represents the result of a single operation within a transaction.

transaction.d.ts
type NimbusDBTransactionOps =
| {
type: NIMBUSDB_TRANSACTION.GET;
selector: (NimbusDBGetByIndex | NimbusDBGetByPrimary | NimbusDBGetByValue | NimbusDBGetByFunction);
result: any;
}
| {
type: NIMBUSDB_TRANSACTION.INSERT;
data_id: int;
data: Struct[];
options: NimbusDBInsertOptions;
result: NimbusDBInsertResult;
}
| {
type: NIMBUSDB_TRANSACTION.REMOVE;
selector: (NimbusDBRemoveByIndex | NimbusDBRemoveByPrimary | NimbusDBRemoveByValue | NimbusDBRemoveByFunction);
result: NimbusDBRemoveResult;
}
| {
type: NIMBUSDB_TRANSACTION.UPDATE;
selector: (NimbusDBUpdateByIndex | NimbusDBUpdateByPrimary | NimbusDBUpdateByValue | NimbusDBUpdateByFunction);
update_data: any | StructExt;
result: NimbusDBUpdateResult;
}
| {
type: NIMBUSDB_TRANSACTION.UPSERT;
data_id: int;
data: Struct[];
options: NimbusDBUpsertOptions;
result: NimbusDBUpsertResult;
}
;

NIMBUSDB_TRANSACTION Internal

Section titled “NIMBUSDB_TRANSACTION ”

The type of operation performed in a transaction.

transaction.d.ts
export const enum NIMBUSDB_TRANSACTION {
GET,
INSERT,
REMOVE,
UPDATE,
UPSERT
}