Skip to content

Commit 3b280c7

Browse files
authored
Merge pull request #35 from picoded/structcache-update
Structcache update
2 parents eb340ed + cae7d96 commit 3b280c7

8 files changed

Lines changed: 494 additions & 68 deletions

File tree

README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,29 +18,32 @@ Followed by `DStack` which faciltates the stacking of data backend provider for
1818
| backend | status | notes | DataObjectMap | KeyValueMap | KeyLongMap | FileWorkspaceMap |
1919
|------------------|---------------|------------------------------------------------------|---------------|-------------|------------|------------------|
2020
| struct.simple | in-production | reference implementation, not recommended for use | storage | storage | storage | storage |
21-
| struct.cache | in-production | local instance caching, useful for WORM data | storage | | | |
21+
| struct.cache | in-production | local instance caching, useful for WORM data | storage | storage | | |
2222
| jsql | in-production | *with limits: see SQL support notes below | full | full | full | full |
23+
| mongodb | in-production | *with limits: see MongoDB support notes below | full | full | full | full |
2324
| hazelcast.cache | in-production | | full | full | full | |
2425
| hazelcast.store | in-production | | full | full | full | |
2526
| file.simple | in-production | | | | | storage |
2627
| file.layered | in-production | | | | | storage |
27-
| ignite | development | roadmap | | | | |
28-
| cockroachdb | development | roadmap | | | | |
28+
| resdisson | experimental | | storage | storage | | |
29+
| ignite | roadmap | roadmap | | | | |
30+
| cockroachdb | roadmap | roadmap | | | | |
2931

3032
**Important notes**
3133

3234
- "full", means it has been optimized for both storage, and query operations.
3335
- "storage" means does not support optimization for queries, for large queries, this can have detrimental performance implication, as the apistack will need to iterate a large number of data.
3436
- Hazelcast require a custom build / deployment with the JavaCommons JAR file to support the required functionality
3537
- MySQL connection / db seems to support only up to 16 digits of accuracy
38+
- Requires read-after-write consistency, for expected behaviour, use w=majority&readConcernLevel=linearizable
3639

3740
# Data Structures
3841

3942
| DataStructure | Status | Description |
4043
|------------------|---------------|----------------------------------------------------------------------------|
4144
| DataObjectMap | in-production | Map document storage, with SQL query support |
4245
| KeyValueMap | in-production | High performance key to string value storage |
43-
| KeyLongMap | experimental | Varient of KeyValue with atomic long support (if used in single tier mode) |
44-
| FileWorkspaceMap | development | File workspace storage support |
46+
| KeyLongMap | in-production | Varient of KeyValue with atomic long support (if used in single tier mode) |
47+
| FileWorkspaceMap | in-production | File workspace storage support |
4548
| MessageQueue | road-map | Message queue |
4649
| JobQueue | road-map | Job request, response queue |

build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ plugins {
1313
// id "com.github.onslip.gradle-one-jar" version "1.0.5"
1414

1515
// Shadow plugin used to build the shade jar
16-
id 'com.github.johnrengelman.shadow' version '2.0.4'
16+
id 'com.github.johnrengelman.shadow' version '4.0.4'
1717

1818
// jacoco code coverage for test
1919
id 'jacoco'
@@ -101,8 +101,8 @@ dependencies {
101101
api "net.sourceforge.jtds:jtds:1.3.1" // for mssql
102102

103103
// Cache2k implementation
104-
api "org.cache2k:cache2k-api:1.2.4.Final"
105-
api "org.cache2k:cache2k-core:1.2.4.Final"
104+
api "org.cache2k:cache2k-api:2.6.1.Final"
105+
api "org.cache2k:cache2k-core:2.6.1.Final"
106106

107107
// Hazelcast
108108
api "com.hazelcast:hazelcast-all:4.0.2"

src/main/java/picoded/dstack/file/simple/FileSimple_FileWorkspaceMap.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ public void backend_setupWorkspace(String oid) {
147147
if (file == null) {
148148
throw new RuntimeException("Invalid OID (unable to setup)");
149149
}
150-
boolean mkdir = file.mkdirs();
150+
file.mkdirs();
151151
}
152152

153153
//--------------------------------------------------------------------------

src/main/java/picoded/dstack/struct/cache/StructCacheStack.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ protected Core_DataStructure initDataStructure(String name, String type) {
3232
Core_DataStructure ret = null;
3333
if (type.equalsIgnoreCase("DataObjectMap")) {
3434
ret = new StructCache_DataObjectMap();
35+
} else if (type.equalsIgnoreCase("KeyValueMap")) {
36+
ret = new StructCache_KeyValueMap();
3537
}
3638

3739
// If datastrucutre initialized, setup name
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package picoded.dstack.struct.cache;
2+
3+
import picoded.core.struct.GenericConvertMap;
4+
5+
import java.util.concurrent.TimeUnit;
6+
7+
// Cache2k implmentation
8+
import org.cache2k.Cache2kBuilder;
9+
import org.cache2k.Cache;
10+
11+
class StructCacheUtil {
12+
13+
/**
14+
* Utility function used to build a new Cache2k cache instance
15+
* This handles all the various common config settings, and set it up accordingly
16+
*/
17+
static <V> Cache<String, V> setupCache2kMap(Cache2kBuilder<String,V> builder, String name, GenericConvertMap<String, Object> config) {
18+
19+
//
20+
// Get Config
21+
//-----------------------------------------------------------------------
22+
23+
//
24+
// Alright, time to build a new cache
25+
// We are in the era of GB ram computing, 10k cache would
26+
// be a good sane default in server environment. Even if there are
27+
// multiple sets of StructCache, as it would take ~60MB each
28+
//
29+
// That means, with 10 data set being cached, that would be about ~600MB
30+
//
31+
// to consider : auto detect RAM size in KB - and use that?
32+
// a good rough guideline would be 1/4 of free ram space divided by 6kb
33+
// as a capcity size auto detction
34+
//
35+
// # DataObjectMap caching napkin math assumptions
36+
// - Assume a hashmap object with 30 parameters (including system keys)
37+
// - Because its hard to predict the capacity/size ratio it is assumed to be 1:1
38+
// - Keys and value storage are assumed to be a 22 character string
39+
//
40+
// > The above assumptions was designed to somewhat be the upper limit of
41+
// > ram storage cost for a data object map. Rather then an average.
42+
//
43+
// # References
44+
// - http://java-performance.info/memory-consumption-of-java-data-types-2/
45+
// - https://www.javamex.com/tutorials/memory/string_memory_usage.shtml
46+
//
47+
// # The Math
48+
//
49+
// 36 bytes : 32+4 bytes - HashMap space on primary cache map
50+
// 108 bytes : 3 x overhead for cache mapping
51+
// 62 bytes : 40 overhead + 22 oid string key
52+
// 1080 bytes : 30 x (32+4) HashMap overhead
53+
// 1860 bytes : 30 x (40+22) ObjectMap key strings
54+
// 1860 bytes : 30 x (40+22) ObjectMap value strings
55+
// ----------
56+
// 5006 bytes : Total bytes per object map
57+
// ~ 6 kilo bytes : Rounded up
58+
//
59+
// # RAM cost for 10k objects
60+
//
61+
// 10,000 * 6 KB = 60 MB
62+
//
63+
// > So yea, we are ok to assume a 10k objects for most parts
64+
//
65+
int capacity = config.getInt("capacity", 10000);
66+
67+
// Disable monitoring statistics by default
68+
boolean monitoring = config.getBoolean("monitoring", false);
69+
70+
// Optimize for high concurrency mode, not recommended unless its
71+
// >100k size, and 8 vCPU that is constantly under heavy load
72+
boolean boostConcurrency = config.getBoolean("boostConcurrency", false);
73+
74+
// Enable / Disable statistics, because we do not provide any API interface for this
75+
// there is almost no use case, unless you are bypassing our api and modifying cach2k directly
76+
boolean statistics = config.getBoolean("statistics", false);
77+
78+
// Number of milliseconds to cache the values up to, note that this should operate independent of
79+
// any expiry logic that is used for KeyValueMap, defaults to 3 days (in seconds)
80+
//
81+
// If you want to disable expiry (which makes no sense), use -1
82+
long valueLifespan = config.getLong("valueLifespan", 3 * 24 * 60 * 60 * 1000L);
83+
84+
//
85+
// Setup the builder
86+
//-----------------------------------------------------------------------
87+
88+
// Configure cache name and capacity
89+
builder = builder.name(name).entryCapacity(capacity);
90+
91+
// Disable monitoring
92+
if (monitoring == false) {
93+
builder = builder.disableMonitoring(true);
94+
}
95+
// boostConcurrency if configured
96+
if (boostConcurrency == true) {
97+
builder = builder.boostConcurrency(true);
98+
}
99+
if (statistics == false) {
100+
builder = builder.disableStatistics(true);
101+
}
102+
if (valueLifespan >= 0) {
103+
builder = builder.expireAfterWrite(valueLifespan, TimeUnit.MILLISECONDS);
104+
} else {
105+
builder = builder.eternal(true);
106+
}
107+
108+
//
109+
// Build and return the built cache
110+
//-----------------------------------------------------------------------
111+
return builder.build();
112+
}
113+
}

src/main/java/picoded/dstack/struct/cache/StructCache_DataObjectMap.java

Lines changed: 21 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
11
package picoded.dstack.struct.cache;
22

33
// Java imports
4-
import java.util.HashMap;
54
import java.util.Map;
6-
import java.util.Set;
75
import java.util.concurrent.ConcurrentHashMap;
8-
import java.util.concurrent.locks.ReentrantReadWriteLock;
96

107
// Picoded imports
11-
import picoded.core.conv.ConvertJSON;
12-
import picoded.core.common.ObjectToken;
13-
import picoded.dstack.*;
148
import picoded.dstack.core.*;
159

1610
// Cache2k implmentation
@@ -19,7 +13,7 @@
1913

2014
/**
2115
* Internal cache implementation of DataObjectMap
22-
* This is done via a minimal implementation via internal data structures.
16+
* This is done via a cache2k implementation via internal data structures.
2317
*
2418
* Built ontop of the Core_DataObjectMap_struct implementation.
2519
**/
@@ -144,65 +138,33 @@ public void systemSetup() {
144138
return;
145139
}
146140

147-
//
148-
// Alright, time to build a new cache
149-
// We are in the era of GB ram computing, 10k cache would
150-
// be a good sane default in server environment. Even if there are
151-
// multiple sets of StructCache, as it would take ~60MB
152-
//
153-
// to consider : auto detect RAM size in KB - and use that?
154-
// a good rough guideline would be 1/4 of free ram space divided by 6kb
155-
// as a capcity size auto detction
156-
//
157-
// # DataObjectMap caching napkin math assumptions
158-
// - Assume a hashmap object with 30 parameters (including system keys)
159-
// - Because its hard to predict the capacity/size ratio it is assumed to be 1:1
160-
// - Keys and value storage are assumed to be a 22 character string
161-
//
162-
// > The above assumptions was designed to somewhat be the upper limit of
163-
// > ram storage cost for a data object map. Rather then an average.
164-
//
165-
// # References
166-
// - http://java-performance.info/memory-consumption-of-java-data-types-2/
167-
// - https://www.javamex.com/tutorials/memory/string_memory_usage.shtml
168-
//
169-
// # The Math
170-
//
171-
// 36 bytes : 32+4 bytes - HashMap space on primary cache map
172-
// 108 bytes : 3 x overhead for cache mapping
173-
// 62 bytes : 40 overhead + 22 oid string key
174-
// 1080 bytes : 30 x (32+4) HashMap overhead
175-
// 1860 bytes : 30 x (40+22) ObjectMap key strings
176-
// 1860 bytes : 30 x (40+22) ObjectMap value strings
177-
// ----------
178-
// 5006 bytes : Total bytes per object map
179-
// ~ 6 kilo bytes : Rounded up
180-
//
181-
// # RAM cost for 10k objects
182-
//
183-
// 100,000 * 6 KB = 60 MB
184-
//
185-
// > So yea, we are ok to assume a 10k objects for most parts
186-
//
187-
int capicity = configMap().getInt("capacity", 10000);
188-
_valueMap = new Cache2kBuilder<String, Map<String, Object>>() {
189-
} //
190-
.name(cacheName())//
191-
.eternal(true)//
192-
.entryCapacity(capicity)//
193-
.build();
194-
195-
// Add it back to the global cache
196-
globalCacheMap.put(cacheName(), _valueMap);
141+
// We perfome the following in a syncronized block, to avoid race conditions
142+
// in the systemSetup process
143+
synchronized (StructCache_DataObjectMap.class) {
144+
// Lets load from global cache map (again) with cache name if possible
145+
_valueMap = globalCacheMap.get(cacheName());
146+
if (_valueMap != null) {
147+
return;
148+
}
149+
150+
// Build the cache
151+
_valueMap = StructCacheUtil.setupCache2kMap(new Cache2kBuilder<String, Map<String,Object>>(){}, cacheName(), configMap());
152+
153+
// Add it back to the global cache
154+
globalCacheMap.put(cacheName(), _valueMap);
155+
}
197156
}
198157

199158
/**
200159
* Teardown and delete the backend storage table, etc. If needed
201160
**/
202161
@Override
203162
public void systemDestroy() {
204-
globalCacheMap.remove(cacheName());
205-
_valueMap = null;
163+
synchronized (StructCache_DataObjectMap.class) {
164+
_valueMap.clear();
165+
globalCacheMap.remove(cacheName());
166+
_valueMap = null;
167+
}
206168
}
207169

208170
}

0 commit comments

Comments
 (0)