-
Couldn't load subscription status.
- Fork 43
Add recommendations on non-idempotent operations to vshard docs #5252
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: latest
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -551,6 +551,104 @@ In a router application, you can define the ``put`` function that specifies how | |||||
|
|
||||||
| Learn more at :ref:`vshard-process-requests`. | ||||||
|
|
||||||
| .. _vshard-deduplication: | ||||||
|
|
||||||
| Deduplication of non-idempotent requests | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||||
|
|
||||||
| **Idempotent requests** produce the same result every time they are executed. | ||||||
| For example, a data read request or a multiplication by one are both idempotent. | ||||||
| Therefore, incrementing by one is an example of a non-idempotent operation. | ||||||
| When such an operation is applied again, the value for the field increases by 2 instead of just 1. | ||||||
|
|
||||||
| .. note:: | ||||||
|
|
||||||
| Any write requests that are intended to be executed repeatedly (for example, retried after an error) should be idempotent. | ||||||
| The operations' idempotency ensures that the change is applied **only once**. | ||||||
|
|
||||||
| A request may need to be run again if an error occurs on the server or client side. | ||||||
| In this case: | ||||||
|
|
||||||
| - Read requests can be executed repeatedly. | ||||||
| For this purpose, :ref:`vshard.router.call() <router_api-call>` (with ``mode=read``) uses the ``request_timeout`` parameter | ||||||
| (since ``vshard`` 0.1.28). | ||||||
| It is necessary to pass the ``request_timeout`` and ``timeout`` parameters together, with the following requirement: | ||||||
|
|
||||||
| .. code-block:: text | ||||||
|
|
||||||
| timeout > request_timeout | ||||||
|
|
||||||
|
|
||||||
| For example, if ``timeout = 10`` and ``request_timeout = 2``, | ||||||
| within 10 seconds the router is able to make 5 attempts (2 seconds each) to send a request to different replicas | ||||||
| until the request finally succeeds. | ||||||
|
|
||||||
| - Write requests (:ref:`vshard.router.callrw() <router_api-callrw>`) generally **cannot be re-executed** without verifying | ||||||
| that they have not been applied before. | ||||||
| Lack of such a check might lead to duplicate records or unplanned data changes. | ||||||
|
|
||||||
| For example, a client has sent a request to the server. The client is waiting for a response within a specified timeout. | ||||||
| If the server sends a successful response after this time has elapsed, | ||||||
| the client won't see this response due to a timeout, and will consider the request as failed. | ||||||
| When re-executing this request without additional check, the operation may be applied twice. | ||||||
|
|
||||||
| A write request can be executed repeatedly without a check in two cases: | ||||||
|
|
||||||
| - The request is idempotent. | ||||||
|
|
||||||
| - It's known for sure that the previous request raised an error before executing any write operations. | ||||||
| For example, ER_READONLY was thrown by the server. | ||||||
| In this case, we know that the request couldn't complete due to server in read-only mode. | ||||||
|
|
||||||
| **Deduplication examples** | ||||||
|
|
||||||
| To ensure that the write requests (INSERT, UPDATE, UPSERT, and autoincrement) are idempotent, | ||||||
| you should implement a check that the request is applied for the first time. | ||||||
|
|
||||||
| .. note:: | ||||||
|
|
||||||
| There is no built-in deduplication check in Tarantool. | ||||||
| Currently, deduplication can be only implemented by the user in the application code. | ||||||
|
|
||||||
| For example, when you add a new tuple to a space, you can use a unique insert ID to check the request. | ||||||
| In the example below within a single transaction: | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| 1. It is checked whether a tuple with the ``key`` ID exists in the ``bands`` space. | ||||||
| 2. If there is no tuple with this ID in the space, the tuple is inserted. | ||||||
|
|
||||||
| .. code-block:: lua | ||||||
|
|
||||||
| box.begin() | ||||||
| if box.space.bands:get{key} == nil then | ||||||
| box.space.bands:insert{key, value} | ||||||
| end | ||||||
| box.commit() | ||||||
|
|
||||||
| For update and upsert requests, you can create a *deduplication space* where the request IDs will be saved. | ||||||
xuniq marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| *Deduplication space* is a user space that contains a list of unique identifiers. | ||||||
| Each identifier corresponds to one applied request. | ||||||
| This space can have any name, in the example it is called ``deduplication``. | ||||||
|
|
||||||
| In the example below, within a single transaction: | ||||||
|
|
||||||
| 1. It is checked whether the ``deduplication_key`` request ID exists in the ``deduplication`` space. | ||||||
| 2. If there is no such ID, The ID is added to the deduplication space. | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| 3. If the request hasn't been applied before, it increments the specified field in the ``bands`` space by one. | ||||||
|
|
||||||
| This approach ensures that each data modification request will be executed **only once**. | ||||||
|
|
||||||
| .. code-block:: lua | ||||||
|
|
||||||
| function update_1(deduplication_key, key) | ||||||
| box.begin() | ||||||
| if box.space.deduplication:get{deduplication_key} == nil then | ||||||
| box.space.deduplication:insert{deduplication_key} | ||||||
| box.space.bands:update(key, {{'+', 'value', 1 }}) | ||||||
| end | ||||||
| box.commit() | ||||||
| end | ||||||
|
|
||||||
|
|
||||||
| .. _vshard-maintenance: | ||||||
|
|
||||||
| Sharded cluster maintenance | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -132,6 +132,15 @@ Router public API | |||||
| * ``timeout`` — a request timeout, in seconds. If the ``router`` cannot identify a | ||||||
| shard with the specified ``bucket_id``, it will retry until the timeout is reached. | ||||||
|
|
||||||
| * ``request_timeout`` (since ``vshard`` 0.1.28) — timeout in seconds that serves as a protection against hung replicas. | ||||||
| The parameter is used in the read requests only (``mode=read``). | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| It is necessary to pass the ``request_timeout`` and ``timeout`` parameters together, with the following requirement: | ||||||
| ``timeout > request_timeout``. | ||||||
|
|
||||||
| The ``request_timeout`` parameter controls how much time a single request attempt may take. | ||||||
| When this time is over (the ``TimedOut`` error is raised), the router retries this request on the next replica as long | ||||||
| as the ``timeout`` value is not elapsed. | ||||||
|
|
||||||
| * other :ref:`net.box options <net_box-options>`, such as ``is_async``, | ||||||
| ``buffer``, ``on_push`` are also supported. | ||||||
|
|
||||||
|
|
@@ -163,6 +172,16 @@ Router public API | |||||
| optional attribute containing a message with the human-readable error description, | ||||||
| and other attributes specific for the error code. | ||||||
|
|
||||||
| .. reference_vshard_note_start | ||||||
|
|
||||||
| .. note:: | ||||||
|
|
||||||
| Any write requests that are intended to be executed repeatedly (for example, retried after an error) should be idempotent. | ||||||
| The operations' idempotency ensures that the change is applied **only once**. | ||||||
| Read more: :ref:`<vshard-deduplication>`. | ||||||
|
|
||||||
| .. reference_vshard_note_end | ||||||
|
|
||||||
| **Examples:** | ||||||
|
|
||||||
| To call ``customer_add`` function from ``vshard/example``, say: | ||||||
|
|
@@ -199,6 +218,13 @@ Router public API | |||||
| * ``timeout`` — a request timeout, in seconds.If the ``router`` cannot identify a | ||||||
| shard with the specified ``bucket_id``, it will retry until the timeout is reached. | ||||||
|
|
||||||
| * ``request_timeout`` (since ``vshard`` 0.1.28) — timeout in seconds that serves as a protection against hung replicas. | ||||||
| It is necessary to pass the ``request_timeout`` and ``timeout`` parameters together, with the following requirement: | ||||||
| ``timeout > request_timeout``. | ||||||
| The ``request_timeout`` parameter controls how much time a single request attempt may take. | ||||||
| When this time is over (the ``TimedOut`` error is raised), the router retries this request on the next replica as long | ||||||
| as the ``timeout`` value is not elapsed. | ||||||
|
|
||||||
| * other :ref:`net.box options <net_box-options>`, such as ``is_async``, | ||||||
| ``buffer``, ``on_push`` are also supported. | ||||||
|
|
||||||
|
|
@@ -248,6 +274,10 @@ Router public API | |||||
| optional attribute containing a message with the human-readable error description, | ||||||
| and other attributes specific for this error code. | ||||||
|
|
||||||
| .. include:: /reference/reference_rock/vshard/vshard_router.rst | ||||||
| :start-after: reference_vshard_note_start | ||||||
| :end-before: reference_vshard_note_end | ||||||
|
|
||||||
| .. _router_api-callre: | ||||||
|
|
||||||
| .. function:: vshard.router.callre(bucket_id, function_name, {argument_list}, {options}) | ||||||
|
|
@@ -267,6 +297,13 @@ Router public API | |||||
| * ``timeout`` — a request timeout, in seconds. If the ``router`` cannot identify a | ||||||
| shard with the specified ``bucket_id``, it will retry until the timeout is reached. | ||||||
|
|
||||||
| * ``request_timeout`` (since ``vshard`` 0.1.28) — timeout in seconds that serves as a protection against hung replicas. | ||||||
| It is necessary to pass the ``request_timeout`` and ``timeout`` parameters together, with the following requirement: | ||||||
| ``timeout > request_timeout``. | ||||||
| The ``request_timeout`` parameter controls how much time a single request attempt may take. | ||||||
| When this time is over (the ``TimedOut`` error is raised), the router retries this request on the next replica as long | ||||||
| as the ``timeout`` value is not elapsed. | ||||||
|
|
||||||
| * other :ref:`net.box options <net_box-options>`, such as ``is_async``, | ||||||
| ``buffer``, ``on_push`` are also supported. | ||||||
|
|
||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.