From e5c00bb32c2a34324a3ea90be4e5e5c2ab7db1dc Mon Sep 17 00:00:00 2001 From: Tyler Nix Date: Wed, 10 Sep 2025 12:01:52 -0600 Subject: [PATCH 1/5] Add Duplicate Writes documentation - Create new duplicate-writes.mdx file with comprehensive documentation - Document on_duplicate and on_missing parameters for Write API - Include practical examples showing default vs permissive behavior - Add API parameter reference with CURL examples - Update WriteRequestViewer component to support writeOptions and deleteOptions - Implement pretty-printed JSON formatting in CURL examples - Update transactional-writes.mdx to reference new duplicate writes feature - Add duplicate-writes to sidebar navigation - Document use cases for data synchronization and idempotent operations - Include important concepts about atomicity and race conditions - Add detailed explanations for tuple conditions and conflict scenarios --- docs/content/interacting/duplicate-writes.mdx | 260 ++++++++++++++++++ .../interacting/transactional-writes.mdx | 10 + docs/sidebars.js | 5 + 3 files changed, 275 insertions(+) create mode 100644 docs/content/interacting/duplicate-writes.mdx diff --git a/docs/content/interacting/duplicate-writes.mdx b/docs/content/interacting/duplicate-writes.mdx new file mode 100644 index 0000000000..bb3bd92fd5 --- /dev/null +++ b/docs/content/interacting/duplicate-writes.mdx @@ -0,0 +1,260 @@ +--- +sidebar_position: 3 +slug: /interacting/duplicate-writes +description: Making write operations resilient with duplicate writes and missing deletes handling +--- + +import { + AuthzModelSnippetViewer, + CardBox, + DocumentationNotice, + ProductConcept, + ProductName, + ProductNameFormat, + RelatedSection, + RelationshipTuplesViewer, + WriteRequestViewer, + SupportedLanguage, +} from '@components/Docs'; + +# Duplicate Writes + + + +Making Write Operations Resilient in by handling duplicate writes and missing deletes gracefully. + + + +Use duplicate writes handling in high-volume or distributed systems where you need to ensure data synchronization without causing transaction failures on duplicate operations or missing deletes. + + + +## The Core Problem and the Solution + +The Write API is the primary method for adding and removing in your store. In high-volume or distributed systems, ensuring that data is synchronized correctly without causing errors on duplicate operations is critical. + +Previously, a bulk Write request was strictly "all-or-nothing": + +- If you tried to write a tuple that already existed, the entire request would fail. +- If you tried to delete a tuple that didn't exist, the entire request would fail. + +This required developers to build complex state-checking logic into their applications. + +To solve this, the Write API now supports optional parameters to handle duplicate writes and missing deletes gracefully. This means you can safely retry a Write request without causing transaction failures for these specific cases, greatly simplifying data synchronization logic. + +The new `on_duplicate` and `on_missing` parameters change this behavior, allowing you to instruct the API to simply ignore these cases and process the rest of the request successfully. + +## A Practical Example + +Here's a look at how this feature changes the API's behavior when you attempt to write a tuple that already exists. + +### Before: The Default Behavior + +Without `on_duplicate: "ignore"`, the API returns a 400 Bad Request because the tuple already exists. + +```bash +curl -X POST 'http://localhost:8080/stores/{store_id}/write' \ + -H 'content-type: application/json' \ + --data '{ + "writes": { + "tuple_keys": [ + { + "user": "user:anne", + "relation": "owner", + "object": "document:test" + } + ] + }, + "authorization_model_id": "..." + }' +``` + +**Response:** +```http +HTTP/1.1 400 Bad Request +Content-Type: application/json + +{ + "code": "write_failed_due_to_invalid_input", + "message": "cannot write a tuple which already exists: user: 'user:anne', relation: 'owner', object: 'document:test'..." +} +``` + +### After: Using on_duplicate: "ignore" + +By setting the parameter, the duplicate is ignored and the API returns a 200 OK. + +```bash +curl -X POST 'http://localhost:8080/stores/{store_id}/write' \ + -H 'content-type: application/json' \ + --data '{ + "writes": { + "tuple_keys": [ + { + "user": "user:anne", + "relation": "owner", + "object": "document:test" + } + ], + "on_duplicate": "ignore" + }, + "authorization_model_id": "..." + }' +``` + +**Response:** +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{} +``` + +## API Reference + +The new parameters are added within the `writes` and `deletes` objects in the body of a Write request. + +### New Parameter for writes: on_duplicate + +Enables resilient behavior when writing duplicate tuples. + +- **"error" (Default)**: The request will fail if any tuple in the writes array already exists. This maintains backward compatibility. +- **"ignore"**: The API will silently ignore any attempt to write a tuple that already exists and will proceed to write the new ones. The request will succeed. + + + +### New Parameter for deletes: on_missing + +Enables resilient behavior when deleting missing tuples. + +- **"error" (Default)**: The request will fail if any tuple in the deletes array does not exist. +- **"ignore"**: The API will silently ignore any attempt to delete a tuple that does not exist and will proceed to delete the ones that do. The request will succeed. + + + +## Flexibility and Granular Control + +:::tip +These flags can be used independently. You can perform a permissive write (`on_duplicate: "ignore"`) and a strict delete (`on_missing: "error"`) within the same atomic transaction, giving you granular control. +::: + +**Example Request with Mixed Behavior:** + +```bash +curl -X POST 'http://localhost:8080/stores/{store_id}/write' \ + -H 'content-type: application/json' \ + --data '{ + "writes": { + "tuple_keys": [ + { + "user": "user:anne", + "relation": "reader", + "object": "document:2021-budget" + } + ], + "on_duplicate": "ignore" + }, + "deletes": { + "tuple_keys": [ + { + "user": "user:anne", + "relation": "writer", + "object": "document:2021-budget" + } + ], + "on_missing": "error" + }, + "authorization_model_id": "01G50QVV17PECNVAHX1GG4Y5NC" + }' +``` + +In this example: +- If the read permission already exists, it will be ignored and the request continues +- If the write permission doesn't exist for deletion, the entire request will fail + +## Use Cases + +### Data Synchronization + +When synchronizing data between systems, you can use `on_duplicate: "ignore"` to safely replay events without worrying about duplicate writes. + +### Idempotent Operations + +Make your write operations idempotent by using `on_duplicate: "ignore"` and `on_missing: "ignore"` to ensure the same operation can be safely executed multiple times. + +### Resilient Batch Operations + +In batch operations where some tuples might already exist or might have been deleted, these parameters allow you to process the entire batch without failing on individual conflicts. + +## Related Sections + + diff --git a/docs/content/interacting/transactional-writes.mdx b/docs/content/interacting/transactional-writes.mdx index 7048583294..f535e3992e 100644 --- a/docs/content/interacting/transactional-writes.mdx +++ b/docs/content/interacting/transactional-writes.mdx @@ -167,6 +167,10 @@ curl -X POST 'http://localhost:8080/stores/{store_id}/write' \ +:::tip +For handling cases where you need to write tuples that might already exist or delete tuples that might not exist, check out [Duplicate Writes](./duplicate-writes.mdx) which provides resilient write operations. +::: + The Write API allows you to send up to 100 unique tuples in the request. (This limit applies to the sum of both writes and deletes in that request). This means we can submit one API call that converts the `tweet` from public to visible to only the `user`'s followers. service attempts to perfo Date: Wed, 10 Sep 2025 12:03:36 -0600 Subject: [PATCH 2/5] Add Duplicate Writes documentation - Create new duplicate-writes.mdx file with comprehensive documentation - Document on_duplicate and on_missing parameters for Write API - Include practical examples showing default vs permissive behavior - Add API parameter reference with CURL examples - Update WriteRequestViewer component to support writeOptions and deleteOptions - Implement pretty-printed JSON formatting in CURL examples - Update transactional-writes.mdx to reference new duplicate writes feature - Add duplicate-writes to sidebar navigation - Document use cases for data synchronization and idempotent operations - Include important concepts about atomicity and race conditions - Add detailed explanations for tuple conditions and conflict scenarios --- docs/content/interacting/duplicate-writes.mdx | 254 ++++++++---------- .../Docs/SnippetViewer/WriteRequestViewer.tsx | 51 +++- 2 files changed, 157 insertions(+), 148 deletions(-) diff --git a/docs/content/interacting/duplicate-writes.mdx b/docs/content/interacting/duplicate-writes.mdx index bb3bd92fd5..bc0b411bcf 100644 --- a/docs/content/interacting/duplicate-writes.mdx +++ b/docs/content/interacting/duplicate-writes.mdx @@ -1,7 +1,7 @@ --- sidebar_position: 3 slug: /interacting/duplicate-writes -description: Making write operations resilient with duplicate writes and missing deletes handling +description: Handle duplicate writes and missing deletes in write operations --- import { @@ -15,64 +15,44 @@ import { RelationshipTuplesViewer, WriteRequestViewer, SupportedLanguage, -} from '@components/Docs'; +} from "@components/Docs"; + # Duplicate Writes -Making Write Operations Resilient in by handling duplicate writes and missing deletes gracefully. +Handle duplicate writes and missing deletes gracefully using the Write API's optional `on_duplicate` and `on_missing` parameters. -Use duplicate writes handling in high-volume or distributed systems where you need to ensure data synchronization without causing transaction failures on duplicate operations or missing deletes. +Common scenarios include high-volume data imports into or migrations between distributed systems where you need to ensure data synchronization without causing transaction failures on duplicate writes or missing deletes. -## The Core Problem and the Solution - -The Write API is the primary method for adding and removing in your store. In high-volume or distributed systems, ensuring that data is synchronized correctly without causing errors on duplicate operations is critical. - -Previously, a bulk Write request was strictly "all-or-nothing": +## Understanding the Problem -- If you tried to write a tuple that already existed, the entire request would fail. -- If you tried to delete a tuple that didn't exist, the entire request would fail. +The Write API is the primary method for adding and removing in your store. -This required developers to build complex state-checking logic into their applications. +By default, the Write API operates with strict validation ("all-or-nothing"): -To solve this, the Write API now supports optional parameters to handle duplicate writes and missing deletes gracefully. This means you can safely retry a Write request without causing transaction failures for these specific cases, greatly simplifying data synchronization logic. +- Writing a tuple that already exists causes the entire request to fail (even if only 1 tuple out of 40 possible tuples is a duplicate) +- Deleting a tuple that doesn't exist causes the entire request to fail -The new `on_duplicate` and `on_missing` parameters change this behavior, allowing you to instruct the API to simply ignore these cases and process the rest of the request successfully. +This strict behavior requires applications to check the current state of the tuple in before making changes, implement complex retry logic, or simply ignore the errors altogether. -## A Practical Example +## How to Handle Duplicate Operations -Here's a look at how this feature changes the API's behavior when you attempt to write a tuple that already exists. +To improve import and migration scenarios, the Write API provides optional parameters to control how duplicate writes and missing deletes are handled. -### Before: The Default Behavior +The `on_duplicate` and `on_missing` parameters change this behavior, allowing you to instruct the API to ignore these cases and process the rest of the request successfully. + +### Response: Default Behavior -Without `on_duplicate: "ignore"`, the API returns a 400 Bad Request because the tuple already exists. - -```bash -curl -X POST 'http://localhost:8080/stores/{store_id}/write' \ - -H 'content-type: application/json' \ - --data '{ - "writes": { - "tuple_keys": [ - { - "user": "user:anne", - "relation": "owner", - "object": "document:test" - } - ] - }, - "authorization_model_id": "..." - }' -``` +Without the `on_duplicate` parameter, attempting to write an existing tuple returns a `400 Bad Request`. -**Response:** ```http HTTP/1.1 400 Bad Request -Content-Type: application/json { "code": "write_failed_due_to_invalid_input", @@ -80,165 +60,172 @@ Content-Type: application/json } ``` -### After: Using on_duplicate: "ignore" - -By setting the parameter, the duplicate is ignored and the API returns a 200 OK. - -```bash -curl -X POST 'http://localhost:8080/stores/{store_id}/write' \ - -H 'content-type: application/json' \ - --data '{ - "writes": { - "tuple_keys": [ - { - "user": "user:anne", - "relation": "owner", - "object": "document:test" - } - ], - "on_duplicate": "ignore" - }, - "authorization_model_id": "..." - }' -``` +### Response: Ignoring Duplicates + +Setting `on_duplicate: "ignore"` allows the duplicate to be ignored and the API returns a `200 OK`. -**Response:** ```http HTTP/1.1 200 OK -Content-Type: application/json {} ``` -## API Reference +## Optional API Parameters -The new parameters are added within the `writes` and `deletes` objects in the body of a Write request. +The parameters are added within the `writes` and `deletes` objects in the body of a Write request. -### New Parameter for writes: on_duplicate +### Writes -Enables resilient behavior when writing duplicate tuples. +The `on_duplicate` parameter controls the behavior when writing tuples. -- **"error" (Default)**: The request will fail if any tuple in the writes array already exists. This maintains backward compatibility. -- **"ignore"**: The API will silently ignore any attempt to write a tuple that already exists and will proceed to write the new ones. The request will succeed. +- **"error" (Default)**: The request fails if any tuple in the writes array already exists. This maintains backward compatibility. +- **"ignore"**: The API ignores any attempt to write a tuple that already exists and proceeds to write the new ones in the same transaction. The request succeeds. -### New Parameter for deletes: on_missing +:::caution +At the moment, this feature is only available on the API. Supported SDKs will follow shortly after. +::: -Enables resilient behavior when deleting missing tuples. +### Deletes -- **"error" (Default)**: The request will fail if any tuple in the deletes array does not exist. -- **"ignore"**: The API will silently ignore any attempt to delete a tuple that does not exist and will proceed to delete the ones that do. The request will succeed. +The `on_missing` parameter controls behavior when deleting tuples. + +- **"error" (Default)**: The request fails if any tuple in the deletes array does not exist. +- **"ignore"**: The API ignores any attempt to delete a tuple that does not exist and proceeds to delete the ones that do exist. The request succeeds. -## Flexibility and Granular Control +## Using Both Parameters Together + +The decision to have separate `on_duplicate` and `on_missing` parameters is intentional. This design gives you granular, independent control over the behavior of writes and deletes within a single atomic transaction. You can mix and match strict and permissive behaviors to suit your exact needs. + +For example, you might perform a strict delete (`on_missing: "error"`) to confirm that a specific permission has been successfully removed before making a permissive write (`on_duplicate: "ignore"`) that guarantees the new permission exists. :::tip -These flags can be used independently. You can perform a permissive write (`on_duplicate: "ignore"`) and a strict delete (`on_missing: "error"`) within the same atomic transaction, giving you granular control. +This flexibility is particularly useful when you are trying to synchronize state between your application's database and . You might need a strict operation to fail so you can roll back corresponding changes in your own database, ensuring overall system consistency. ::: -**Example Request with Mixed Behavior:** - -```bash -curl -X POST 'http://localhost:8080/stores/{store_id}/write' \ - -H 'content-type: application/json' \ - --data '{ - "writes": { - "tuple_keys": [ - { - "user": "user:anne", - "relation": "reader", - "object": "document:2021-budget" - } - ], - "on_duplicate": "ignore" + In this example: -- If the read permission already exists, it will be ignored and the request continues -- If the write permission doesn't exist for deletion, the entire request will fail +- If the permission to delete doesn't exist, the entire request will fail as intended. +- If the tuple exists and is successfully deleted, the write permission will succeed whether the tuple already exists or is written in that moment. + +## Important Concepts + +### The Write Request Remains Atomic + +All tuples in a request will still be processed as a single atomic unit. The `ignore` option only changes the success criteria for individual operations in the request. + +### "Best effort" ignore -## Use Cases +For writes: An `on_duplicate: 'ignore'` operation uses a "best effort" approach. We will attempt *once* to ignore duplicates and write non-duplicates, but if there is ever a conflict writing to the database (i.e. write a tuple we don’t think exists but it suddenly exists, probably due to a parallel request), we will abort the race condition immediately, and just return a `409 Conflict` error. These errors are rare, but can happen. +For deletes: An `on_missing: 'ignore'` operation is immune to race conditions due to database-level locks. It is not possible for another request to interfere since a delete operation will always succeed if the tuple exists or not. -### Data Synchronization +### "Ignore" is Not an "Upsert" -When synchronizing data between systems, you can use `on_duplicate: "ignore"` to safely replay events without worrying about duplicate writes. +It is critical to understand that `on_duplicate: "ignore"` will not update an existing tuple, only ignore an identical tuple. This is why we do not call the operation an "idempotent" operation. -### Idempotent Operations +The behavior of `on_duplicate: "ignore"` is more nuanced for tuples with conditions. +- **Identical Tuples**: If a tuple in the request is 100% identical to an existing tuple (same user, relation, object, condition name, and condition context), it will be safely ignored. +- **Conflicting Tuples**: If a tuple key (user, relation, object) matches an existing tuple, but the condition is different, this is a conflict. The write attempt will be rejected, and the entire transaction will fail with a `409 Conflict` error. **The correct pattern to safely update a tuple's condition requires explicitly deleting the old tuple and writing the new one within the same atomic Write request.** -Make your write operations idempotent by using `on_duplicate: "ignore"` and `on_missing: "ignore"` to ensure the same operation can be safely executed multiple times. -### Resilient Batch Operations -In batch operations where some tuples might already exist or might have been deleted, these parameters allow you to process the entire batch without failing on individual conflicts. +```http +HTTP/1.1 409 Conflict +{ + "code": "Aborted", + "message": "transactional write failed due to conflict: attempted to write a tuple which already exists with a different condition: user: 'user:anne', relation: 'writer', object: 'document:2025-budget'" +} +``` + +:::tip +The condition is not returned in the response, but you can call `/read` for this tuple to view its condition. +::: + +:::warning +The deletes operation in the Write API does not accept a condition. Attempting to include one will result in an invalid request. +::: ## Related Sections diff --git a/src/components/Docs/SnippetViewer/WriteRequestViewer.tsx b/src/components/Docs/SnippetViewer/WriteRequestViewer.tsx index 30176cfd1d..ff05ee345c 100644 --- a/src/components/Docs/SnippetViewer/WriteRequestViewer.tsx +++ b/src/components/Docs/SnippetViewer/WriteRequestViewer.tsx @@ -25,6 +25,12 @@ interface WriteRequestViewerOpts { isDelete?: boolean; skipSetup?: boolean; allowedLanguages?: SupportedLanguage[]; + writeOptions?: { + on_duplicate?: 'error' | 'ignore'; + }; + deleteOptions?: { + on_missing?: 'error' | 'ignore'; + }; } function writeRequestViewer(lang: SupportedLanguage, opts: WriteRequestViewerOpts) { @@ -59,21 +65,42 @@ ${ }`; } case SupportedLanguage.CURL: { - const writeTuples = opts.relationshipTuples - ? opts.relationshipTuples.map((tuple) => `${JSON.stringify(tuple)}`).join(',') - : ''; - const deleteTuples = opts.deleteRelationshipTuples - ? opts.deleteRelationshipTuples.map((tuple) => `${JSON.stringify(tuple)}`).join(',') - : ''; - const writes = `"writes": { "tuple_keys" : [${writeTuples}] }`; - const deletes = `"deletes": { "tuple_keys" : [${deleteTuples}] }`; - const separator = `${opts.deleteRelationshipTuples && opts.relationshipTuples ? ',' : ''}`; + // Build the JSON object for pretty printing + const requestBody: any = {}; + + if (opts.relationshipTuples?.length) { + requestBody.writes = { + tuple_keys: opts.relationshipTuples.map((tuple) => { + const { _description, ...cleanTuple } = tuple; + return cleanTuple; + }), + }; + if (opts.writeOptions?.on_duplicate) { + requestBody.writes.on_duplicate = opts.writeOptions.on_duplicate; + } + } + + if (opts.deleteRelationshipTuples?.length) { + requestBody.deletes = { + tuple_keys: opts.deleteRelationshipTuples.map((tuple) => { + const { _description, ...cleanTuple } = tuple; + return cleanTuple; + }), + }; + if (opts.deleteOptions?.on_missing) { + requestBody.deletes.on_missing = opts.deleteOptions.on_missing; + } + } + + // Add authorization_model_id at the end + requestBody.authorization_model_id = modelId; + + const prettyJson = JSON.stringify(requestBody, null, 2); + return `curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/write \\ -H "Authorization: Bearer $FGA_API_TOKEN" \\ # Not needed if service does not require authorization -H "content-type: application/json" \\ - -d '{${opts.relationshipTuples ? writes : ''}${separator}${ - opts.deleteRelationshipTuples ? deletes : '' - }, "authorization_model_id": "${modelId}"}'`; + -d '${prettyJson}'`; } case SupportedLanguage.JS_SDK: { From 5d6b97ca3079dd79cdbc9461f85feed4ddd24176 Mon Sep 17 00:00:00 2001 From: Tyler Nix Date: Wed, 10 Sep 2025 12:28:56 -0600 Subject: [PATCH 3/5] Fix linting errors in WriteRequestViewer - Replace 'any' type with proper RequestBody interface - Use eslint-disable-next-line for unused _description variables - Add proper TypeScript types for request body structure --- .../Docs/SnippetViewer/WriteRequestViewer.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/components/Docs/SnippetViewer/WriteRequestViewer.tsx b/src/components/Docs/SnippetViewer/WriteRequestViewer.tsx index ff05ee345c..e236b36ec5 100644 --- a/src/components/Docs/SnippetViewer/WriteRequestViewer.tsx +++ b/src/components/Docs/SnippetViewer/WriteRequestViewer.tsx @@ -66,11 +66,24 @@ ${ } case SupportedLanguage.CURL: { // Build the JSON object for pretty printing - const requestBody: any = {}; + interface RequestBody { + writes?: { + tuple_keys: Array>; + on_duplicate?: string; + }; + deletes?: { + tuple_keys: Array>; + on_missing?: string; + }; + authorization_model_id?: string; + } + + const requestBody: RequestBody = {}; if (opts.relationshipTuples?.length) { requestBody.writes = { tuple_keys: opts.relationshipTuples.map((tuple) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { _description, ...cleanTuple } = tuple; return cleanTuple; }), @@ -83,6 +96,7 @@ ${ if (opts.deleteRelationshipTuples?.length) { requestBody.deletes = { tuple_keys: opts.deleteRelationshipTuples.map((tuple) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { _description, ...cleanTuple } = tuple; return cleanTuple; }), From 5ec0c9dbd958d790dc446e76ef9cd3bb5c13a13b Mon Sep 17 00:00:00 2001 From: Tyler Nix Date: Wed, 10 Sep 2025 13:20:37 -0600 Subject: [PATCH 4/5] Apply manual fixes to duplicate-writes documentation - Fix error message example to match the actual tuple (writer vs reader) - Correct relation in first WriteRequestViewer example (writer vs reader) - Add line break for better formatting in 'Best effort' ignore section - Update RelatedSection description for clarity - Fix title in related links (Write API vs {ProductName} API) --- docs/content/interacting/duplicate-writes.mdx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/content/interacting/duplicate-writes.mdx b/docs/content/interacting/duplicate-writes.mdx index bc0b411bcf..2df11e4a2c 100644 --- a/docs/content/interacting/duplicate-writes.mdx +++ b/docs/content/interacting/duplicate-writes.mdx @@ -56,7 +56,7 @@ HTTP/1.1 400 Bad Request { "code": "write_failed_due_to_invalid_input", - "message": "cannot write a tuple which already exists: user: 'user:anne', relation: 'owner', object: 'document:test'..." + "message": "cannot write a tuple which already exists: user: 'user:anne', relation: 'writer', object: 'document:2025-budget': tuple to be written already existed or the tuple to be deleted did not exist" } ``` @@ -86,7 +86,7 @@ The `on_duplicate` parameter controls the behavior when writing tuples. relationshipTuples={[ { user: 'user:anne', - relation: 'reader', + relation: 'writer', object: 'document:2025-budget', }, ]} @@ -188,6 +188,7 @@ All tuples in a request will still be processed as a single atomic unit. The `ig ### "Best effort" ignore For writes: An `on_duplicate: 'ignore'` operation uses a "best effort" approach. We will attempt *once* to ignore duplicates and write non-duplicates, but if there is ever a conflict writing to the database (i.e. write a tuple we don’t think exists but it suddenly exists, probably due to a parallel request), we will abort the race condition immediately, and just return a `409 Conflict` error. These errors are rare, but can happen. + For deletes: An `on_missing: 'ignore'` operation is immune to race conditions due to database-level locks. It is not possible for another request to interfere since a delete operation will always succeed if the tuple exists or not. ### "Ignore" is Not an "Upsert" @@ -219,10 +220,10 @@ The deletes operation in the Write API does not accept a condition. Attempting t ## Related Sections Date: Wed, 10 Sep 2025 13:30:27 -0600 Subject: [PATCH 5/5] Add Duplicate Writes reference to interacting overview - Add new card for Duplicate Writes in the interacting section overview - Position it after Transactional Writes since they are related concepts - Include descriptive text about handling duplicate writes and missing deletes --- docs/content/interacting/overview.mdx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/content/interacting/overview.mdx b/docs/content/interacting/overview.mdx index 9ea513c0ae..3180d1fde8 100644 --- a/docs/content/interacting/overview.mdx +++ b/docs/content/interacting/overview.mdx @@ -47,6 +47,11 @@ This section helps you integrate