|
캐시 엔트리 만료 정책
|
설명
|
|---|---|
|
없음
|
캐시 엔트리는 캐시에서 소요된 시간에 따라 제거되지 않습니다. 그러나 캐시가 최대 크기 구성에 도달하거나 DeleteEntry 서비스를 사용하면 여전히 제거할 수 있습니다. Never 만료 정책이 구성되면 캐시 엔트리 만료 시간(초) 설정이 무시됩니다.
|
|
마지막 액세스 이후 만료 시간
|
지정된 만료 시간 내에 읽지 않으면 엔트리가 제거됩니다.
|
|
작성 이후 만료 시간
|
엔트리가 생성되고 지정된 시간이 경과하면 엔트리가 제거됩니다.
|
|
마지막 수정 이후 만료 시간
|
지정된 만료 시간 내에 수정되지 않으면 엔트리가 제거됩니다.
|
|
마지막 터치 이후 만료 시간
|
지정된 만료 시간 내에 액세스되지 않거나 수정되지 않으면 엔트리가 제거됩니다.
|
|
서비스 이름
|
설명
|
|---|---|
|
PutEntry
|
• 캐시에 엔트리를 추가합니다.
• 입력 값 - CacheThing의 구성된 DataShape를 사용하는 한 행 인포테이블입니다.
|
|
GetEntry
|
• 캐시에서 한 행의 인포테이블 결과를 반환합니다.
• 엔트리를 찾을 수 없고(캐시 누락) 캐시가 Cache Performs Loading of Missing Entries로 구성되지 않으면 빈 인포테이블이 반환됩니다. 해당 옵션을 사용하여 캐시를 구성하면 LoadEntry 서비스가 자동으로 호출되고 해당 결과가 반환됩니다.
• 엔트리가 캐시에 추가되지 않았거나, 제거되거나, 만료된 경우 캐시 누락이 발생할 수 있습니다.
• 입력 값 - CacheThing의 구성된 DataShape를 사용하는 한 행 인포테이블입니다. Primary Key 값만 채워져야 합니다.
|
|
GetEntryByKey
|
• 캐시에서 한 행의 인포테이블 결과를 반환합니다.
• 엔트리를 찾을 수 없으면 빈 인포테이블이 반환됩니다. 엔트리가 캐시에 추가되지 않았거나, 제거되거나, 만료된 경우 캐시 누락이 발생할 수 있습니다.
• 입력 값 - 검색 중인 엔트리의 기본 키를 나타내는 문자열입니다. 이 서비스는 DataShape에 단일 기본 키가 있는 경우에만 사용할 수 있습니다. 이는 문자열 기본 키에서 가장 잘 작동하며 명확한 문자열 변환을 통해 대부분의 유형도 지원합니다.
|
|
LoadEntry
|
• 캐시 누락이 발생할 때 데이터 소스에서 캐시 데이터를 자동으로 가져오는 데 사용됩니다.
• Cache Performs Loading of Missing Entries가 활성화된 경우 이 서비스를 재정의해야 합니다.
|
|
DeleteEntry
|
• 캐시에서 엔트리를 삭제합니다.
• 고가용성 환경에서는 DeleteEntry 서비스가 호출되는 노드에만 영향을 미칩니다.
• 입력 값 - CacheThing의 구성된 DataShape를 사용하는 한 행 인포테이블입니다. Primary Key 값만 채워져야 합니다.
|
|
DeleteEntryByKey
|
• 캐시에서 엔트리를 삭제합니다.
• 고가용성 환경에서는 DeleteEntryByKey 서비스가 호출되는 노드에만 영향을 미칩니다.
• 입력 값 - 검색 중인 엔트리의 기본 키를 나타내는 문자열입니다. 이 서비스는 DataShape에 단일 기본 키가 있는 경우에만 사용할 수 있습니다.
|
|
PurgeCache
|
• 모든 캐시 엔트리를 제거합니다.
• 고가용성 환경에서는 PurgeCache 서비스가 호출되는 노드에만 영향을 미칩니다.
|
|
GetDataShape
|
캐시의 구성된 DataShape를 반환합니다.
|
|
SetDataShape
|
캐시의 구성 테이블에서 DataShape 필드를 설정합니다. 이 값을 변경하면 캐시가 자동으로 삭제됩니다.
|
|
GetEstimatedEntryCount
|
캐시에 있는 엔트리 수의 추정치를 반환합니다. 진행 중인 PUT 및 DELETE 작업과 만료 또는 제거 프로세스는 정확성에 영향을 미칠 수 있습니다.
|
|
EstimateEntrySize
|
• 엔트리가 캐시에 추가된 경우 해당 엔트리의 예상 크기를 반환합니다. 이렇게 하면 엔트리가 캐시에 배치되지 않으며 캐시에 이미 있는 엔트리가 필요하지 않습니다. 이 서비스는 적절한 캐시 최대 크기(MB)를 추정하거나 큰 엔트리가 캐시에 추가되지 않도록 하는 데 유용할 수 있습니다.
• 입력 값 - CacheThing의 구성된 DataShape를 사용하는 한 행 인포테이블입니다.
|
|
캐시 메트릭
|
설명
|
|---|---|
|
thingworx_cache_hit_rate
|
캐시에서 엔트리가 발견된 캐시 요청의 비율
|
|
thingworx_cache_hits
|
캐시 조회 메소드가 캐시된 값을 반환한 횟수
|
|
thingworx_cache_request_count
|
캐시 조회 메소드가 캐시된 값 또는 캐시되지 않은 값을 반환한 횟수
|
|
thingworx_cache_miss_rate
|
캐시되지 않은(새로 로드된) 값 또는 null을 반환한 캐시 조회 메소드의 비율
|
|
thingworx_cache_misses
|
캐시 조회 메소드가 캐시되지 않은(새로 로드된) 값 또는 null을 반환한 횟수
|
|
thingworx_cache_eviction_count
|
항목이 제거된 횟수
|
|
thingworx_cache_average_load_penalty
|
새 값을 로드하는 데 소요된 평균 시간
|
|
thingworx_cache_weighted_size
|
캐시의 대략적인 현재 크기(바이트)
|
|
thingworx_cache_max_weight
|
제거 전 캐시의 최대 크기(바이트)
|
|
thingworx_cache_estimated_entry_count
|
캐시에 있는 엔트리 수의 추정치
|
|
thingworx_cache_global_max_size
|
모든 캐시에 대해 구성된 전역 최대 크기(바이트)
|
// 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;
이 서비스는 질의된 날짜 범위의 DataTable에 데모 데이터를 자동으로 채웁니다. |
/* 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);
}