Avoiding Race Conditions and Data Loss When Autosaving in React-Query

Irina Kudryavtseva • Jun 10, 2024

Auto-saving is a common feature in many popular web applications like Gmail. Although it is very convenient for users it introduces certain challenges that developers and designers must address to ensure a seamless user experience.


For instance, when implementing an autosave feature, it's crucial to manage user expectations by providing clear feedback about when data is being saved and ensuring that there is no data loss during the save process. When it works, it can make your application feel natural and seamless, but if auto-save breaks (especially without notifying the user) it can destroy the user’s trust in your application.

 

Depending on the chosen technology, state management and app architecture, developers may face different issues with autosaving. But for simplicity of this discussion, let’s say we have a React app which uses react-query for state management, data fetching, caching, and background updates. We also have a number of controlled React components on a page, and every time a user interacts with this page, the same mutation request with a different payload representing the latest “state” that the user has entered or selected is made to update one database entity on the back end. 

Pic 1. Client-server interaction while autosaving

Impossible to lose any data in this simple setup, you say, but here we are.

 

As the user enters some precious information on the form, by checking checkboxes, selecting items from dropdowns or performing any other mutating activities, API requests get fired with new information, updating the same database entity. Due to the asynchronous nature of HTTP requests, we cannot guarantee when and in which order these API calls will arrive at the destination. If the requests arrived at the server out-of-order, the information entered by the user last would be overridden by the API call which arrived last, or if the API has been written with some kind of optimistic concurrency checking an error will be raised and some data changes will be lost. It’s also possible that the requests are received in the correct order, but the processing time of each request varies, and the net result is the same as the “out-of-order” scenario: the final state that is saved in the database is not the most recent data that the user entered.

 

Another risk is of the requests arriving at the same time and attempting to modify the same entity simultaneously. This is bad, no one wishes for a race condition in their system. In this case one of the mutations would never be completed due to an exception. All these errors will be transient and may be hard to reproduce.

Pic 2. Autosaving with requests arriving out-of-order and/or failing due to concurrency update exception

One way to address this issue would be to cancel the “in flight” mutation requests and issue a new one. React Query supports query cancellation, but not cancellation of mutations. This is for good architectural reasons - although simple APIs could process and handle the cancellation correctly it may not receive the cancellation until processing is complete. More complex APIs that can’t execute as a single atomic operation (and thus can’t be rolled back) couldn’t be cancelled properly, and the react-query developers wisely decided that supporting mutation cancellation would set an expectation that their front-end library could not deliver on.

 

To avoid data being written by out-of-order request processing and concurrency exceptions from race conditions, we decided to use queues to manage the order in which the requests are executed. This approach involves pushing requests to a queue and processing them one at a time, thereby eliminating the risk of race conditions. After some refinement, this solution moulded into the following reusable custom hook useQueuedMutation:

This hook uses the same signature as useMutation from react-query for ease of use. Internally the useQueuedMutation hook adds a mutation to a queue when the queue method is called, performing the optimistic update straight away and also filtering out the mutations in the queue which did not start execution yet (isInFlight for them is false). When there are no in-flight mutations, the next mutation in line will start its execution. When the mutation is complete, it gets removed from the queue. This means the queue will at most have 1 pending mutation queued up. New incoming mutation calls will supersede queued ones, which will be evicted from the queue.

 

This approach guarantees the FIFO order of the request execution, eliminates exceptions due to concurrent entity updates and reduces load on the server by reducing the number of API requests. This makes our autosaving feature robust with no data loss in the process.

Pc 3. Autosaving with managed requests queue

What happens if the user navigates away while a mutation is queued up? If the navigation is within the same React application the queued mutation will still be executed. If the user is closing the browser window or navigating to a URL outside of the current React application there is the potential for data loss. Handling these scenarios will be covered in a subsequent blog post.

 

Another approach for addressing these issues is debouncing the mutation requests. Although typically used to prevent time consuming tasks from being triggered too frequently, debouncing can also be used here to hopefully prevent these kinds of concurrency and out-of-order execution issues. I choose the word hopefully, because depending on the debouncing interval you choose they could still occur. A larger interval reduces the chance of the problem, but also reduces the “live”-ness of the user’s data and increases the amount of data that would be lost if the current state was unable to be saved. We also wanted to use React Query features like optimistic updates and using React Query controlled models as our component state, and switching to a debouncing strategy would have removed our ability to do this. Debouncing is still worth investigating if you don’t have these constraints.

 

Another approach which we intend to investigate further is using WebSockets for mutations in conjunction with react-query. WebSockets offer in-order, exactly-once delivery guarantees which HTTP does not. This would remove one of the reasons for requiring mutations to be queued, since they will always be processed in order. It would still be theoretically possible for issues if one mutation request was processed much more slowly than others. Queued updates also potentially reduce server load if mutations are slow, by triggering less of them.

 

Hope you enjoyed reading!

Share This Post

Get In Touch

Recent Posts

01 Sep, 2024
Congratulations to three of our team members for being selected as finalists in the ARN Women in ICT Awards 2024. Recognised for their achievements and contributions within Patient Zero, our finalists are: Bay McGovern - Shining Star Demelza Green - Innovation Weasley Au - Graduate “This is a stunning display of emerging and established female talent in Australia,” said ARN Editor Julia Talevski. “This year’s finalists have set an extremely high bar and are a source of inspiration for women leading the way in technology — we are proud and privileged to be celebrating each and every one of them.” WIICTA 2024 will honour the channel across eight categories, spanning Innovation, Technical, Entrepreneur, Graduate, Rising Star, Shining Star, Achievement, and DE&I Individual Champion awards. In response to a wealth of standout submissions, specific categories have been divided to best acknowledge and highlight the depth of female talent in the Australian market. The winners will be announced on September 19th at the prestigious event set to take place at Doltone House in Jones Bay Wharf Sydney. For more information on the ARN Women in ICT Awards 2024, visit the official ARN announcement here .
By Irina Kudryavtseva 10 Jun, 2024
Auto-saving is a common feature in many popular web applications like Gmail. Although it is very convenient for users it introduces certain challenges that developers and designers must address to ensure a seamless user experience.  For instance, when implementing an autosave feature, it's crucial to manage user expectations by providing clear feedback about when data is being saved and ensuring that there is no data loss during the save process. When it works, it can make your application feel natural and seamless, but if auto-save breaks (especially without notifying the user) it can destroy the user’s trust in your application. Depending on the chosen technology, state management and app architecture, developers may face different issues with autosaving. But for simplicity of this discussion, let’s say we have a React app which uses react-query for state management, data fetching, caching, and background updates. We also have a number of controlled React components on a page, and every time a user interacts with this page, the same mutation request with a different payload representing the latest “state” that the user has entered or selected is made to update one database entity on the back end.
By Demelza Green 27 Feb, 2024
With the advent of ChatGPT, Bard/Gemini and Co-pilot, Generative AI, and Large Language Models (LLMs) have been thrust into the spotlight. AI is set to disrupt all industries, especially those that are predominately based on administrative support, legal, business, and financial operations, much like insurance and financial organisations.
By Joe Cooney 22 Feb, 2024
One of the features of life working at PZ is our brown bag lunch and learn sessions; presentations by staff on topics of interest – sometimes, but not always technical, and hopefully amusing-as-hell. Yesterday we took a break from discussing the book Accelerate and the DORA metrics to take a whirlwind tour of the current state of play running “open source” generative AI models locally. Although this talk had been ‘in the works’ for a while, one challenge was that it needed to constantly be revised as the state of AI and LLMs changed. For example, the Stable Video Diffusion examples looked kind of lame in comparison to OpenAI’s Sora videos (released less than a week ago) and Groq’s amazing 500 token-per-second hardware demo on Monday/Tuesday , and the massive context size available now in the Gemini 1.5 models (released a few hours before OpenAI announced Sora...coincidence? An effort by OpenAI to steal back the limelight! Surely NOT!). And now a day later, with the paint still drying on a highly amusing slide-deck for the talk, Google releases their “open-source" Gemma models! The day itself presented an excellent example of why having more control of your models might be a good thing. ChatGPT 4 users began reporting “crazy” and highly amusing responses to fairly normal questions . We became alerted to this when one of our own staff reported on our internal Slack about a crazy response she received to a question about the pros and cons of some API design choices. The response she got back started normally enough, but then began to seem to channel Shakespeare’s Macbeth and some other olde English phrases and finished thusly. "Choose the right charm from the box* dense or astray, it’ll call for the norm. Your batch is yours to halter or belt. When in fetch, marry the clue to the pintle, and for the after, the wood-wand’s twist'll warn it. A past to wend and a feathered rite to tend. May the gulch be bygones and the wrath eased. So set your content to the cast, with the seal, a string or trove, well-deep. A good script to set a good cast. Good health and steady wind!" The sample JSON payload was also in keeping with the rest of the answer. { "htmlContent": "

Your HTML here

", "metadata": { "modifiedBy": "witch-of-the-wood", "safety": "sanitized", "mood": "lunar" } } Hubble, bubble, toil and trouble. Although there were no reports of the GPT4 API being affected by this (only ChatGPT) it might have given people developing automated stock trading bots using GPT4 a reason to pause and contemplate what might have been if their stock portfolio now consisted of a massive long position on Griselda’s Cauldron Supplies. As ChatGPT would say, Good health and steady wind.
More Posts
Share by: