[Concurrency Control] Locking, MVCC, and Message Queue
1. Definition of Concurrency Control
Concurrency Control is a technique used by database systems to allow multiple transactions to execute at the same time while maintaining data consistency and integrity, instead of executing transactions in sequence. It is one of the most important concepts in database systems.
2. Purpose of Concurrency Control
To maintain data consistency and integrity while allowing multiple users to access the database.
To maintain database system performance and efficiency.
Lost Update: occurs when two transactions try to update the same data at the same time, resulting in one operation not being performed.
Inconsistency: occurs when two transactions simultaneously update the same data, resulting in a state that does not match the user's intended result.
Cascading Rollback: occurs when one transaction fails during the process of two transactions attempting to update the same data, resulting in both transactions being rolled back due to atomicity.
Uncommitted Dependency: occurs when one transaction fails, and another transaction refers to the failed result before the transaction is recovered.
3. Methods of Concurrency Control
3-1. Locking
What is Locking?
Locking is a traditional method of controlling concurrent access to shared resources.
It only operates in a single thread or process, allowing only one thread to access the shared resource at a time.
It is reliable and safe, and depending on the level of locking applied, it can prevent problems such as deadlock or race condition.
However, it can slow down concurrency processing speed and cause waiting times.
It uses lock and unlock operations as the basis.
Types of Locking
Shared lock (s-lock): used when reading data.
A transaction with a shared lock can only perform read operations on the data item.
- If T1 sets an S-lock for x, T1 can only perform read(x) operations.
Multiple shared locks (S-locks) can be set for one data item.
- If T1 sets an S-lock for x, T2 can also set an S-lock for x at the same time.
Other transactions can also perform read operations.
- If T1 sets an S-lock for x, T2 can only perform read(x) operations while T1 is executing S-lock(x).
Exclusive lock (x-lock): used when modifying data.
A transaction with an exclusive lock can perform both read and write operations on the data item.
- If T1 sets an S-lock for x, T1 can perform both read(x) and write(x) operations.
Only one exclusive lock (x-lock) can be set for one data item.
- If T1 sets an X-lock for x, T2 cannot set an X-lock for x until T1 unlocks(x).
No other transactions can perform read or write operations.
- If T1 sets an X-lock for x, T2 cannot perform read(x) or write(x) operations until T1 unlocks(x).
Additional) Deadlock: a situation where all transactions are in a waiting state, and no progress is made. If deadlock occurs, the transaction must be forcibly aborted from the outside, or the lock must be released.
Concurrency Control Techniques using Locking
Optimistic lock: a concurrency control technique used when the probability of collision is low.
If a collision occurs, resolve it through retrying or merging.
It does not use locks directly to preempt resources, but rather uses a version to synchronize.
When reading data, it does not use locks, but when updating, it checks whether the updated version matches the version that it read.
In other words, it does not directly preempt resources by locking the resource, but instead processes collisions when actual concurrency issues occurs.
Pessimistic lock: a concurrency control technique used when the probability of collisions is high.
It uses a lock on the data to preempt access to the data by other users and control access only by threads with locks.
It ensures exclusive access rights to data and prevents collisions.
It is actually a method of synchronizing the resource by locking the data, so if a concurrency problem arises, the application area must address it.
It acquires an s-lock or x-lock at the start of a transaction.
Optimistic Locking | Pessimistic Locking | |
Advantages | Does not require transactions or explicit locks, resulting in better performance. | If concurrency issues occur frequently, it can reduce the number of rollbacks, resulting in improved performance. |
Disadvantages | If concurrency issues occur frequently, constant rollback processing is required, and developers need to implement retry logic for failed updates. | Locks are used for every transaction, even when not necessary, which can negatively impact performance. This can be a disadvantage especially in situations with frequent read operations. Additionally, in cases where locks are applied to multiple tables and resources are needed by each other, deadlocks can occur, which cannot be resolved by pessimistic locking. |
Distributed lock: used to control concurrent access to shared resources between multiple computers or processes.
It is used to solve concurrency issues in a distributed system and requires interaction between distributed servers or clusters.
It is mainly used in distributed systems such as databases or message queues.
Common distributed locks include ZooKeeper and Redis.
Redis proposes the RedLock algorithm as a distributed lock to guarantee three properties.
Only one worker is allowed to lock it at a time.
If a lock is not released due to any problem, another worker can acquire the lock.
All workers can acquire and release a lock as long as Redis nodes are functioning.
To implement a distributed lock, lock information must be stored in Redis, and multiple servers in the distributed environment look at the common Redis to check whether they can access the resource.
Spinning lock: waits repeatedly in an infinite loop while checking resource access, waiting until another thread releases the lock.
It is effective when there is a short race condition (a situation where two or more processes access a shared resource at the same time) and the resource holding time is not long.
It is mainly used in multi-core systems, and since locking requires continuous use of the CPU, it can cause a lot of burden on the server.
3-2. MVCC (Multi-Version Concurrency Control)
Background of MVCC: Limitations of Locking
When locking a resource, other users were restricted from controlling the resource until the user who had pulled the lock released it.
If an s-lock was set for a data item, read operations could be performed, but write operations could not be performed by other transactions.
If an x-lock was set for a data item, neither read nor write operations can be performed by other transactions.
MVCC was created to address the limitations of locking.
What is MVCC?
It means that multiple versions of a single record are managed.
In other words, when updating data, a new version of the data is created instead of overwriting the existing content.
Versioning: manages the version of the data each transaction reads or modifies, thus maintaining the state of the data at the time the transaction started.
Snapshot Isolation: transactions run concurrently but access the data using isolated snapshots. Since each transaction uses the data snapshot at the time it started, it is not affected by changes made to the data by other transactions.
Features
The approach of MVCC does not require preemption of resources using locks, so it operates much faster than general RDBMS.
Once you start reading data, you can use the data even if someone else deletes or modifies the data at the same time.
However, unused data continues to accumulate, so a system for organizing the data is needed.
Since the MVCC model allows multiple versions of data for a single piece of data, data versions can collide, so the application area must address these issues.
Overhead such as UNDO block I/O, CR copy generation, and CR block caching occurs.
3-3. BullQueue
What is BullQueue?
BullQueue is a distributed job queue based on Redis, provided by Nest.js. It allows you to handle and manage jobs in a distributed environment.
It utilizes the Queue Design Pattern to solve concurrency issues.
It enables you to add asynchronous jobs to the queue and process them. The jobs are executed in the background, and the queue follows the FIFO (First-In-First-Out) order for job processing.
Nest.js provides the
@nestjs/bull
package for working with BullQueue.Job Management: You can add jobs to the queue, track their status, and execute or complete the jobs. Jobs are executed asynchronously, and you can handle the results or status changes through events.
Job Prioritization: Jobs can be assigned priorities, allowing for different processing order in the queue. This helps ensure that important jobs are processed first.
Job Retry and Failure Handling: BullQueue supports job retries and failure handling. You can configure the retry count and failure handling strategy, ensuring error resilience and reliable job processing.
Job Status Monitoring: BullQueue provides functionality to monitor and track the status of jobs. You can check the progress of jobs and determine if they have completed or failed.
Reasons for Choosing BullQueue
Flexible Job Queue and Processing: BullQueue allows you to add jobs to a queue and process them using worker processes, providing flexibility in asynchronous job processing.
Concurrency Control in Distributed Environments: In situations where performance improvements require a distributed server environment, BullQueue allows sharing and distributed processing of jobs across multiple servers or clusters.
State Management and Event Notifications: BullQueue provides job state management and event notifications. You can monitor the progress of jobs and receive notifications for job completion, errors, or other state changes. This allows real-time tracking of job status and taking necessary actions.
Additional Note: Apache Kafka: Real-time data processing and distributed streaming platform (microservice)
\=> Concurrency control refers to controlling and synchronizing tasks that are executed simultaneously between services in Microservices Architecture (MSA). Kafka itself is not designed as a tool specifically for concurrency control. Kafka is a distributed data streaming platform used for reliable data transmission and storage of large volumes of data. To achieve concurrency control, other mechanisms and tools need to be used. Kafka enables the quick transmission and processing of data between different applications, facilitating the implementation of event-driven architectures and enabling loose coupling and scalability between services.
4. Concurrency Control Testing and Monitoring Tool
Artillery
Reasons for Choosing Artillery
JavaScript-based: Artillery uses JavaScript-based scripts for performance testing.
Real-time Monitoring and Result Analysis: Art
Artillery provides real-time monitoring information during the test and allows real-time analysis of the results. It enables monitoring performance metrics while performing concurrency processing, allowing adjustments to the test or quick identification of performance issues. 3. Flexibility and Scalability: Artillery supports various concurrency scenarios and provides flexibility in writing test scripts. It allows simulating different concurrent user counts, request patterns, and scenarios to effectively evaluate an application's concurrency handling capabilities.
Alternative Option: Apache JMeter
- JMeter is a powerful performance testing tool that supports various protocols and features. It offers features such as testing scenarios, request types, data manipulation and extraction, and report generation. If you require support for various protocols or need to handle complex testing scenarios, JMeter may be more suitable. However, for our concurrency control testing, which does not involve highly complex scenarios, using a lightweight monitoring tool like Artillery is considered appropriate.