JDBC Lock acquire optimization
Thanks to the pair of `CLIENT_ID`/`CREATED_DATE`,
it is possible to know if the lock is expired, already held or can be reacquire.
I think that the pre delete row is unnecessary and redundant with these two columns.
The rows doesn't need to be purged because the number of row in the table is finite and equal to the number of locks.
At worst, the table will be able to be purged by `deleteExpired()`.
I propose to overwrite these two columns at acquire lock time instead of pre delete the row.
So `delete` then `update` or `insert` become `update` or `insert`.
One client held a lock.
- Another client will be able to hold the same lock only if:
1. the row doesn't exist. Done when the first client will call `unlock()`.
2. the lock expire in case of hardware shutdown. Done by "`OR CREATED_DATE<?`" in `updateQuery`.
- The same client will be able to reacquire it only if:
1. the row doesn't exist.
2. the lock is already held by him. Done by "`CLIENT_ID=? OR`" in `updateQuery`.
**Cost with PostgreSQL:**
```
EXPLAIN DELETE FROM INT_LOCK WHERE REGION='DEFAULT' AND LOCK_KEY='FOO' AND CREATED_DATE<'2020-07-14';
Delete on int_lock (cost=0.14..8.17 rows=1 width=6)
-> Index Scan using int_lock_pk on int_lock (cost=0.14..8.17 rows=1 width=6)
Index Cond: (((region)::text = 'DEFAULT'::text) AND (lock_key = 'FOO'::bpchar))
Filter: (created_date < '2020-07-14 00:00:00'::timestamp without time zone)
EXPLAIN UPDATE INT_LOCK SET CREATED_DATE='2020-07-15' WHERE REGION='DEFAULT' AND LOCK_KEY='FOO' AND CLIENT_ID=NULL;
Update on int_lock (cost=0.00..11.40 rows=1 width=520)
-> Result (cost=0.00..11.40 rows=1 width=520)
One-Time Filter: false
-> Seq Scan on int_lock (cost=0.00..11.40 rows=1 width=520)
```
```
EXPLAIN UPDATE INT_LOCK SET CLIENT_ID=NULL, CREATED_DATE='2020-07-15' WHERE REGION='DEFAULT' AND LOCK_KEY='FOO' AND (CLIENT_ID=NULL OR CREATED_DATE<'2020-07-14')
Update on int_lock (cost=0.14..8.17 rows=1 width=372)
-> Index Scan using int_lock_pk on int_lock (cost=0.14..8.17 rows=1 width=372)
Index Cond: (((region)::text = 'DEFAULT'::text) AND (lock_key = 'FOO'::bpchar))
Filter: (created_date < '2020-07-14 00:00:00'::timestamp without time zone)
```
This commit is contained in:
committed by
Artem Bilan
parent
9c252be028
commit
e4611d97a1
@@ -36,10 +36,13 @@ import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
* @author Artem Bilan
|
||||
* @author Stefan Vassilev
|
||||
* @author Alexandre Strubel
|
||||
*
|
||||
* @since 4.3
|
||||
*/
|
||||
@@ -55,6 +58,9 @@ public class JdbcLockRegistryTests {
|
||||
@Autowired
|
||||
private LockRepository client;
|
||||
|
||||
@Autowired
|
||||
private DataSource dataSource;
|
||||
|
||||
@BeforeEach
|
||||
public void clear() {
|
||||
this.registry.expireUnusedOlderThan(0);
|
||||
@@ -127,6 +133,26 @@ public class JdbcLockRegistryTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReentrantLockAfterExpiration() throws Exception {
|
||||
DefaultLockRepository client = new DefaultLockRepository(dataSource);
|
||||
client.setTimeToLive(1);
|
||||
client.afterPropertiesSet();
|
||||
JdbcLockRegistry registry = new JdbcLockRegistry(client);
|
||||
Lock lock1 = registry.obtain("foo");
|
||||
assertThat(lock1.tryLock()).isTrue();
|
||||
Thread.sleep(100);
|
||||
try {
|
||||
Lock lock2 = registry.obtain("foo");
|
||||
assertThat(lock2).isSameAs(lock1);
|
||||
assertThat(lock2.tryLock()).isTrue();
|
||||
lock2.unlock();
|
||||
}
|
||||
finally {
|
||||
lock1.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoLocks() throws Exception {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
|
||||
Reference in New Issue
Block a user