Repository navigation
Improve concurrency in TaskQueue - #17828
Conversation
gianm
left a comment
There was a problem hiding this comment.
LGTM, feel free to merge after considering the comment about the name of the giant lock.
| * queue management is in progress. {@link #start} and {@link #stop} methods | ||
| * acquire a WRITE lock whereas all other operations acquire a READ lock. | ||
| */ | ||
| private final ReentrantReadWriteLock giant = new ReentrantReadWriteLock(true); |
There was a problem hiding this comment.
Consider renaming to startStopLock, since its main purpose now is to synchronize start and stop with each other and with the other operations.
There was a problem hiding this comment.
Updated. Thanks for the review.
| public void stop() | ||
| { | ||
| giant.lock(); | ||
| giant.writeLock().lock(); |
There was a problem hiding this comment.
It would be good we we add tests triggering this behavior ie
one threads is hitting start/stop method while the other is trying to insert tasks to activeTasks
There was a problem hiding this comment.
Added TaskQueueConcurrencyTest.
| final List<Task> tasks; | ||
| if (taskQueue.isPresent()) { | ||
| tasks = taskQueue.get().getActiveTasksForDatasource(dataSource); | ||
| return taskQueue.get().getActiveTasksForDatasource(dataSource); |
There was a problem hiding this comment.
We can probably remove line 4296 and 4298 and fold them inside the else at 4301. The resulting method would be much leaner.
|
@maytasm , this PR needs some more tests. I will add them soon and get the PR merged. |
Description
The giant lock in TaskQueue is acquired when performing any update or read operation, such as:
add a task
remove a task
read the list of tasks
sync from storage
On large clusters with several concurrent tasks, this can become a bottleneck.
This patch attempts to improve the concurrency in TaskQueue by using a concurrent hash map instead.
Changes
Use a ConcurrentHashMap to track active tasks. The giant lock was needed only to handle
competing updates made to the same task ID. This can be handled by a ConcurrentHashMap too.
Perform any update on the entry for a task ID within ConcurrentHashMap.compute to ensure atomicity
Convert the giant lock from a ReentrantLock to a ReentrantReadWriteLock
Repurpose giant lock to ensure that the TaskQueue start and stop is mutually exclusive from any other
normal operation on the TaskQueue
Keep a lastUpdatedTime in every TaskEntry to handle race conditions when syncing from metadata store
Handling race conditions
The only possible race conditions are with syncFromStorage(),
and are handled by maintaining a lastUpdatedTime for every task entry.
All updates to a task entry happen inside a ConcurrentHashMap.compute(taskId, entry -> update(entry))
and are thus thread-safe.
[A] Sync should not add a task to queue if it has just been removed
This is handled by ensuring that only the syncFromStorage() method can remove tasks from the queue.
A task that completed after the poll started would have a lastUpdatedTime after syncStartTime.
In this case, since an entry already exists in the queue, a new one will not be added.
The next invocation of syncFromStorage() will add the task, if necessary.
[B] Sync should not remove a task from queue if it has just been added
A task added after the poll started would have a lastUpdatedTime after syncStartTime.
In this case, the entry will not be removed.
The next invocation of syncFromStorage() will clean up the task, if necessary.
Description
The
giantlock inTaskQueueis acquired when performing any update or read operation, such as:On large clusters with several concurrent tasks, this can become a bottleneck.
This patch attempts to improve the concurrency in
TaskQueueby using a concurrent hash map instead.Changes
ConcurrentHashMapto track active tasks. The giant lock was needed only to handlecompeting updates made to the same task ID. This can be handled by a
ConcurrentHashMaptoo.ConcurrentHashMap.computeto ensure atomicitygiantlock from aReentrantLockto aReentrantReadWriteLockgiantlock to ensure that theTaskQueuestartandstopis mutually exclusive from any othernormal operation on the
TaskQueuelastUpdatedTimein everyTaskEntryto handle race conditions when syncing from metadata storeHandling race conditions
The only possible race conditions are with
syncFromStorage(),and are handled by maintaining a
lastUpdatedTimefor every task entry.All updates to a task entry happen inside a
ConcurrentHashMap.compute(taskId, entry -> update(entry))and are thus thread-safe.
[A] Sync should not add a task to queue if it has just been removed
This is handled by ensuring that only the
syncFromStorage()method can remove tasks from the queue.A task that completed after the poll started would have a
lastUpdatedTimeaftersyncStartTime.In this case, since an entry already exists in the queue, a new one will not be added.
The next invocation of
syncFromStorage()will add the task, if necessary.[B] Sync should not remove a task from queue if it has just been added
A task added after the poll started would have a
lastUpdatedTimeaftersyncStartTime.In this case, the entry will not be removed.
The next invocation of
syncFromStorage()will clean up the task, if necessary.Pending
This PR has: