Avoid DB binary storage deadlock (#2062)

* Avoid DB binary storage deadlock

* Add changelog

* Rework
This commit is contained in:
James Agnew 2020-09-01 09:52:38 -04:00 committed by GitHub
parent b2853567f3
commit c6777578a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 33 additions and 69 deletions

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 2062
title: "A deadlock was fixed where the Database-backed Binary Storage service could lock the system up when running
under very high contention."

View File

@ -222,11 +222,6 @@ public class DaoConfig {
*/
private boolean myLastNEnabled = false;
/**
* @since 5.1.0
*/
private boolean myPreloadBlobFromInputStream = false;
/**
* Constructor
*/
@ -2102,28 +2097,11 @@ public class DaoConfig {
* </p>
*
* @since 5.1.0
* @deprecated In 5.2.0 this setting no longer does anything
*/
public boolean isPreloadBlobFromInputStream() {
return myPreloadBlobFromInputStream;
}
/**
* <p>
* This determines whether $binary-access-write operations should first load the InputStream into memory before persisting the
* contents to the database. This needs to be enabled for MS SQL Server as this DB requires that the blob size be known
* in advance.
* </p>
* <p>
* Note that this setting should be enabled with caution as it can lead to significant demands on memory.
* </p>
* <p>
* The default value for this setting is {@code false}.
* </p>
*
* @since 5.1.0
*/
@Deprecated
public void setPreloadBlobFromInputStream(Boolean thePreloadBlobFromInputStream) {
myPreloadBlobFromInputStream = thePreloadBlobFromInputStream;
// ignore
}
}

View File

@ -60,8 +60,15 @@ public class DatabaseBlobBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
private DaoConfig myDaoConfig;
@Override
@Transactional(Transactional.TxType.SUPPORTS)
@Transactional(Transactional.TxType.REQUIRED)
public StoredDetails storeBlob(IIdType theResourceId, String theBlobIdOrNull, String theContentType, InputStream theInputStream) throws IOException {
/*
* Note on transactionality: This method used to have a propagation value of SUPPORTS and then do the actual
* write in a new transaction.. I don't actually get why that was the original design, but it causes
* connection pool deadlocks under load!
*/
Date publishedDate = new Date();
HashingInputStream hashingInputStream = createHashingInputStream(theInputStream);
@ -77,32 +84,18 @@ public class DatabaseBlobBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
Session session = (Session) myEntityManager.getDelegate();
LobHelper lobHelper = session.getLobHelper();
Blob dataBlob;
if (myDaoConfig.isPreloadBlobFromInputStream()) {
byte[] loadedStream = IOUtils.toByteArray(countingInputStream);
dataBlob = lobHelper.createBlob(loadedStream);
} else {
dataBlob = lobHelper.createBlob(countingInputStream, 0);
}
Blob dataBlob = lobHelper.createBlob(loadedStream);
entity.setBlob(dataBlob);
// Save the entity
TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
txTemplate.execute(t -> {
myEntityManager.persist(entity);
return null;
});
// Update the entity with the final byte count and hash
long bytes = countingInputStream.getCount();
String hash = hashingInputStream.hash().toString();
txTemplate.execute(t -> {
myBinaryStorageEntityDao.setSize(id, (int) bytes);
myBinaryStorageEntityDao.setHash(id, hash);
return null;
});
entity.setSize((int) bytes);
entity.setHash(hash);
// Save the entity
myEntityManager.persist(entity);
return new StoredDetails()
.setBlobId(id)

View File

@ -30,14 +30,6 @@ import java.util.Optional;
public interface IBinaryStorageEntityDao extends JpaRepository<BinaryStorageEntity, String> {
@Modifying
@Query("UPDATE BinaryStorageEntity e SET e.mySize = :blob_size WHERE e.myBlobId = :blob_id")
void setSize(@Param("blob_id") String theId, @Param("blob_size") int theBytes);
@Modifying
@Query("UPDATE BinaryStorageEntity e SET e.myHash = :blob_hash WHERE e.myBlobId = :blob_id")
void setHash(@Param("blob_id") String theId, @Param("blob_hash") String theHash);
@Query("SELECT e FROM BinaryStorageEntity e WHERE e.myBlobId = :blob_id AND e.myResourceId = :resource_id")
Optional<BinaryStorageEntity> findByIdAndResourceId(@Param("blob_id") String theBlobId, @Param("resource_id") String theResourceId);

View File

@ -45,18 +45,6 @@ public class DatabaseBlobBinaryStorageSvcImplTest extends BaseJpaR4Test {
@Autowired
private DaoConfig myDaoConfig;
@BeforeEach
public void backupDaoConfig() {
defaultPreloadBlobFromInputStream = myDaoConfig.isPreloadBlobFromInputStream();
}
@AfterEach
public void restoreDaoConfig() {
myDaoConfig.setPreloadBlobFromInputStream(defaultPreloadBlobFromInputStream);
}
boolean defaultPreloadBlobFromInputStream;
@Test
public void testStoreAndRetrieve() throws IOException {
@ -74,7 +62,7 @@ public class DatabaseBlobBinaryStorageSvcImplTest extends BaseJpaR4Test {
assertEquals(0, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
assertEquals(1, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size());
assertEquals(2, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
myCaptureQueriesListener.clear();
@ -128,7 +116,7 @@ public class DatabaseBlobBinaryStorageSvcImplTest extends BaseJpaR4Test {
assertEquals(0, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
assertEquals(1, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size());
assertEquals(2, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
myCaptureQueriesListener.clear();

View File

@ -89,4 +89,12 @@ public class BinaryStorageEntity {
public String getBlobId() {
return myBlobId;
}
public void setSize(int theSize) {
mySize = theSize;
}
public void setHash(String theHash) {
myHash = theHash;
}
}