---
title: Best Practices for CRUD
slug: v4-4/aWGbRnWZHcfuXNgGjA-KQ
docTags: 
createdAt: 2023-09-15T20:13:28.243Z
---

To help you make the most out of Ditto's capabilities and ensure a frictionless experience for your end users, implement optimization strategies to enhance the efficiency, speed, and precision of your `create`, `read`, `update`, and `delete` (CRUD) operations.&#x20;

This article offers a variety of optimization techniques for improving app performance.

# Querying Dynamic Data Using $args&#x20;

When invoking the Find and Observe Local methods to query values  that may change dynamically during runtime, you can declare a top-level `args` variable that defines the dynamic values, and then pass the the `$args` identifier within your query conditions.

That way, the query engine separates your query logic from the data so you can easily define and pass values as needed to adapt without having to change the query structure itself.

:::hint{type="warning"}
Using `strings` to filter dynamic data may impact the maintainability, security, and performance of your app by introducing various issues such as syntax errors in your code.
:::

:::CodeblockTabs
```swift
let query = "color == $args.color"
let args = [ "color": "blue" ]
let documents = ditto.store
  .collection("your_collection_name")
  .find(query, args)
  .exec()
```

```kotlin
val foundDocs = ditto.store.collection("people")
    .find("name == \$args.name && age <= \$args.age", mapOf("name" to "max", "age" to 32))
```

```javascript
const query = 'name == $args.name && age <= $args.age'
const documents = await ditto.store.collection('people').find(query, {
  age: 32,
  name: 'Max',
})
```

```java
Map<String, Object> queryArgs = new HashMap<>();
queryArgs.put("name", "max");
queryArgs.put("age", 32);

List<DittoDocument> foundDocs = ditto.store.collection("users")
        .find("name == $args.name && age <= $args.age", queryArgs)
        .exec();
```

```csharp
var docs = ditto.Store
    .Collection("cars")
    .Find(
        "color == $args.color",
        new Dictionary<string, object> { "color", "blue" } )
    .Exec();
```

```cpp
json args = json({{"color", "red"}});
std::vector<Document> big_c_values =
    ditto.get_store()
        .collection("cars")
        .find("color == $args.color, args)
        .exec();
```

```rust
let query = "color == $args.color";
let args = json!({ "color": "blue" });

let documents = collection
  .find_with_args(query, args)
  .exec()?;
```
:::

# Batching Multiple Operations

To reduce the number of network roundtrips needed for each individual operation, ensure data consistency, and simplify your code, use the `write` method to perform multiple upsert, update, remove, and evict operations across two or more document collections within a single Ditto call.

Additionally, you can incorporate logic that facilitates the retrieval of a document's current state and, when necessary, initiate a conditional `update` operation based on that state.&#x20;

:::hint{type="warning"}
Initiating a write transaction within another write transaction can cause Ditto to hang indefinitely, resulting in a deadlock.&#x20;

If a deadlock situation occurs, both transactions are unable to complete, the log messages at the ERROR level persist in a forever loop, and you’ll need to review your app’s codebase.
:::

:::hint{type="warning"}
Do not combine Remove and Upsert functions for the same document within a single write transaction.&#x20;

If you remove a document as part of the operation, potential concurrency conflicts may occur for it is impossible to update a document that no longer exists in Ditto.
:::

The following snippet demonstrates a batch operation in which two write transactions are contained in a single transaction enclosure:

:::CodeblockTabs
```swift
ditto.store.write { transaction in
    let cars = transaction.scoped(toCollectionNamed: "cars")
    let people = transaction.scoped(toCollectionNamed: "people")
    let docId = "abc123"
    do {
        try people.upsert(["_id": docId, "name": "Susan"] as [String: Any?])
        try cars.upsert(["make": "Ford", "color": "red", "owner": docId] as [String: Any?])
        try cars.upsert(["make": "Toyota", "color": "black", "owner": docId] as [String: Any?])
    } catch (let err) {
      print(err.localizedDescription)
    }
    people.findByID(docId).evict()
}
```

```kotlin
val results = ditto.store.write { transaction ->
    val cars = transaction.scoped("cars")
    val people = transaction.scoped("people")
    val docId = "abc123"
    people.upsert(mapOf("_id" to docId, "name" to "Susan"))
    cars.upsert(mapOf("make" to "Hyundai", "color" to "red", "owner" to docId))
    cars.upsert(mapOf("make" to "Jeep", "color" to "pink", "owner" to docId))
    people.findById(DittoDocumentId(docId)).evict()
}
```

```javascript
const results = await ditto.store.write(async (transaction) => {
  // Use the `WriteTransaction` object that is available in the
  // transaction closure to acquire a handle to any collection, which
  // allows modifying and accessing its documents in the transaction
  // context.
  const cars = transaction.scoped('cars')
  const people = transaction.scoped('people')

  // In this example a new person and car document are created, and
  // finally the person document that was just created is evicted.
  // If any of these operations fail, all others are not applied.
  const susanId = await people.upsert({
    name: 'Susan',
  })
  await cars.upsert({
    make: 'Hyundai',
    color: 'red',
    owner: susanId,
  })
  await people.findByID(susanId).evict()
})

// The return value of a transaction is a list that contains a
// summary of all operations in the transaction and the document IDs
// that were affected:

// results == [
//   {
//     type: 'inserted',
//     docID: DocumentID { ... },
//     collectionName: 'people'
//   },
//   {
//     type: 'inserted',
//     docID: DocumentID { ... },
//     collectionName: 'cars'
//   },
//   {
//     type: 'evicted',
//     docID: DocumentID { ... },
//     collectionName: 'people'
//   }
// ]
```

```java
val results = ditto.store.write { transaction ->
    val cars = transaction.scoped("cars")
    val people = transaction.scoped("people")
    val docId = "abc123"
    people.upsert(mapOf("_id" to docId, "name" to "Susan"))
    cars.upsert(mapOf("make" to "Hyundai", "color" to "red", "owner" to docId))
    cars.upsert(mapOf("make" to "Jeep", "color" to "pink", "owner" to docId))
    people.findById(DittoDocumentId(docId)).evict()
}
```

```csharp
var results = ditto.Store.Write(writeTxn =>
        {
            var collOneTx = writeTxn.Scoped(collectionOne.Name);
            var collTwoTx = writeTxn[collectionTwo.Name];
```

```cpp
auto results = ditto.get_store().write([&](WriteTransaction &write_txn) {
  ScopedWriteTransaction people = write_txn.scoped("people");
  ScopedWriteTransaction cars = write_txn.scoped("cars");
  auto docId = "abc123";
  people.upsert({{"name", "Susan"}, {"_id", DocumentId(docId)}});
  cars.upsert({{"make", "Hyundai"}, {"owner", DocumentId(docId)}});
  cars.upsert({{"make", "Toyota"}, {"owner", DocumentId(docId)}});
});
```

```rust
ditto.store().with_batched_write(|batch| {
    let mut foo_coll = batch.collection("foo");
    foo_coll.find...().remove();
    let mut bar_coll = batch.collection("bar");
    // Expensive multi-mutation op:
    for _ in 0 .. 10_000 {
        let doc = ...;
        bar_coll.insert(doc, None, false);
    }
    // Commit (or revert) these changes:
    batch.commit_changes()
})
```
:::

# Using ISO-8601 for Date Strings

For more precise representations of date strings in queries for comparison operations, use the International Organization for Standardization (*ISO*) *8601* standard format. For an overview of operators and `string` functions and formats, see *Ditto Basics* > [Query Tools](docId\:ESQ8nYEK3msPdw9-EcDh1).

ISO-8601 is an international standard for representing dates, times, and durations. The format adheres to specific patterns, such as the following standard formatting for date and time:

| **Date**     | **Time**   |
| ------------ | ---------- |
| `YYYY-MM-DD` | `HH:mm:ss` |

For more information about ISO 8601, see the official ISO documentation > [ISO 8601 Date and time format](https://www.iso.org/iso-8601-date-and-time-format.html).&#x20;

# Implementing Write Strategies

The Ditto SDK offers *write strategies* that you can incorporate into your code to control how Ditto makes changes and resolves any potential concurrency conflicts, helping you manage your data more effectively.&#x20;

A write strategy is effectively a set of instructions that tell Ditto how to handle specific changes and conflicts when you are updating and upserting data to the local Ditto store.&#x20;

The Ditto SDK provides the following write strategies that you can use when performing update and upsert operation in your app:

## Insert if Absent

The Insert if Absent write strategy ensures that a new document with the same `_id` value is inserted only if there is no existing document with that ID in the given collection.&#x20;

If a document with the same ID already exists, the upsert operation does not perform any changes and instead maintains the existing document.

The Insert if Absent write strategy is useful for scenarios where you want to add new fields only when they are missing; if the fields exists, but the values are changed, the upsert operation does not update the changed fields.

:::CodeblockTabs
```swift
let initialDocument : [String: Any] = [
  "_id": "123456",
  "color": "blue"
]
let documentId = ditto.store
  .collection("cars")
  .upsert(initialDocument, writeStrategy: .insertIfAbsent)
```

```kotlin
val initialDocument = mapOf(
  "_id" to "123456",
  "color" to "blue"
)
val documentId = ditto.store
  .collection("cars")
  .upsert(initialDocument, DittoWriteStrategy.InsertIfAbsent)
```

```javascript
```

```java
```

```csharp
var results = ditto.Store.Write(writeTxn =>
            {
                var collOneTx = writeTxn.Scoped(collectionOne.Name);
                // we are also testing the custom subscript accessor
                var collTwoTx = writeTxn[collectionTwo.Name];

                docThreeId = collOneTx.Upsert(new Dictionary<string, object> { { "three", "document three" } });
                docFourId = collOneTx.Upsert(new Dictionary<string, object> { { "four", "document four" } });
                collOneTx.FindById(docOneId).Evict();

                docFiveId = collTwoTx.Upsert(new Dictionary<string, object> { { "five", "document five" } });
                collTwoTx.FindById(docTwoId).Update(doc =>
                {
                    doc["two"].Set("updated document two");
                });
```

```cpp
auto results = ditto.get_store().write([&](WriteTransaction &write_txn) {
  ScopedWriteTransaction people = write_txn.scoped("people");
  ScopedWriteTransaction cars = write_txn.scoped("cars");
  auto docId = "abc123";
  people.upsert({{"name", "Susan"}, {"_id", DocumentId(docId)}});
  cars.upsert({{"make", "Hyundai"}, {"owner", DocumentId(docId)}});
  cars.upsert({{"make", "Toyota"}, {"owner", DocumentId(docId)}});
});
```

```rust
ditto.store().with_batched_write(|batch| {
    let mut foo_coll = batch.collection("foo");
    foo_coll.find...().remove();
    let mut bar_coll = batch.collection("bar");
    // Expensive multi-mutation op:
    for _ in 0 .. 10_000 {
        let doc = ...;
        bar_coll.insert(doc, None, false);
    }
    // At this point, we must say whether we commit or revert
    // these changes:
    batch.commit_changes()
})
```
:::

## Insert Default if Absent

The Insert Default If Absent write strategy, when implemented, ensures that the given document upserts only if a document with the same ID does not already exist in the local Ditto store.&#x20;

If the document does not exist and it is is upserted, it will be timestamped with a value of `0`, making it appear as if it was performed before any other action. This approach helps manage concurrency conflicts and ensure that the data is inserted only when necessary.

The following snippet shows an example of the `writeStrategy: insertDefaultIfAbsent` parameter passed as an argument to the`upsert` method:

:::CodeblockTabs
```swift
do {
    let docID = try ditto.store["people"].upsert([
        "name": "Susan",
        "age": 31
    ], writeStrategy: .insertDefaultIfAbsent)
} catch {
    //handle error
    print(error)
}
```

```kotlin
val initialDocument = mapOf(
  "_id" to "123456",
  "color" to "blue"
)
val documentId = ditto.store
  .collection("cars")
  .upsert(initialDocument, DittoWriteStrategy.InsertDefaultIfAbsent)
```

```javascript
const initialDocument = {
  _id: "123456",
  color: "blue"
}
const documentId = await ditto.store
  .collection("cars")
  .upsert(initialDocument, { writeStrategy: "insertDefaultIfAbsent" })
```

```java
Map<String, Object> content = new HashMap<>();
content.put("name", "Susan");
content.put("age", 31);
DittoDocumentId docId = ditto.store
        .collection("people")
        .upsert(content, DittoWriteStrategy.InsertDefaultIfAbsent);
```

```csharp
// Set the intial color to blue
var initialDocument = new Dictionary<string, object> {
  { "_id", "123456" },
  { "color", "blue" },
};
ditto.Store
  .Collection("cars")
  .Upsert(initialDocument, DittoWriteStrategy.InsertDefaultIfAbsent)
```

```cpp
DocumentId doc_id = ditto.get_store().collection("people").upsert(
    content, WriteStrategy::insertDefaultIfAbsent);
```

```rust
//not supported in Rust
```
:::

### Example Use Case: Upserting Initial Data

When upserting *initial data* from an external source to all peers at app startup, you must pass the enumeration value: `writeStrategy: 'insertDefaultIfAbsent' ` as an argument in the `upsert` function.&#x20;

Initial data is common data like sample chat messages from a central backend API that is accessible to end users at app startup.

:::hint{type="danger"}
Failing to indicate initial upserted data may risk future overwrites due to unbounded metadata, impacting storage, app performance, and stability.
:::

For example, consider the concurrency conflict scenario and resolution outcome: Both Device A and Device B must retain the the change that occurred after Device B downloaded the common data — specifically, Device A’s change executed in step 2 at time = 1:

::::WorkflowBlock
:::WorkflowBlockItem
Device A downloads common data from a central API, and then upserts it as a document at time = 0:&#x20;

```json
{"firstName": "Adam"}
```
:::

:::WorkflowBlockItem
Device A then upserts changes to the document at time = 1:

```json
{"firstName": "Max"}
```
:::

:::WorkflowBlockItem
Device B syncs with Device A at time = 2:

```json
{"firstName": "Max"}
```
:::

:::WorkflowBlockItem
Device B downloads the same common data, but from a backend API instead, and then upserts it as a document at time = 3. &#x20;

The concurrent conflict that results from Device B’s upsert results in the previous upsert synced at time = 1 to be overwritten instead of being preserved:&#x20;

```json
{"firstName": "Adam"}
```
:::
::::

## Implementing Global Async Transactions

All write transactions are event-driven, or asynchronous, but locally scoped by default.&#x20;

When you have many documents to write to a peer, you can initiate a transaction in a way that avoids blocking or slowing down the main thread of your app by starting the transaction asynchronously; as in, it can occur concurrently with other Ditto operations.

For example, use `DispatchQueue.global`, as follows:

:::CodeblockTabs
```swift
DispatchQueue.global(qos: .default).async {

    ditto.store.write { transaction in

        let scope = transaction.scoped(toCollectionNamed: "passengers-\(thisFlight)")

    // Loop inside the transaction to avoid writing to database too frequently
        self.passengers.forEach {
            scope.upsert($0.dict)
        }
    }
}
```

```kotlin
DispatchQueue.global(qos: .default).async {

    ditto.store.write { transaction in

        let scope = transaction.scoped(toCollectionNamed: "passengers-\(thisFlight)")

    // Loop inside the transaction to avoid writing to database too frequently
        self.passengers.forEach {
            scope.upsert($0.dict)
        }
    }
}
```

```javascript
DispatchQueue.global(qos: .default).async {

    ditto.store.write { transaction in

        let scope = transaction.scoped(toCollectionNamed: "passengers-\(thisFlight)")

    // Loop inside the transaction to avoid writing to database too frequently
        self.passengers.forEach {
            scope.upsert($0.dict)
        }
    }
}
```

```java
DispatchQueue.global(qos: .default).async {

    ditto.store.write { transaction in

        let scope = transaction.scoped(toCollectionNamed: "passengers-\(thisFlight)")

    // Loop inside the transaction to avoid writing to database too frequently
        self.passengers.forEach {
            scope.upsert($0.dict)
        }
    }
}
```

```csharp
DispatchQueue.global(qos: .default).async {

    ditto.store.write { transaction in

        let scope = transaction.scoped(toCollectionNamed: "passengers-\(thisFlight)")

    // Loop inside the transaction to avoid writing to database too frequently
        self.passengers.forEach {
            scope.upsert($0.dict)
        }
    }
}
```

```cpp
DispatchQueue.global(qos: .default).async {

    ditto.store.write { transaction in

        let scope = transaction.scoped(toCollectionNamed: "passengers-\(thisFlight)")

    // Loop inside the transaction to avoid writing to database too frequently
        self.passengers.forEach {
            scope.upsert($0.dict)
        }
    }
}
```

```rust
//not supported in Rust
```
:::

## Avoiding Deadlocks

To avoid *deadlocks*, it is important to ensure that nested write transactions do not initiate within the scope of another write transaction.&#x20;

A deadlock is a situation that occurs when a conflicting code pattern blocks two or more threads or processes from proceeding with their execution because each is waiting for a resource that the other holds (and never receives), resulting in a standstill of circular dependency.&#x20;

:::hint{type="warning"}
In order to prevent potential conflicts that could arise from concurrent write operations, do not initiate a new write transaction to modify data until the current clause or write transaction is complete.
:::

### Example of a Deadlock

The following snippet demonstrates a queued write transaction that has the potential to create a deadlock situation. This is because the write transaction initiated on the `"people"` collection, along with its nested write transaction on the `"settings"` collection both attempt to acquire resources held by the other:

:::CodeblockTabs
```swift
// Start a write transaction:
ditto.store["people"].findByID(docID).update { mutableDoc in
  // Start a write transaction _within_ a write transacton.
  // !! Deadlocks !!
  let docID = try! ditto.store["settings"].upsert([
      "_id": "abc123",
      "preference": 31,
  ]) 
  // ...
}
```

```kotlin
// Start a write transaction:
ditto.store["people"].findByID(docID).update { mutableDoc in
  // Start a write transaction _within_ a write transacton.
  // !! Deadlocks !!
  let docID = try! ditto.store["settings"].upsert([
      "_id": "abc123",
      "preference": 31,
  ]) 
  // ...
}
```

```javascript
// Start a write transaction:
ditto.store["people"].findByID(docID).update { mutableDoc in
  // Start a write transaction _within_ a write transacton.
  // !! Deadlocks !!
  let docID = try! ditto.store["settings"].upsert([
      "_id": "abc123",
      "preference": 31,
  ]) 
  // ...
}
```

```java
// Start a write transaction:
ditto.store["people"].findByID(docID).update { mutableDoc in
  // Start a write transaction _within_ a write transacton.
  // !! Deadlocks !!
  let docID = try! ditto.store["settings"].upsert([
      "_id": "abc123",
      "preference": 31,
  ]) 
  // ...
}
```

```csharp
// Start a write transaction:
ditto.store["people"].findByID(docID).update { mutableDoc in
  // Start a write transaction _within_ a write transacton.
  // !! Deadlocks !!
  let docID = try! ditto.store["settings"].upsert([
      "_id": "abc123",
      "preference": 31,
  ]) 
  // ...
}
```

```cpp
// Start a write transaction:
ditto.store["people"].findByID(docID).update { mutableDoc in
  // Start a write transaction _within_ a write transacton.
  // !! Deadlocks !!
  let docID = try! ditto.store["settings"].upsert([
      "_id": "abc123",
      "preference": 31,
  ]) 
  // ...
}
```

```rust
//not supported in Rust
```
:::

### Log Message: Transaction Remains Blocked

If you have a queued write transaction that remains blocked, the following message appears in your logs:

```json
LOG_LEVEL: Waiting for write transaction (elapsed: XXs), originator=User blocked_by=User
```

