mirror of
https://github.com/corda/corda.git
synced 2025-06-19 23:53:52 +00:00
AppendOnlyPersistentMapBase.allPersisted no longer loads everything into memory at once (#5286)
As a general purpose API, allPersisted should not be loading the entire contents of the database table into memory. Instead now it returns a Stream for processing of elements.
This commit is contained in:
@ -45,12 +45,15 @@ import java.util.*
|
|||||||
import java.util.Spliterator.*
|
import java.util.Spliterator.*
|
||||||
import java.util.concurrent.ExecutorService
|
import java.util.concurrent.ExecutorService
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import java.util.stream.Collectors
|
||||||
|
import java.util.stream.Collectors.toCollection
|
||||||
import java.util.stream.IntStream
|
import java.util.stream.IntStream
|
||||||
import java.util.stream.Stream
|
import java.util.stream.Stream
|
||||||
import java.util.stream.StreamSupport
|
import java.util.stream.StreamSupport
|
||||||
import java.util.zip.Deflater
|
import java.util.zip.Deflater
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
import java.util.zip.ZipOutputStream
|
import java.util.zip.ZipOutputStream
|
||||||
|
import kotlin.collections.LinkedHashSet
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.full.createInstance
|
import kotlin.reflect.full.createInstance
|
||||||
|
|
||||||
@ -273,6 +276,9 @@ inline fun <T, R : Any> Stream<T>.mapNotNull(crossinline transform: (T) -> R?):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Similar to [Collectors.toSet] except the Set is guaranteed to be ordered. */
|
||||||
|
fun <T> Stream<T>.toSet(): Set<T> = collect(toCollection { LinkedHashSet<T>() })
|
||||||
|
|
||||||
fun <T> Class<T>.castIfPossible(obj: Any): T? = if (isInstance(obj)) cast(obj) else null
|
fun <T> Class<T>.castIfPossible(obj: Any): T? = if (isInstance(obj)) cast(obj) else null
|
||||||
|
|
||||||
/** Returns a [DeclaredField] wrapper around the declared (possibly non-public) static field of the receiver [Class]. */
|
/** Returns a [DeclaredField] wrapper around the declared (possibly non-public) static field of the receiver [Class]. */
|
||||||
|
@ -115,13 +115,13 @@ class DBNetworkParametersStorage(
|
|||||||
val currentParameters = lookup(currentHash)
|
val currentParameters = lookup(currentHash)
|
||||||
?: throw IllegalStateException("Unable to obtain NotaryInfo – current network parameters not set.")
|
?: throw IllegalStateException("Unable to obtain NotaryInfo – current network parameters not set.")
|
||||||
val inCurrentParams = currentParameters.notaries.singleOrNull { it.identity == party }
|
val inCurrentParams = currentParameters.notaries.singleOrNull { it.identity == party }
|
||||||
if (inCurrentParams == null) {
|
if (inCurrentParams != null) return inCurrentParams
|
||||||
val inOldParams = hashToParameters.allPersisted().flatMap { (_, signedParams) ->
|
return hashToParameters.allPersisted.use {
|
||||||
val parameters = signedParams.raw.deserialize()
|
it.flatMap { (_, signedNetParams) -> signedNetParams.raw.deserialize().notaries.stream() }
|
||||||
parameters.notaries.asSequence()
|
.filter { it.identity == party }
|
||||||
}.firstOrNull { it.identity == party }
|
.findFirst()
|
||||||
return inOldParams
|
.orElse(null)
|
||||||
} else return inCurrentParams
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
|
@ -26,6 +26,7 @@ import sun.reflect.generics.reflectiveObjects.NotImplementedException
|
|||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
import java.util.Comparator.comparingInt
|
||||||
|
|
||||||
class MigrationServicesForResolution(
|
class MigrationServicesForResolution(
|
||||||
override val identityService: IdentityService,
|
override val identityService: IdentityService,
|
||||||
@ -87,8 +88,10 @@ class MigrationServicesForResolution(
|
|||||||
private val filedParams = getNetworkParametersFromFile()
|
private val filedParams = getNetworkParametersFromFile()
|
||||||
|
|
||||||
override val defaultHash: SecureHash = filedParams?.raw?.hash ?: SecureHash.getZeroHash()
|
override val defaultHash: SecureHash = filedParams?.raw?.hash ?: SecureHash.getZeroHash()
|
||||||
override val currentHash: SecureHash = cordaDB.transaction {
|
override val currentHash: SecureHash = cordaDB.transaction {
|
||||||
storage.allPersisted().maxBy { it.second.verified().epoch }?.first ?: defaultHash
|
storage.allPersisted.use {
|
||||||
|
it.max(comparingInt { it.second.verified().epoch }).map { it.first }.orElse(defaultHash)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun lookup(hash: SecureHash): NetworkParameters? {
|
override fun lookup(hash: SecureHash): NetworkParameters? {
|
||||||
|
@ -4,12 +4,12 @@ import net.corda.core.crypto.SecureHash
|
|||||||
import net.corda.core.identity.*
|
import net.corda.core.identity.*
|
||||||
import net.corda.core.internal.NamedCacheFactory
|
import net.corda.core.internal.NamedCacheFactory
|
||||||
import net.corda.core.internal.hash
|
import net.corda.core.internal.hash
|
||||||
|
import net.corda.core.internal.toSet
|
||||||
import net.corda.core.node.services.UnknownAnonymousPartyException
|
import net.corda.core.node.services.UnknownAnonymousPartyException
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
|
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.debug
|
import net.corda.core.utilities.debug
|
||||||
import net.corda.core.utilities.toBase58String
|
|
||||||
import net.corda.node.services.api.IdentityServiceInternal
|
import net.corda.node.services.api.IdentityServiceInternal
|
||||||
import net.corda.node.utilities.AppendOnlyPersistentMap
|
import net.corda.node.utilities.AppendOnlyPersistentMap
|
||||||
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
|
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
|
||||||
@ -25,6 +25,7 @@ import javax.persistence.Column
|
|||||||
import javax.persistence.Entity
|
import javax.persistence.Entity
|
||||||
import javax.persistence.Id
|
import javax.persistence.Id
|
||||||
import javax.persistence.Lob
|
import javax.persistence.Lob
|
||||||
|
import kotlin.streams.toList
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An identity service that stores parties and their identities to a key value tables in the database. The entries are
|
* An identity service that stores parties and their identities to a key value tables in the database. The entries are
|
||||||
@ -171,8 +172,10 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We give the caller a copy of the data set to avoid any locking problems
|
// We give the caller a copy of the data set to avoid any locking problems
|
||||||
override fun getAllIdentities(): Iterable<PartyAndCertificate> = database.transaction {
|
override fun getAllIdentities(): Iterable<PartyAndCertificate> {
|
||||||
keyToParties.allPersisted().map { it.second }.asIterable()
|
return database.transaction {
|
||||||
|
keyToParties.allPersisted.use { it.map { it.second }.toList() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = certificateFromCordaX500Name(name)?.party
|
override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = certificateFromCordaX500Name(name)?.party
|
||||||
@ -190,13 +193,9 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
|
|||||||
|
|
||||||
override fun partiesFromName(query: String, exactMatch: Boolean): Set<Party> {
|
override fun partiesFromName(query: String, exactMatch: Boolean): Set<Party> {
|
||||||
return database.transaction {
|
return database.transaction {
|
||||||
val results = LinkedHashSet<Party>()
|
principalToParties.allPersisted.use {
|
||||||
principalToParties.allPersisted().forEach { (x500name, partyId) ->
|
it.filter { x500Matches(query, exactMatch, it.first) }.map { keyToParties[it.second]!!.party }.toSet()
|
||||||
if (x500Matches(query, exactMatch, x500name)) {
|
|
||||||
results += keyToParties[partyId]!!.party
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
results
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ import java.security.PrivateKey
|
|||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.persistence.*
|
import javax.persistence.*
|
||||||
|
import kotlin.collections.LinkedHashSet
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A persistent re-implementation of [E2ETestKeyManagementService] to support CryptoService for initial keys and
|
* A persistent re-implementation of [E2ETestKeyManagementService] to support CryptoService for initial keys and
|
||||||
@ -76,7 +77,13 @@ class BasicHSMKeyManagementService(cacheFactory: NamedCacheFactory, val identity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val keys: Set<PublicKey> get() = database.transaction { originalKeysMap.keys.plus(keysMap.allPersisted().map { it.first }.toSet()) }
|
override val keys: Set<PublicKey> get() {
|
||||||
|
return database.transaction {
|
||||||
|
val set = LinkedHashSet<PublicKey>(originalKeysMap.keys)
|
||||||
|
keysMap.allPersisted.use { it.forEach { set += it.first } }
|
||||||
|
set
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun containsPublicKey(publicKey: PublicKey): Boolean {
|
private fun containsPublicKey(publicKey: PublicKey): Boolean {
|
||||||
return (publicKey in originalKeysMap || publicKey in keysMap)
|
return (publicKey in originalKeysMap || publicKey in keysMap)
|
||||||
|
@ -3,6 +3,7 @@ package net.corda.node.services.keys
|
|||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.identity.PartyAndCertificate
|
import net.corda.core.identity.PartyAndCertificate
|
||||||
import net.corda.core.internal.NamedCacheFactory
|
import net.corda.core.internal.NamedCacheFactory
|
||||||
|
import net.corda.core.internal.toSet
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
|
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
|
||||||
import net.corda.node.services.identity.PersistentIdentityService
|
import net.corda.node.services.identity.PersistentIdentityService
|
||||||
@ -69,7 +70,7 @@ class PersistentKeyManagementService(cacheFactory: NamedCacheFactory, val identi
|
|||||||
initialKeyPairs.forEach { keysMap.addWithDuplicatesAllowed(it.public, it.private) }
|
initialKeyPairs.forEach { keysMap.addWithDuplicatesAllowed(it.public, it.private) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override val keys: Set<PublicKey> get() = database.transaction { keysMap.allPersisted().map { it.first }.toSet() }
|
override val keys: Set<PublicKey> get() = database.transaction { keysMap.allPersisted.use { it.map { it.first }.toSet() } }
|
||||||
|
|
||||||
override fun filterMyKeys(candidateKeys: Iterable<PublicKey>): Iterable<PublicKey> = database.transaction {
|
override fun filterMyKeys(candidateKeys: Iterable<PublicKey>): Iterable<PublicKey> = database.transaction {
|
||||||
identityService.stripNotOurKeys(candidateKeys)
|
identityService.stripNotOurKeys(candidateKeys)
|
||||||
|
@ -26,6 +26,7 @@ import org.apache.commons.lang3.ArrayUtils.EMPTY_BYTE_ARRAY
|
|||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
import javax.persistence.*
|
import javax.persistence.*
|
||||||
|
import kotlin.streams.toList
|
||||||
|
|
||||||
// cache value type to just store the immutable bits of a signed transaction plus conversion helpers
|
// cache value type to just store the immutable bits of a signed transaction plus conversion helpers
|
||||||
typealias TxCacheValue = Pair<SerializedBytes<CoreTransaction>, List<TransactionSignature>>
|
typealias TxCacheValue = Pair<SerializedBytes<CoreTransaction>, List<TransactionSignature>>
|
||||||
@ -88,10 +89,7 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
|
|||||||
private const val transactionSignatureOverheadEstimate = 1024
|
private const val transactionSignatureOverheadEstimate = 1024
|
||||||
|
|
||||||
private fun weighTx(tx: AppendOnlyPersistentMapBase.Transactional<TxCacheValue>): Int {
|
private fun weighTx(tx: AppendOnlyPersistentMapBase.Transactional<TxCacheValue>): Int {
|
||||||
val actTx = tx.peekableValue
|
val actTx = tx.peekableValue ?: return 0
|
||||||
if (actTx == null) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return actTx.second.sumBy { it.size + transactionSignatureOverheadEstimate } + actTx.first.size
|
return actTx.second.sumBy { it.size + transactionSignatureOverheadEstimate } + actTx.first.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,7 +112,7 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
|
|||||||
override fun track(): DataFeed<List<SignedTransaction>, SignedTransaction> {
|
override fun track(): DataFeed<List<SignedTransaction>, SignedTransaction> {
|
||||||
return database.transaction {
|
return database.transaction {
|
||||||
txStorage.locked {
|
txStorage.locked {
|
||||||
DataFeed(allPersisted().map { it.second.toSignedTx() }.toList(), updates.bufferUntilSubscribed())
|
DataFeed(snapshot(), updates.bufferUntilSubscribed())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -133,6 +131,7 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
|
|||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
val transactions: Iterable<SignedTransaction>
|
val transactions: List<SignedTransaction> get() = database.transaction { snapshot() }
|
||||||
get() = database.transaction { txStorage.content.allPersisted().map { it.second.toSignedTx() }.toList() }
|
|
||||||
|
private fun snapshot() = txStorage.content.allPersisted.use { it.map { it.second.toSignedTx() }.toList() }
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import java.util.*
|
|||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
import java.util.stream.Stream
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements a caching layer on top of an *append-only* table accessed via Hibernate mapping. Note that if the same key is [set] twice,
|
* Implements a caching layer on top of an *append-only* table accessed via Hibernate mapping. Note that if the same key is [set] twice,
|
||||||
@ -36,24 +37,24 @@ abstract class AppendOnlyPersistentMapBase<K, V, E, out EK>(
|
|||||||
/**
|
/**
|
||||||
* Returns the value associated with the key, first loading that value from the storage if necessary.
|
* Returns the value associated with the key, first loading that value from the storage if necessary.
|
||||||
*/
|
*/
|
||||||
operator fun get(key: K): V? {
|
operator fun get(key: K): V? = cache.get(key)?.orElse(null)
|
||||||
return cache.get(key)!!.orElse(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
val size get() = allPersisted().toList().size
|
val size: Long get() = allPersisted.use { it.count() }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all key/value pairs from the underlying storage.
|
* Returns all key/value pairs from the underlying storage in a [Stream].
|
||||||
|
*
|
||||||
|
* Make sure to close the [Stream] once it's been processed.
|
||||||
*/
|
*/
|
||||||
fun allPersisted(): Sequence<Pair<K, V>> {
|
val allPersisted: Stream<Pair<K, V>>
|
||||||
val session = currentDBSession()
|
get() {
|
||||||
val criteriaQuery = session.criteriaBuilder.createQuery(persistentEntityClass)
|
val session = currentDBSession()
|
||||||
val root = criteriaQuery.from(persistentEntityClass)
|
val criteriaQuery = session.criteriaBuilder.createQuery(persistentEntityClass)
|
||||||
criteriaQuery.select(root)
|
val root = criteriaQuery.from(persistentEntityClass)
|
||||||
val query = session.createQuery(criteriaQuery)
|
criteriaQuery.select(root)
|
||||||
val result = query.resultList
|
val query = session.createQuery(criteriaQuery)
|
||||||
return result.map { x -> fromPersistentEntity(x) }.asSequence()
|
return query.stream().map(fromPersistentEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun set(key: K, value: V, logWarning: Boolean, store: (K, V) -> V?): Boolean {
|
private fun set(key: K, value: V, logWarning: Boolean, store: (K, V) -> V?): Boolean {
|
||||||
// Will be set to true if store says it isn't in the database.
|
// Will be set to true if store says it isn't in the database.
|
||||||
@ -157,7 +158,7 @@ abstract class AppendOnlyPersistentMapBase<K, V, E, out EK>(
|
|||||||
Transactional.InFlight(this, key, { loadValue(key) }, { loadValue(key)!! })
|
Transactional.InFlight(this, key, { loadValue(key) }, { loadValue(key)!! })
|
||||||
} else {
|
} else {
|
||||||
// If no one is writing, then the value may or may not exist in the database.
|
// If no one is writing, then the value may or may not exist in the database.
|
||||||
Transactional.Unknown(this, key, { loadValue(key) })
|
Transactional.Unknown(this, key) { loadValue(key) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -335,7 +335,7 @@ object BFTSmart {
|
|||||||
// LinkedHashMap for deterministic serialisation
|
// LinkedHashMap for deterministic serialisation
|
||||||
val committedStates = LinkedHashMap<StateRef, SecureHash>()
|
val committedStates = LinkedHashMap<StateRef, SecureHash>()
|
||||||
val requests = services.database.transaction {
|
val requests = services.database.transaction {
|
||||||
commitLog.allPersisted().forEach { committedStates[it.first] = it.second }
|
commitLog.allPersisted.use { it.forEach { committedStates[it.first] = it.second } }
|
||||||
val criteriaQuery = session.criteriaBuilder.createQuery(PersistentUniquenessProvider.Request::class.java)
|
val criteriaQuery = session.criteriaBuilder.createQuery(PersistentUniquenessProvider.Request::class.java)
|
||||||
criteriaQuery.select(criteriaQuery.from(PersistentUniquenessProvider.Request::class.java))
|
criteriaQuery.select(criteriaQuery.from(PersistentUniquenessProvider.Request::class.java))
|
||||||
session.createQuery(criteriaQuery).resultList
|
session.createQuery(criteriaQuery).resultList
|
||||||
|
@ -175,11 +175,13 @@ class RaftTransactionCommitLog<E, EK>(
|
|||||||
*/
|
*/
|
||||||
override fun snapshot(writer: SnapshotWriter) {
|
override fun snapshot(writer: SnapshotWriter) {
|
||||||
db.transaction {
|
db.transaction {
|
||||||
writer.writeInt(map.size)
|
writer.writeInt(map.size.toInt())
|
||||||
map.allPersisted().forEach {
|
map.allPersisted.use {
|
||||||
val bytes = it.serialize(context = SerializationDefaults.STORAGE_CONTEXT).bytes
|
it.forEach {
|
||||||
writer.writeUnsignedShort(bytes.size)
|
val bytes = it.serialize(context = SerializationDefaults.STORAGE_CONTEXT).bytes
|
||||||
writer.writeObject(bytes)
|
writer.writeUnsignedShort(bytes.size)
|
||||||
|
writer.writeObject(bytes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val criteriaQuery = session.criteriaBuilder.createQuery(PersistentUniquenessProvider.Request::class.java)
|
val criteriaQuery = session.criteriaBuilder.createQuery(PersistentUniquenessProvider.Request::class.java)
|
||||||
|
Reference in New Issue
Block a user