mirror of
https://github.com/corda/corda.git
synced 2024-12-19 21:17:58 +00:00
LRU for JDBCHashMap loadOnInit=false, with tests.
This commit is contained in:
parent
dede118c58
commit
5c1d81824e
@ -25,8 +25,10 @@ class JDBCHashMapTestSuite {
|
|||||||
lateinit var transaction: Transaction
|
lateinit var transaction: Transaction
|
||||||
lateinit var database: Database
|
lateinit var database: Database
|
||||||
lateinit var loadOnInitFalseMap: JDBCHashMap<String, String>
|
lateinit var loadOnInitFalseMap: JDBCHashMap<String, String>
|
||||||
|
lateinit var memoryConstrainedMap: JDBCHashMap<String, String>
|
||||||
lateinit var loadOnInitTrueMap: JDBCHashMap<String, String>
|
lateinit var loadOnInitTrueMap: JDBCHashMap<String, String>
|
||||||
lateinit var loadOnInitFalseSet: JDBCHashSet<String>
|
lateinit var loadOnInitFalseSet: JDBCHashSet<String>
|
||||||
|
lateinit var memoryConstrainedSet: JDBCHashSet<String>
|
||||||
lateinit var loadOnInitTrueSet: JDBCHashSet<String>
|
lateinit var loadOnInitTrueSet: JDBCHashSet<String>
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@ -37,8 +39,10 @@ class JDBCHashMapTestSuite {
|
|||||||
database = dataSourceAndDatabase.second
|
database = dataSourceAndDatabase.second
|
||||||
setUpDatabaseTx()
|
setUpDatabaseTx()
|
||||||
loadOnInitFalseMap = JDBCHashMap<String, String>("test_map_false", loadOnInit = false)
|
loadOnInitFalseMap = JDBCHashMap<String, String>("test_map_false", loadOnInit = false)
|
||||||
|
memoryConstrainedMap = JDBCHashMap<String, String>("test_map_constrained", loadOnInit = false, maxBuckets = 1)
|
||||||
loadOnInitTrueMap = JDBCHashMap<String, String>("test_map_true", loadOnInit = true)
|
loadOnInitTrueMap = JDBCHashMap<String, String>("test_map_true", loadOnInit = true)
|
||||||
loadOnInitFalseSet = JDBCHashSet<String>("test_set_false", loadOnInit = false)
|
loadOnInitFalseSet = JDBCHashSet<String>("test_set_false", loadOnInit = false)
|
||||||
|
memoryConstrainedSet = JDBCHashSet<String>("test_set_constrained", loadOnInit = false, maxBuckets = 1)
|
||||||
loadOnInitTrueSet = JDBCHashSet<String>("test_set_true", loadOnInit = true)
|
loadOnInitTrueSet = JDBCHashSet<String>("test_set_true", loadOnInit = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,8 +54,8 @@ class JDBCHashMapTestSuite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun createMapTestSuite(loadOnInit: Boolean): TestSuite = com.google.common.collect.testing.MapTestSuiteBuilder
|
fun createMapTestSuite(loadOnInit: Boolean, constrained: Boolean): TestSuite = com.google.common.collect.testing.MapTestSuiteBuilder
|
||||||
.using(JDBCHashMapTestGenerator(loadOnInit = loadOnInit))
|
.using(JDBCHashMapTestGenerator(loadOnInit = loadOnInit, constrained = constrained))
|
||||||
.named("test JDBCHashMap with loadOnInit=$loadOnInit")
|
.named("test JDBCHashMap with loadOnInit=$loadOnInit")
|
||||||
.withFeatures(
|
.withFeatures(
|
||||||
com.google.common.collect.testing.features.CollectionSize.ANY,
|
com.google.common.collect.testing.features.CollectionSize.ANY,
|
||||||
@ -65,8 +69,8 @@ class JDBCHashMapTestSuite {
|
|||||||
.createTestSuite()
|
.createTestSuite()
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun createSetTestSuite(loadOnInit: Boolean): TestSuite = com.google.common.collect.testing.SetTestSuiteBuilder
|
fun createSetTestSuite(loadOnInit: Boolean, constrained: Boolean): TestSuite = com.google.common.collect.testing.SetTestSuiteBuilder
|
||||||
.using(JDBCHashSetTestGenerator(loadOnInit = loadOnInit))
|
.using(JDBCHashSetTestGenerator(loadOnInit = loadOnInit, constrained = constrained))
|
||||||
.named("test JDBCHashSet with loadOnInit=$loadOnInit")
|
.named("test JDBCHashSet with loadOnInit=$loadOnInit")
|
||||||
.withFeatures(
|
.withFeatures(
|
||||||
com.google.common.collect.testing.features.CollectionSize.ANY,
|
com.google.common.collect.testing.features.CollectionSize.ANY,
|
||||||
@ -96,31 +100,41 @@ class JDBCHashMapTestSuite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Guava test suite generator for JDBCHashMap(loadOnInit=false).
|
* Guava test suite generator for JDBCHashMap(loadOnInit=false, constrained = false).
|
||||||
*/
|
*/
|
||||||
class MapLoadOnInitFalse {
|
class MapLoadOnInitFalse {
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun suite(): TestSuite = createMapTestSuite(false)
|
fun suite(): TestSuite = createMapTestSuite(false, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Guava test suite generator for JDBCHashMap(loadOnInit=true).
|
* Guava test suite generator for JDBCHashMap(loadOnInit=false, constrained = true).
|
||||||
|
*/
|
||||||
|
class MapConstained {
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun suite(): TestSuite = createMapTestSuite(false, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guava test suite generator for JDBCHashMap(loadOnInit=true, constrained = false).
|
||||||
*/
|
*/
|
||||||
class MapLoadOnInitTrue {
|
class MapLoadOnInitTrue {
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun suite(): TestSuite = createMapTestSuite(true)
|
fun suite(): TestSuite = createMapTestSuite(true, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generator of map instances needed for testing.
|
* Generator of map instances needed for testing.
|
||||||
*/
|
*/
|
||||||
class JDBCHashMapTestGenerator(val loadOnInit: Boolean) : com.google.common.collect.testing.TestStringMapGenerator() {
|
class JDBCHashMapTestGenerator(val loadOnInit: Boolean, val constrained: Boolean) : com.google.common.collect.testing.TestStringMapGenerator() {
|
||||||
override fun create(elements: Array<Map.Entry<String, String>>): Map<String, String> {
|
override fun create(elements: Array<Map.Entry<String, String>>): Map<String, String> {
|
||||||
val map = if (loadOnInit) loadOnInitTrueMap else loadOnInitFalseMap
|
val map = if (loadOnInit) loadOnInitTrueMap else if(constrained) memoryConstrainedMap else loadOnInitFalseMap
|
||||||
map.clear()
|
map.clear()
|
||||||
map.putAll(elements.associate { Pair(it.key, it.value) })
|
map.putAll(elements.associate { Pair(it.key, it.value) })
|
||||||
return map
|
return map
|
||||||
@ -128,31 +142,41 @@ class JDBCHashMapTestSuite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Guava test suite generator for JDBCHashSet(loadOnInit=false).
|
* Guava test suite generator for JDBCHashSet(loadOnInit=false, constrained = false).
|
||||||
*/
|
*/
|
||||||
class SetLoadOnInitFalse {
|
class SetLoadOnInitFalse {
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun suite(): TestSuite = createSetTestSuite(false)
|
fun suite(): TestSuite = createSetTestSuite(false, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Guava test suite generator for JDBCHashSet(loadOnInit=true).
|
* Guava test suite generator for JDBCHashSet(loadOnInit=false, constrained = true).
|
||||||
|
*/
|
||||||
|
class SetConstrained {
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun suite(): TestSuite = createSetTestSuite(false, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guava test suite generator for JDBCHashSet(loadOnInit=true, constrained = false).
|
||||||
*/
|
*/
|
||||||
class SetLoadOnInitTrue {
|
class SetLoadOnInitTrue {
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun suite(): TestSuite = createSetTestSuite(true)
|
fun suite(): TestSuite = createSetTestSuite(true, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generator of set instances needed for testing.
|
* Generator of set instances needed for testing.
|
||||||
*/
|
*/
|
||||||
class JDBCHashSetTestGenerator(val loadOnInit: Boolean) : com.google.common.collect.testing.TestStringSetGenerator() {
|
class JDBCHashSetTestGenerator(val loadOnInit: Boolean, val constrained: Boolean) : com.google.common.collect.testing.TestStringSetGenerator() {
|
||||||
override fun create(elements: Array<String>): Set<String> {
|
override fun create(elements: Array<String>): Set<String> {
|
||||||
val set = if (loadOnInit) loadOnInitTrueSet else loadOnInitFalseSet
|
val set = if (loadOnInit) loadOnInitTrueSet else if(constrained) memoryConstrainedSet else loadOnInitFalseSet
|
||||||
set.clear()
|
set.clear()
|
||||||
set.addAll(elements)
|
set.addAll(elements)
|
||||||
return set
|
return set
|
||||||
|
@ -26,7 +26,10 @@ import kotlin.system.measureTimeMillis
|
|||||||
* If you can extend [AbstractJDBCHashMap] and implement less Kryo dependent key and/or value mappings then that is
|
* If you can extend [AbstractJDBCHashMap] and implement less Kryo dependent key and/or value mappings then that is
|
||||||
* likely preferrable.
|
* likely preferrable.
|
||||||
*/
|
*/
|
||||||
class JDBCHashMap<K : Any, V : Any>(tableName: String, loadOnInit: Boolean = false) : AbstractJDBCHashMap<K, V, JDBCHashMap.BlobMapTable>(BlobMapTable(tableName), loadOnInit) {
|
class JDBCHashMap<K : Any, V : Any>(tableName: String,
|
||||||
|
loadOnInit: Boolean = false,
|
||||||
|
maxBuckets: Int = 256)
|
||||||
|
: AbstractJDBCHashMap<K, V, JDBCHashMap.BlobMapTable>(BlobMapTable(tableName), loadOnInit, maxBuckets) {
|
||||||
|
|
||||||
class BlobMapTable(tableName: String) : JDBCHashedTable(tableName) {
|
class BlobMapTable(tableName: String) : JDBCHashedTable(tableName) {
|
||||||
val key = blob("key")
|
val key = blob("key")
|
||||||
@ -73,7 +76,10 @@ fun <T : Any> deserializeFromBlob(blob: Blob): T = bytesFromBlob<T>(blob).deseri
|
|||||||
* If you can extend [AbstractJDBCHashSet] and implement less Kryo dependent element mappings then that is
|
* If you can extend [AbstractJDBCHashSet] and implement less Kryo dependent element mappings then that is
|
||||||
* likely preferrable.
|
* likely preferrable.
|
||||||
*/
|
*/
|
||||||
class JDBCHashSet<K : Any>(tableName: String, loadOnInit: Boolean = false) : AbstractJDBCHashSet<K, JDBCHashSet.BlobSetTable>(BlobSetTable(tableName), loadOnInit) {
|
class JDBCHashSet<K : Any>(tableName: String,
|
||||||
|
loadOnInit: Boolean = false,
|
||||||
|
maxBuckets: Int = 256)
|
||||||
|
: AbstractJDBCHashSet<K, JDBCHashSet.BlobSetTable>(BlobSetTable(tableName), loadOnInit, maxBuckets) {
|
||||||
|
|
||||||
class BlobSetTable(tableName: String) : JDBCHashedTable(tableName) {
|
class BlobSetTable(tableName: String) : JDBCHashedTable(tableName) {
|
||||||
val key = blob("key")
|
val key = blob("key")
|
||||||
@ -92,8 +98,10 @@ class JDBCHashSet<K : Any>(tableName: String, loadOnInit: Boolean = false) : Abs
|
|||||||
*
|
*
|
||||||
* See [AbstractJDBCHashMap] for implementation details.
|
* See [AbstractJDBCHashMap] for implementation details.
|
||||||
*/
|
*/
|
||||||
abstract class AbstractJDBCHashSet<K : Any, out T : JDBCHashedTable>(protected val table: T, loadOnInit: Boolean = false) : MutableSet<K>, AbstractSet<K>() {
|
abstract class AbstractJDBCHashSet<K : Any, out T : JDBCHashedTable>(protected val table: T,
|
||||||
protected val innerMap = object : AbstractJDBCHashMap<K, Unit, T>(table, loadOnInit) {
|
loadOnInit: Boolean = false,
|
||||||
|
maxBuckets: Int = 256) : MutableSet<K>, AbstractSet<K>() {
|
||||||
|
protected val innerMap = object : AbstractJDBCHashMap<K, Unit, T>(table, loadOnInit, maxBuckets) {
|
||||||
override fun keyFromRow(row: ResultRow): K = this@AbstractJDBCHashSet.elementFromRow(row)
|
override fun keyFromRow(row: ResultRow): K = this@AbstractJDBCHashSet.elementFromRow(row)
|
||||||
|
|
||||||
// Return constant.
|
// Return constant.
|
||||||
@ -163,9 +171,13 @@ abstract class AbstractJDBCHashSet<K : Any, out T : JDBCHashedTable>(protected v
|
|||||||
* inherited columns that all tables must provide to support iteration order and hashing.
|
* inherited columns that all tables must provide to support iteration order and hashing.
|
||||||
*
|
*
|
||||||
* The map operates in one of two modes.
|
* The map operates in one of two modes.
|
||||||
* 1. loadOnInit=true where the entire table is materialised in the JVM and only writes need to perform database access.
|
* 1. loadOnInit=true where the entire table is loaded into memory in the constructor and all entries remain in memory,
|
||||||
* 2. loadOnInit=false where all entries with the same key hash code are materialised in the JVM on demand when accessed
|
* with only writes needing to perform database access.
|
||||||
* via any method other than via keys/values/entries properties, and thus the whole map is not materialised.
|
* 2. loadOnInit=false where all entries with the same key hash code are loaded from the database on demand when accessed
|
||||||
|
* via any method other than via keys/values/entries properties, and thus the whole map is not loaded into memory. The number
|
||||||
|
* of entries retained in memory is controlled indirectly by an LRU algorithm (courtesy of [LinkedHashMap]) and a maximum
|
||||||
|
* number of hash "buckets", where one bucket represents all entries with the same hash code. There is a default value
|
||||||
|
* for maximum buckets.
|
||||||
*
|
*
|
||||||
* All operations require a [databaseTransaction] to be started.
|
* All operations require a [databaseTransaction] to be started.
|
||||||
*
|
*
|
||||||
@ -175,22 +187,32 @@ abstract class AbstractJDBCHashSet<K : Any, out T : JDBCHashedTable>(protected v
|
|||||||
*
|
*
|
||||||
* This class is *not* thread safe.
|
* This class is *not* thread safe.
|
||||||
*
|
*
|
||||||
* TODO: buckets grows forever. Support some form of LRU cache option (e.g. use [LinkedHashMap.removeEldestEntry] feature).
|
|
||||||
* TODO: consider caching size once calculated for the first time.
|
* TODO: consider caching size once calculated for the first time.
|
||||||
* TODO: buckets just use a list and so are vulnerable to poor hash code implementations with collisions.
|
* TODO: buckets just use a list and so are vulnerable to poor hash code implementations with collisions.
|
||||||
* TODO: if iterators are used extensively when loadOnInit=true, consider maintaining a collection of keys in iteration order to avoid sorting each time.
|
* TODO: if iterators are used extensively when loadOnInit=true, consider maintaining a collection of keys in iteration order to avoid sorting each time.
|
||||||
* TODO: revisit whether we need the loadOnInit==true functionality and remove if not.
|
* TODO: revisit whether we need the loadOnInit==true functionality and remove if not.
|
||||||
*/
|
*/
|
||||||
abstract class AbstractJDBCHashMap<K : Any, V : Any, out T : JDBCHashedTable>(val table: T, val loadOnInit: Boolean = false) : MutableMap<K, V>, AbstractMap<K,V>() {
|
abstract class AbstractJDBCHashMap<K : Any, V : Any, out T : JDBCHashedTable>(val table: T,
|
||||||
|
val loadOnInit: Boolean = false,
|
||||||
|
val maxBuckets: Int = 256) : MutableMap<K, V>, AbstractMap<K,V>() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
protected val log = loggerFor<AbstractJDBCHashMap<*,*,*>>()
|
protected val log = loggerFor<AbstractJDBCHashMap<*,*,*>>()
|
||||||
|
|
||||||
|
private const val INITIAL_CAPACITY: Int = 16
|
||||||
|
private const val LOAD_FACTOR: Float = 0.75f
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash code -> entries mapping. Lazy when loadOnInit=false.
|
// Hash code -> entries mapping.
|
||||||
private val buckets = HashMap<Int, MutableList<NotReallyMutableEntry<K, V>>>()
|
// When loadOnInit = false, size will be limited to maxBuckets entries (which are hash buckets) and map maintains access order rather than insertion order.
|
||||||
|
private val buckets = object : LinkedHashMap<Int, MutableList<NotReallyMutableEntry<K, V>>>(INITIAL_CAPACITY, LOAD_FACTOR, !loadOnInit) {
|
||||||
|
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<Int, MutableList<NotReallyMutableEntry<K, V>>>?): Boolean {
|
||||||
|
return !loadOnInit && size > maxBuckets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
check(maxBuckets > 0) { "The maximum number of buckets to retain in memory must be a positive integer." }
|
||||||
// TODO: Move this to schema version managment tool.
|
// TODO: Move this to schema version managment tool.
|
||||||
createTablesIfNecessary()
|
createTablesIfNecessary()
|
||||||
if (loadOnInit) {
|
if (loadOnInit) {
|
||||||
|
Loading…
Reference in New Issue
Block a user