mirror of
https://github.com/corda/corda.git
synced 2025-02-06 11:09:18 +00:00
ENT-2848 More contract attachment caching to avoid database queries slowing the node down (#4433)
This commit is contained in:
parent
9d8618224a
commit
a4037b374d
@ -78,12 +78,21 @@ interface AttachmentStorage {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the Attachment Id of the contract attachment with the highest version for a given contract class name
|
* Find the Attachment Id of the contract attachment with the highest version for a given contract class name
|
||||||
* from trusted upload sources.
|
* from trusted upload sources. If both a signed and unsigned attachment exist, prefer the signed one.
|
||||||
*
|
*
|
||||||
* @param contractClassName The fully qualified name of the contract class.
|
* @param contractClassName The fully qualified name of the contract class.
|
||||||
* @param minContractVersion The minimum contract version that should be returned.
|
* @param minContractVersion The minimum contract version that should be returned.
|
||||||
* @return the [AttachmentId] of the contract, or null if none meet the criteria.
|
* @return the [AttachmentId] of the contract, or null if none meet the criteria.
|
||||||
*/
|
*/
|
||||||
fun getContractAttachmentWithHighestContractVersion(contractClassName: String, minContractVersion: Int): AttachmentId?
|
fun getContractAttachmentWithHighestContractVersion(contractClassName: String, minContractVersion: Int): AttachmentId?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the Attachment Ids of the contract attachments for a given contract class name
|
||||||
|
* from trusted upload sources.
|
||||||
|
*
|
||||||
|
* @param contractClassName The fully qualified name of the contract class.
|
||||||
|
* @return the [AttachmentId]s of the contract attachments, or an empty set if none meet the criteria.
|
||||||
|
*/
|
||||||
|
fun getContractAttachments(contractClassName: String): Set<AttachmentId>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,8 +256,7 @@ open class TransactionBuilder @JvmOverloads constructor(
|
|||||||
val outputHashConstraints = outputStates?.filter { it.constraint is HashAttachmentConstraint } ?: emptyList()
|
val outputHashConstraints = outputStates?.filter { it.constraint is HashAttachmentConstraint } ?: emptyList()
|
||||||
val outputSignatureConstraints = outputStates?.filter { it.constraint is SignatureAttachmentConstraint } ?: emptyList()
|
val outputSignatureConstraints = outputStates?.filter { it.constraint is SignatureAttachmentConstraint } ?: emptyList()
|
||||||
if (inputsHashConstraints.isNotEmpty() && (outputHashConstraints.isNotEmpty() || outputSignatureConstraints.isNotEmpty())) {
|
if (inputsHashConstraints.isNotEmpty() && (outputHashConstraints.isNotEmpty() || outputSignatureConstraints.isNotEmpty())) {
|
||||||
val attachmentQueryCriteria = AttachmentQueryCriteria.AttachmentsQueryCriteria(contractClassNamesCondition = Builder.equal(listOf(contractClassName)))
|
val attachmentIds = services.attachments.getContractAttachments(contractClassName)
|
||||||
val attachmentIds = services.attachments.queryAttachments(attachmentQueryCriteria)
|
|
||||||
// only switchover if we have both signed and unsigned attachments for the given contract class name
|
// only switchover if we have both signed and unsigned attachments for the given contract class name
|
||||||
if (attachmentIds.isNotEmpty() && attachmentIds.size == 2) {
|
if (attachmentIds.isNotEmpty() && attachmentIds.size == 2) {
|
||||||
val attachmentsToUse = attachmentIds.map {
|
val attachmentsToUse = attachmentIds.map {
|
||||||
|
@ -10,6 +10,7 @@ import net.corda.core.CordaRuntimeException
|
|||||||
import net.corda.core.contracts.Attachment
|
import net.corda.core.contracts.Attachment
|
||||||
import net.corda.core.contracts.ContractAttachment
|
import net.corda.core.contracts.ContractAttachment
|
||||||
import net.corda.core.contracts.ContractClassName
|
import net.corda.core.contracts.ContractClassName
|
||||||
|
import net.corda.core.contracts.Version
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.sha256
|
import net.corda.core.crypto.sha256
|
||||||
import net.corda.core.internal.*
|
import net.corda.core.internal.*
|
||||||
@ -54,7 +55,6 @@ class NodeAttachmentService(
|
|||||||
cacheFactory: NamedCacheFactory,
|
cacheFactory: NamedCacheFactory,
|
||||||
private val database: CordaPersistence
|
private val database: CordaPersistence
|
||||||
) : AttachmentStorageInternal, SingletonSerializeAsToken() {
|
) : AttachmentStorageInternal, SingletonSerializeAsToken() {
|
||||||
|
|
||||||
// This is to break the circular dependency.
|
// This is to break the circular dependency.
|
||||||
lateinit var servicesForResolution: ServicesForResolution
|
lateinit var servicesForResolution: ServicesForResolution
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ class NodeAttachmentService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "${NODE_DATABASE_PREFIX}attachments", indexes = [Index(name = "att_id_idx", columnList = "att_id")])
|
@Table(name = "${NODE_DATABASE_PREFIX}attachments", indexes = [(Index(name = "att_id_idx", columnList = "att_id"))])
|
||||||
class DBAttachment(
|
class DBAttachment(
|
||||||
@Id
|
@Id
|
||||||
@Column(name = "att_id", nullable = false)
|
@Column(name = "att_id", nullable = false)
|
||||||
@ -403,10 +403,29 @@ class NodeAttachmentService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val contractsCache = InfrequentlyMutatedCache<String, NavigableMap<Int, AttachmentId>>("NodeAttachmentService_contractAttachmentVersions", cacheFactory)
|
// Holds onto a signed and/or unsigned attachment (at least one or the other).
|
||||||
|
private data class AttachmentIds(val signed: AttachmentId?, val unsigned: AttachmentId?) {
|
||||||
|
init {
|
||||||
|
// One of them at least must exist.
|
||||||
|
check(signed != null || unsigned != null)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getContractAttachmentWithHighestContractVersion(contractClassName: String, minContractVersion: Int): AttachmentId? {
|
fun toList(): List<AttachmentId> =
|
||||||
val versions: NavigableMap<Int, AttachmentId> = contractsCache.get(contractClassName) { name ->
|
if(signed != null) {
|
||||||
|
if(unsigned != null) {
|
||||||
|
listOf(signed, unsigned)
|
||||||
|
} else listOf(signed)
|
||||||
|
} else listOf(unsigned!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This caches contract attachment versions by contract class name. For each version, we support one signed and one unsigned attachment, since that is allowed.
|
||||||
|
*
|
||||||
|
* It is correctly invalidated as new attachments are uploaded.
|
||||||
|
*/
|
||||||
|
private val contractsCache = InfrequentlyMutatedCache<ContractClassName, NavigableMap<Version, AttachmentIds>>("NodeAttachmentService_contractAttachmentVersions", cacheFactory)
|
||||||
|
|
||||||
|
private fun getContractAttachmentVersions(contractClassName: String): NavigableMap<Version, AttachmentIds> = contractsCache.get(contractClassName) { name ->
|
||||||
val attachmentQueryCriteria = AttachmentQueryCriteria.AttachmentsQueryCriteria(contractClassNamesCondition = Builder.equal(listOf(name)),
|
val attachmentQueryCriteria = AttachmentQueryCriteria.AttachmentsQueryCriteria(contractClassNamesCondition = Builder.equal(listOf(name)),
|
||||||
versionCondition = Builder.greaterThanOrEqual(0), uploaderCondition = Builder.`in`(TRUSTED_UPLOADERS))
|
versionCondition = Builder.greaterThanOrEqual(0), uploaderCondition = Builder.`in`(TRUSTED_UPLOADERS))
|
||||||
val attachmentSort = AttachmentSort(listOf(AttachmentSort.AttachmentSortColumn(AttachmentSort.AttachmentSortAttribute.VERSION, Sort.Direction.DESC)))
|
val attachmentSort = AttachmentSort(listOf(AttachmentSort.AttachmentSortColumn(AttachmentSort.AttachmentSortAttribute.VERSION, Sort.Direction.DESC)))
|
||||||
@ -426,9 +445,26 @@ class NodeAttachmentService(
|
|||||||
val query = session.createQuery(criteriaQuery)
|
val query = session.createQuery(criteriaQuery)
|
||||||
|
|
||||||
// execution
|
// execution
|
||||||
TreeMap(query.resultList.map { it.version to AttachmentId.parse(it.attId) }.toMap())
|
TreeMap(query.resultList.groupBy { it.version }.map { makeAttachmentIds(it) }.toMap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return versions.tailMap(minContractVersion, true).lastEntry()?.value
|
|
||||||
|
private fun makeAttachmentIds(it: Map.Entry<Int, List<DBAttachment>>): Pair<Version, AttachmentIds> {
|
||||||
|
check(it.value.size <= 2)
|
||||||
|
val signed = it.value.filter { it.signers?.isNotEmpty() ?: false }.map { AttachmentId.parse(it.attId) }.singleOrNull()
|
||||||
|
val unsigned = it.value.filter { it.signers?.isEmpty() ?: true }.map { AttachmentId.parse(it.attId) }.singleOrNull()
|
||||||
|
return it.key to AttachmentIds(signed, unsigned)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getContractAttachmentWithHighestContractVersion(contractClassName: String, minContractVersion: Int): AttachmentId? {
|
||||||
|
val versions: NavigableMap<Version, AttachmentIds> = getContractAttachmentVersions(contractClassName)
|
||||||
|
val newestAttachmentIds = versions.tailMap(minContractVersion, true).lastEntry()?.value
|
||||||
|
return newestAttachmentIds?.toList()?.first()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getContractAttachments(contractClassName: String): Set<AttachmentId> {
|
||||||
|
val versions: NavigableMap<Version, AttachmentIds> = getContractAttachmentVersions(contractClassName)
|
||||||
|
return versions.values.flatMap { it.toList() }.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -29,7 +29,6 @@ import java.util.jar.JarInputStream
|
|||||||
* A mock implementation of [AttachmentStorage] for use within tests
|
* A mock implementation of [AttachmentStorage] for use within tests
|
||||||
*/
|
*/
|
||||||
class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() {
|
class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() {
|
||||||
|
|
||||||
private data class ContractAttachmentMetadata(val name: ContractClassName, val version: Int, val isSigned: Boolean)
|
private data class ContractAttachmentMetadata(val name: ContractClassName, val version: Int, val isSigned: Boolean)
|
||||||
|
|
||||||
private val _files = HashMap<SecureHash, Pair<Attachment, ByteArray>>()
|
private val _files = HashMap<SecureHash, Pair<Attachment, ByteArray>>()
|
||||||
@ -123,4 +122,9 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() {
|
|||||||
val attachmentSort = AttachmentSort(listOf(AttachmentSort.AttachmentSortColumn(AttachmentSort.AttachmentSortAttribute.VERSION, Sort.Direction.DESC)))
|
val attachmentSort = AttachmentSort(listOf(AttachmentSort.AttachmentSortColumn(AttachmentSort.AttachmentSortAttribute.VERSION, Sort.Direction.DESC)))
|
||||||
return queryAttachments(attachmentQueryCriteria, attachmentSort).firstOrNull()
|
return queryAttachments(attachmentQueryCriteria, attachmentSort).firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getContractAttachments(contractClassName: String): Set<AttachmentId> {
|
||||||
|
val attachmentQueryCriteria = AttachmentQueryCriteria.AttachmentsQueryCriteria(contractClassNamesCondition = Builder.equal(listOf(contractClassName)))
|
||||||
|
return queryAttachments(attachmentQueryCriteria).toSet()
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user