August 19, 2024 in Software Development Practices

Defusing Concurrency Landmines: Crafting Resilient Frontend-Backend Confirmation Protocols

Defusing Concurrency Landmines: Crafting Resilient Frontend-Backend Confirmation Protocols

Ever faced a tricky situation, needing to prompt users with a confirmation message about potential side effects? Imagine you’re deep in the code, striving to ensure a smooth user experience. But lurking in the shadows are concurrency issues, ready to disrupt the flow between your backend operations and the frontend interface. Let’s dive into this world where timing is crucial, and ensuring seamless interactions becomes our top priority.

Imagine the scenario your business approaches you with a common request — they need confirmation messages implemented before any action that could have side effects. Let’s say you’re working with an API to schedule meetings. If a new meeting conflicts with an existing one, it gets canceled, but only after user confirmation. You set up two APIs: one to check for conflicts and another to execute the action. All seems well, right?

But here’s the catch: there’s no time limit on user confirmation. They might confirm after the system status changes, leading to outdated actions being confirmed.

Picture this you’re already scheduled to meet with an employee, and you’re attempting to set up another meeting conflicting with employee’s meeting. A confirmation message appears, warning you about the conflicting meeting (employee meeting) that would be canceled. Before you can confirm, the system updates, swapping the employee meeting with one involving the CEO. Unaware of this change, you proceed and confirm the cancellation of conflicting meetings, unintentionally canceling the CEO meeting. The consequences? Potentially catastrophic — you may find yourself fired-

Outdated confirmation

After encountering this situation numerous times, our team engaged in discussions to devise a solution. We settled on employing optimistic concurrency control, relying on the principle of “Detect changes in premise, retry execution.”

Here’s how our solution works: In the API responsible for performing the action, we introduced a new parameter to accept the IDs of meetings confirmed for cancellation. The frontend stores the IDs of conflicting meetings when it calls first API and prompt the confirmation message.

When the action API is called, it checks if the status has changed since the confirmation message was triggered. If another meeting conflict arises or the status has changed, we raise an error signaling unconfirmed changes and reject the action.

Here’s a snippet of our Ruby implementation:

In the controller:

def create
Meeting.create!(conflicting_resources_ids: params[:conflicting_resources_ids])
end

In the model:

attr_accessor :conflicting_resources_ids

validate :conflicting_resources_confirmed?, on: :create

def conflicting_resources_confirmed?
  return if get_conflicts().pluck(:id) == conflicting_resources_ids

  errors.add :base, 'stale_update'
end

This approach ensures that actions are only executed when the system’s state aligns with the user’s intentions, mitigating the risk of inadvertently carrying out outdated or erroneous changes.

In “Designing Data-Intensive Applications”, the debate over optimistic concurrency control is acknowledged as an ongoing discussion. While this approach has its advantages, it faces challenges in high contention scenarios, where the likelihood of transactions needing to be aborted increases significantly. When the system operates near maximum throughput, the increased volume of retried transactions can worsen performance.



By browsing this website, you agree to our privacy policy.
I Agree