Brian Shaughnessy (lcdweb), who has been working with the New York State Senate's CiviCRM project, recently raised the issue of simultaneous editing: What happens when two users simultaneously make changes to same contact record? We've held a few discussions on IRC to examine the issue and draft some solutions. We would appreciate further feedback and ideas on how to address the issue.
As described by Brian:
The issue is that if you and I get into the same record around the same time, and you save the record first, when I save, it will overwrite your changes. So let’s say you add a middle name, and I get in and add a birthdate. The data is not in conflict, and both changes should be preserved, but they aren’t -- when you save the birthdate (where the edit form was loaded before I saved the middle name), the empty middle name overwrites the value I had saved.
The most common solution (used by Drupal, Joomla, and many other systems) is called record-locking. In this approach, the first user will be allowed to make her changes -- and the second user will see an error indicating that a 'lock' on the record prevents his changes. To implement record-locking, we must make a few decisions.
In some systems (such as the Java Persistence API), locking can be easily configured for any entity or table stored in the database. This can work well if all of the "Edit" screens are designed with an understanding of the locks, but fine-grained locking can create more corner-cases: what happens when one user locks on a phone number record (eg using inline "Edit" blocks) while a second user locks an email (eg using "Batch Update") and a third user tries to obtain locks on the street and email and phone (eg using "Edit Contact")?
For an initial implementation, we propose to enable coarse-grained locking for the primary elements of a contact -- the first name, last name, demographics, phone numbers, email addresses, and street addresses. In future releases, locking can be enabled for events or other high-level entities other high-level entities.
Decision: Optimsitic or Pessimistic
Drupal and Joomla demonstrate slightly different variations on locking:
- Optimistic Locking: In Drupal, two simultaneous users who edit the same web page will both be allowed to see the "Edit" screen and begin making changes -- under the optimistic assumption that only one of them will actually <em>save</em> the changes. The first user who hits "Save" will succeed -- the second user to hit "Save" will get the error (and feel a pang of disappoinment because his changes cannot be saved).
- Pessimistic Locking: In Joomla, a web page can be edited by only one user a time -- if a second user tries, he will immediately see an error. This spares the second user from the disappointment of unsavable changes, but it comes with a different downside: if anything goes wrong for the first user (eg he walks away or suffers a computer crash), then the record will be stuck -- locked and uneditable by others. (The record won't be stuck permanently -- after a few minutes, the lock can timeout.)
Decision: Legacy Code
Any locking solution requires extra logic to detect locks, display errors, etc. We can add this logic to several important screens (like "Edit Contact"), but practical considerations will require leaving other code unchanged. (For example, any scripts that call the API are currently written with the assumption that there are no locks.) So what happens when legacy code tries to modify an otherwise locked record?
For optimistic locking, there is basically one option: let the edit go through. For pessimistic locking, three options:
- Ignore the lock: Any change made by the legacy code will go through immediately, regardless of whether someone else has a lock.
- Steal the lock: Any change made by the legacy code will go through immediately. If someone else has a lock, they will lose it and be unable to save changes.
- Fail if locked: The legacy code will receive an error (eg API error or PHP exception) telling it that the change could not be saved.
Lock functionality will be a new addition to CiviCRM -- which means that some administrators may not be familiar with it, subtle bugs or performance issues may arise in the initial implementation, etc.
For the first release, we may wish to treat "locking" as an optional feature which can be enabled or disabled by administrators. Alternatively, we could go all-in and activate locking on all installations.
An issue with more specific ideas and steps has been filed as CRM-10553.