|
Cache Entry Expiration Policy
|
Description
|
|---|---|
|
Never
|
The cache entry is never removed based on the amount of time it has spent in the cache. However, it can still be evicted if the cache reaches the maximum size configuration or by using the DeleteEntry service. The Cache Entry Expiration Time (Seconds) setting is ignored when the Never expiration policy is configured.
|
|
Expiration Time Since Last Accessed
|
The entry is removed if it hasn’t been read within the specified expiration time.
|
|
Expiration Time Since Created
|
The entry is removed after the specified time has passed since it was created.
|
|
Expiration Time Since Last Modified
|
The entry is removed if it hasn’t been modified within the specified expiration time.
|
|
Expiration Time Since Last Touched
|
The entry is removed if it hasn’t been accessed or modified within the specified expiration time.
|
|
Service Name
|
Description
|
|---|---|
|
PutEntry
|
• Adds an entry to the cache.
• Input Value—One-row InfoTable using the CacheThing's configured DataShape.
|
|
GetEntry
|
• Returns a one-row InfoTable result from the cache.
• If the entry is not found (a cache miss), and the cache is not configured with Cache Performs Loading of Missing Entries , an empty InfoTable is returned. If the cache is configured with that option, the LoadEntry service is called automatically, and its result is returned.
• Cache misses can occur if the entry was never added to the cache, was evicted, or expired.
• Input Value—One-row InfoTable using the CacheThing's configured DataShape. Only the Primary Key values should be populated.
|
|
GetEntryByKey
|
• Returns a one-row InfoTable result from the cache.
• If the entry is not found, an empty InfoTable is returned. Cache misses can occur if the entry was never added to the cache, was evicted, or expired.
• Input Value—String representing the primary key of the entry being retrieved. This service is available only if the DataShape has a single primary key. It works best with String primary keys but also supports most types with a clear String conversion.
|
|
LoadEntry
|
• Used to automatically fetch cache data from the data source when a cache miss occurs.
• This service must be overridden if the Cache Performs Loading of Missing Entries is enabled.
|
|
DeleteEntry
|
• Deletes an entry from the cache.
• In a high-availability environment, this only affects the node where the DeleteEntry service is called.
• Input Value—One-row InfoTable using the CacheThing's configured DataShape. Only the Primary Key values should be populated.
|
|
DeleteEntryByKey
|
• Deletes an entry from the cache.
• In a high-availability environment, this only affects the node where the DeleteEntryByKey service is called.
• Input Value—String representing the primary key of the entry being retrieved. This service is available only if the DataShape has a single primary key.
|
|
PurgeCache
|
• Removes all cache entries.
• In a high-availability environment, this only affects the node where the PurgeCache service is called.
|
|
GetDataShape
|
Returns the cache's configured DataShape.
|
|
SetDataShape
|
Sets the DataShape field in the cache's Configuration Table. Changing this value will automatically purge the cache.
|
|
GetEstimatedEntryCount
|
Returns the estimated number of entries in the cache. Ongoing PUT and DELETE operations, as well as Expiration or Evictions processes, may affect accuracy.
|
|
EstimateEntrySize
|
• Returns the estimated size of an entry if it were added to the cache. This does not put the entry into the cache and does not require any entries already in the cache. This service can be useful to estimate an appropriate Cache Maximum Size (MB) or to prevent large entries from being added to the cache.
• Input Value—One-row InfoTable using the CacheThing's configured DataShape.
|
|
Cache Metrics
|
Description
|
|---|---|
|
thingworx_cache_hit_rate
|
Ratio of cache requests that resulted in an entry being found in the cache
|
|
thingworx_cache_hits
|
Number of times cache lookup methods have returned a cached value
|
|
thingworx_cache_request_count
|
Number of times cache lookup methods have returned either a cached or uncached value
|
|
thingworx_cache_miss_rate
|
Ratio of cache lookup methods that have returned an uncached (newly loaded) value or null
|
|
thingworx_cache_misses
|
Number of times cache lookup methods have returned an uncached (newly loaded) value or null
|
|
thingworx_cache_eviction_count
|
Number of times an entry has been evicted
|
|
thingworx_cache_average_load_penalty
|
Average time spent loading new values
|
|
thingworx_cache_weighted_size
|
Approximate current size of the cache in bytes
|
|
thingworx_cache_max_weight
|
Maximum size of the cache in bytes before eviction
|
|
thingworx_cache_estimated_entry_count
|
Estimated number of entries in the cache
|
|
thingworx_cache_global_max_size
|
Global maximum configured size for all caches in bytes
|
// Input is x as a LONG
// Output is result as LONG
// MyTimesTwoCache CacheThing is configured with a DataShape with two fields:
// operand: (LONG, primaryKey)
// timesTwoValue: (LONG)
result = -1;
// Check the cache first, to see if we have already calculated x*2
// GetEntryByKey is a convenience method that takes the String value the configured DataShape's Primary Key ("key" in this example)
// cacheResult is an InfoTable with the DataShape of the cache
let cacheResult = Things["MyTimesTwoCache"].GetEntryByKey({ operand: x.toString() });
if (cacheResult.getRowCount() === 0) {
// Cache didn't have a result, so calculate it
let timesTwo = x*2;
// Put the result in the cache for the next time we need this multiple
// operand and value are from the Cache's configured DataShape
let cacheInput = getInfoTableForPut(x, timesTwo);
Things["MyTimesTwoCache"].PutEntry({
values: cacheInput
});
// Turn ScriptLogger to debug mode to see the logger messages
logger.debug("PutEntry the following entry into the MyTimesTwoCache: {operand: " + x + ", timesTwoValue: " + timesTwo + "}");
// Set the global result Output variable
result = timesTwo;
} else {
// If results are found, they are always in the first row of the InfoTable
let row = cacheResult.getRow(0);
logger.debug("Found the following entry already in MyTimesTwoCache: {operand: " + row.operand + ", timesTwoValue: " + row.timesTwoValue + "}");
result = row.timesTwoValue;
}
function getInfoTableForPut(operand, timesTwoValue) {
let infoTable = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName: "InfoTable",
dataShapeName: Things["MyTimesTwoCache"].GetDataShape()
});
infoTable.AddRow({"operand": operand, "timesTwoValue ": timesTwoValue });
return infoTable;
}
/*
This Service queries QueryImplementingThingsV2 and caches the results as needed
Service Name: CachingQueryImplementingThingsV2 (Overridden on CacheThing)
Input: values InfoTable with Cached_QueryImplementingThingsV2_DataShape
Cached_QueryImplementingThingsV2_DataShape:
maxItems: NUMBER, Primary Key
nameMask: STRING, Primary Key
query: QUERY, Primary Key
isSortFirst: BOOLEAN, Primary Key
result: INFOTABLE, DataShape RootEntityListV2 (NOT a Primary Key!)
*/
let cacheEntryInfoTable = getCacheEntryInfoTable();
cacheEntryInfoTable.AddRow({
maxItems: maxItems /* NUMBER */,
nameMask: nameMask /* STRING */,
query: query /* QUERY */,
isSortFirst: isSortFirst /* BOOLEAN */
});
let getEntryResult = me.GetEntry({values: cacheEntryInfoTable});
if (getEntryResult.getRowCount() === 0) {
// Cache didn't have results, so call QueryImplementingThingsV2
let queryResult = ThingTemplates["GenericThing"].QueryImplementingThingsV2({
maxItems: maxItems /* NUMBER */,
nameMask: nameMask /* STRING */,
query: query /* QUERY */,
isSortFirst: isSortFirst /* BOOLEAN */
});
// Add the result to the cache, with the primaryKey parameters used to uniquely identify the query.
let newCacheEntryInfoTable = getCacheEntryInfoTable();
newCacheEntryInfoTable.AddRow({
maxItems: maxItems /* NUMBER */,
nameMask: nameMask /* STRING */,
query: query /* QUERY */,
isSortFirst: isSortFirst /* BOOLEAN */,
result: queryResult /* INFOTABLE */
});
me.PutEntry({
values: newCacheEntryInfoTable
});
logger.debug("Entry not found in Cache, retrieved from QueryImplementingThingsV2:" + queryResult.ToJSON());
result = queryResult;
} else {
logger.debug("Found entry from Cache:" + getEntryResult.ToJSON());
let row = getEntryResult.getRow(0);
result = row.result;
}
function getCacheEntryInfoTable() {
return Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName: "InfoTable",
dataShapeName: "Cached_QueryImplementingThingsV2_DataShape"
});
}
/*
This Service simply proxies a query to QueryImplementingThingsV2 for Read Through caching
Service Name: LoadEntry (Overridden on CacheThing)
Input: values InfoTable with Cached_QueryImplementingThingsV2_DataShape
Cached_QueryImplementingThingsV2_DataShape:
maxItems: NUMBER, Primary Key
nameMask: STRING, Primary Key
query: QUERY, Primary Key
isSortFirst: BOOLEAN, Primary Key
result: INFOTABLE, DataShape RootEntityListV2 (NOT a Primary Key!)
*/
let queryResult = ThingTemplates["GenericThing"].QueryImplementingThingsV2({
maxItems: values.maxItems /* NUMBER */,
nameMask: values.nameMask /* STRING */,
query: values.query /* QUERY */,
isSortFirst: values.isSortFirst /* BOOLEAN */
});
// Copy the results into a new InfoTable.
// This prevents some internal ThingWorx casting issues with some InfoTables
let resultTable = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName: "InfoTable",
dataShapeName: me.GetDataShape() // Returns Cached_QueryImplementingThingsV2_DataShape
});
resultTable.AddRow({
maxItems: values.maxItems /* NUMBER {"defaultValue":500} */,
nameMask: values.nameMask /* STRING */,
query: values.query /* QUERY */,
isSortFirst: values.isSortFirst /* BOOLEAN */,
result: queryResult
});
let result = resultTable;
This service automatically populates demo data into the DataTable in the queried date range. |
/* Reads the Cached results of BetweenDatesDataTableQuery Service.
If Cache doesn't have the result, call BetweenDatesDataTableQuery and load results into Cache.
Service Name: CachingBetweenDatesDataTableQuery
Inputs: StartDate[DATETIME, required], EndDate[DATETIME, required]
Output: LONG
*/
// This example uses CacheThing.GetEntry, which requires a one-row infoTable populated with the CacheThing's DataShapes primaryKey getEntryByKey
let cacheEntryInfoTable = getCacheEntryInfoTable();
cacheEntryInfoTable.AddRow({"startDate": StartDate, "endDate": EndDate});
let getEntryResult = Things["CacheThingDemo"].GetEntry({
values: cacheEntryInfoTable
});
if (getEntryResult.getRowCount() === 0) {
// Cache didn't have results, so go to backing DataTable, by calling the BetweenDatesDataTableQuery Service
let queryResult = Things["CacheThingDemo"].BetweenDatesDataTableQuery({
StartDate: StartDate,
EndDate: EndDate
});
// Turn ScriptLogger to debug mode to see the logger messages
logger.debug("PutEntry called for BetweenDatesQueryCache: {StartDate: " + StartDate + ", EndDate: " + EndDate + " value: " + queryResult + "}");
if (!queryResult || queryResult < 0) {
// BetweenDatesDataTableQuery should always populate with demo data if no data is in the StartDate - EndDate range
throw new Error("BetweenDatesDataTableQuery did not return results");
}
let newCacheEntryInfoTable = getCacheEntryInfoTable();
newCacheEntryInfoTable.AddRow({"startDate": StartDate, "endDate": EndDate, "longValue": queryResult});
Things["CacheThingDemo"].PutEntry({
values: newCacheEntryInfoTable
});
result = queryResult;
} else {
let row = getEntryResult.getRow(0);
logger.debug("Found the following entry already in Cache : {StartDate: " + row.startDate + ", EndDate: " + row.endDate + ", LongValue: " + row.longValue + "}");
result = row.longValue;
}
function getCacheEntryInfoTable() {
return Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName: "InfoTable",
dataShapeName: "TimeSpanValueCacheDataShape"
});
}
/* This service queries a range of dates, based on the dateKey column, and returns the greatest longKey value found.
If there are no entries between StartDate and EndDate this Service dynamically populates demo data into demoDataTable, and returns that result.
Service Name: BetweenDatesDataTableQuery
Inputs: StartDate[DATETIME, required], EndDate[DATETIME, required]
Output: LONG
*/
if (StartDate >= EndDate) {
throw Error("StartDate must be before EndDate");
}
let dataTable = Things["DemoDataTable"];
let queryResult = queryGreatestLongKeyBetweenDates(dataTable, StartDate, EndDate);
if (queryResult.getRowCount() === 0) {
// If we don't get any results, first populate the date range with random values, then retry
// Turn ScriptLogger to debug mode to see the logger messages
logger.debug("No values found between [" + StartDate + "] and [" + EndDate + "], populating 100 demo values and re-querying");
populateDemoDataTable(dataTable, StartDate, EndDate);
// Re-query the now populated DateTable
queryResult = queryGreatestLongKeyBetweenDates(dataTable, StartDate, EndDate);
result = queryResult.getRow(0).get("longKey");
} else {
result = queryResult.getRow(0).get("longKey");
}
// Queries the DataTable for the largest longKey column value between the given dates
// Returns: One-row InfoTable with the found row.
function queryGreatestLongKeyBetweenDates(dateTable, startDate, endDate) {
let greatestBetweenDatesQuery = {
"filters": {
"type": "Between",
"fieldName": "dateKey",
"from": startDate,
"to": endDate
},
"sorts": [{
"fieldName": "longKey",
"isAscending": false
}]
};
// Run the Query to find the greatest Value Between Two Dates
let queryResult = dataTable.QueryDataTableEntries({
maxItems: 1,
query: greatestBetweenDatesQuery,
});
logger.debug("Queried between [" + StartDate + "] and [" + EndDate + "], found:" + queryResult.toJSON());
return queryResult;
}
function populateDemoDataTable(dataTable, startDate, endDate) {
// add 100 random entries between startDate and endDate
for (let entry = 0; entry < 100; entry++) {
let randomTimeBetween = randomDate(startDate, endDate);
let entry = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName: "InfoTable",
dataShapeName: "TimeValueDataShape"
});
entry.AddRow({
"longKey": Math.floor(Math.random() * 1000),
"dateKey": randomTimeBetween
});
Things["DemoDataTable"].AddDataTableEntry({
values: entry /* INFOTABLE */ ,
});
}
}
function randomDate(startDate, endDate) {
let randomTime = Math.random() * (endDate.getTime() - startDate.getTime()) + startDate.getTime();
return new Date(randomTime);
}