CacheThings
CacheThing은 키-값 데이터를 저장하고 읽어들이기 위한 빠른 메모리 내 인터페이스를 제공합니다. 느린 질의와 같이 비용이 많이 들거나 시간이 많이 걸리는 작업의 결과를 캐싱하는 데 이상적입니다.
CacheThing은 메모리에서 작동하므로 ThingWorx를 다시 시작하면 해당 콘텐츠가 지워집니다. 고가용성(HA) 환경에서 각 ThingWorx 노드는 자체의 독립적 캐시를 유지합니다. 따라서 한 노드에서 생성되거나 업데이트된 캐시 항목이 다른 노드와 자동으로 공유되지 않습니다. 여기에는 PurgeCache, DeleteEntryDeleteEntryByKey와 같은 캐시 무효화 호출이 포함됩니다.
CacheThing을 사용하면 다음을 포함한 다양한 유형의 데이터를 캐시할 수 있습니다.
DatabaseThings에 대한 비용이 많이 들거나 빈번한 질의의 결과
ValueStreams 또는 DataTables에 대한 비용이 많이 들거나 빈번한 질의의 결과
느린 네트워크 호출의 결과(예: ContentLoaderFunctions를 사용한 호출)
비용이 많이 드는 계산된 값
FileRepositories에서 자주 사용하는 작은 파일의 콘텐츠
CacheThing을 사용하면 데이터베이스나 네트워크 리소스 같은 외부 시스템에 액세스하는 것에 비해 성능을 크게 향상시키고 리소스 사용량을 줄일 수 있습니다.
사용 지침
CacheThing은 영구 데이터 저장소로 사용할 수 없습니다. CacheThing에 캐시된 데이터는 원래 소스에서 재생성할 수 있어야 합니다.
CacheThing의 데이터는 만료됨 또는 제거됨일 수 있습니다. CacheThing을 사용하는 서비스는 캐시가 결과를 반환하지 않는 경우 원래 소스에서 데이터를 읽어들이도록 설계되어야 합니다. 자세한 내용은 를 참조하십시오.
구성
각 CacheThing은 기본 키가 있는 DataShape로 구성되어야 합니다. 기본 키는 캐시에서 엔트리를 읽어들이는 데 사용됩니다.
복합소재 기본 키는 지원되지만 ByKey 편의 서비스, 만료 정책 및 전역 최대 크기를 사용할 수 없습니다.
만료 정책
만료 정책은 각 캐시 엔트리에 대한 TTL(Time To Live)을 정의합니다. 캐시 엔트리 만료 시간(초) 설정을 사용하여 만료 시간을 구성할 수 있습니다. 엔트리 만료 기간이 지난 후 값을 읽어들이면 캐시에서 삭제된 경우와 유사하게 빈 결과가 반환됩니다.
지원되는 만료 정책:
캐시 엔트리 만료 정책
설명
없음
캐시 엔트리는 캐시에서 소요된 시간에 따라 제거되지 않습니다. 그러나 캐시가 최대 크기 구성에 도달하거나 DeleteEntry 서비스를 사용하면 여전히 제거할 수 있습니다. Never 만료 정책이 구성되면 캐시 엔트리 만료 시간(초) 설정이 무시됩니다.
마지막 액세스 이후 만료 시간
지정된 만료 시간 내에 읽지 않으면 엔트리가 제거됩니다.
작성 이후 만료 시간
엔트리가 생성되고 지정된 시간이 경과하면 엔트리가 제거됩니다.
마지막 수정 이후 만료 시간
지정된 만료 시간 내에 수정되지 않으면 엔트리가 제거됩니다.
마지막 터치 이후 만료 시간
지정된 만료 시간 내에 액세스되지 않거나 수정되지 않으면 엔트리가 제거됩니다.
인터페이스 만료 정책에 대한 자세한 내용은 인터페이스 만료 정책을 참조하십시오.
캐시 최대 크기(제거)
캐시가 캐시 최대 크기(MB) 설정에 지정된 크기에 도달하면 캐시 엔트리가 자동으로 제거(삭제)됩니다. 제거 프로세스는 LFU(가장 자주 사용되지 않음) 엔트리를 제거하여 새 엔트리를 위한 공간을 확보합니다. 엔트리가 제거된 후 이를 다시 읽어들이면 삭제된 경우와 유사하게 빈 결과가 반환됩니다.
큰 엔트리가 추가되는 경우 이를 수용하기 위해 여러 개의 작은 엔트리가 제거될 수 있습니다. 이를 방지하려면 엔트리를 캐시에 추가하기 전에 EstimateEntrySize 서비스를 사용하여 엔트리의 크기를 확인합니다.
캐시 제거 및 만료는 독립적으로 작동합니다.
제거는 캐시 크기 및 사용 빈도를 기준으로 합니다.
만료는 구성된 만료 정책에 정의된 시간을 기준으로 합니다.
전역 최대 크기
플랫폼 하위 시스템에는 ThingWorx 노드당 모든 CacheThing 간에 공유되는 최대 크기(MB) 설정이 포함되어 있습니다. 이 설정은 과도한 메모리 사용을 방지하기 위해 단일 노드에 있는 모든 CacheThings의 결합된 크기를 제한합니다. 비활성화된 CacheThings는 이 제한에 포함되지 않습니다.
이렇게 구성하면 다음 작업이 방지됩니다.
최대 크기를 초과하는 새 CacheThing 생성
최대 크기를 초과하는 CacheThing의 캐시 최대 크기(MB) 업데이트
최대 크기를 초과하는 CacheThings 가져오기(엔티티 가져오기로 또는 확장의 일부로)로 인해 전체 가져오기 실패
모든 CacheThings의 현재 결합된 캐시 최대 크기(MB) 미만으로 최대 크기 축소
전역 제한을 초과하는 CacheThing 활성화
캐시 구성 변경
캐시 구성을 변경하면 내부 일관성을 유지하기 위해 캐시가 자동으로 제거됩니다. 다음 중 하나를 편집하면 캐시가 제거됩니다.
데이터 셰이프 - 구성된 DataShape를 수정하는 작업(필드 추가 또는 제거 등)이 포함됩니다.
캐시 엔트리 만료 정책
캐시 엔트리 만료 시간(초)
캐시 최대 크기(MB)
Cache Performs Loading of Missing Entries
서비스
CacheThing에서 사용할 수 있는 서비스는 다음과 같습니다. 고가용성(HA) 환경에서는 이러한 서비스는 서비스가 실행되는 ThingWorx 노드의 캐시에 영향을 미칩니다.
아래 나열된 *ByKey 편의 서비스는 캐시의 DataShape에 단일 기본 키가 있는 경우에만 사용할 수 있습니다. 캐시의 DataShape에 여러 기본 키가 포함된 경우 서비스의 전체 인포테이블 입력 버전을 사용합니다.
서비스 이름
설명
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를 사용하는 한 행 인포테이블입니다.
LoadEntry 서비스 재정의
Cache Performs Loading of Missing Entries(읽기 캐싱)를 수행하도록 하려면 CacheThing에서 LoadEntry 서비스를 재정의해야 합니다. 이 서비스는 캐시 누락이 발생하면 내부적으로 호출되어 캐시가 소스에서 데이터를 읽어들이고 저장할 수 있도록 합니다.
LoadEntry 서비스를 직접 호출해도 캐시에 엔트리가 추가되지는 않습니다. 엔트리는 캐시 누락 중에 GetEntry에 의해 내부적으로 LoadEntry가 호출되는 경우에만 추가됩니다.
입력
입력은 해당 GetEntry 호출의 매개 변수와 일치하는 행 하나가 있는 인포테이블입니다. 일반적으로 기본 키 필드만 채워집니다.
서비스 구현
LoadEntry 서비스는 데이터 소스에서 캐시될 값을 읽어들이거나 생성해야 합니다. 여기에는 데이터베이스 질의, 웹 서비스 호출 또는 생성된 값이 포함될 수 있습니다. 서비스 내에서 속성 또는 파일과 같은 응용 프로그램 상태를 업데이트하지 마십시오.
LoadEntry 내에서 GetEntry 또는 PutEntry 와 같은 다른 캐싱 서비스를 호출하지 마십시오. 캐시는 캐시 누락 후 반환된 값의 저장을 자동으로 처리합니다.
출력
출력은 입력과 동일한 DataShape를 가진 인포테이블이어야 합니다. 기본 키가 아닌 필드를 읽어들인 데이터로 채웁니다. 발생한 모든 예외는 GetEntry를 호출한 측으로 전파됩니다.
출력 결과 인포테이블에 대한 새 인포테이블을 생성하는 것이 가장 좋습니다. 입력 값 인포테이블을 재사용하고 채울 수는 있지만, 이렇게 하면 인포테이블이 JavaScript에서 특정 기본 유형을 처리하는 방식의 제약으로 인해 예기치 않은 동작이 발생할 수 있습니다. 자세한 내용은 LoadEntry를 사용하여 예제를 참조하십시오.
Read-Through 캐싱
Cache Performs Loading of Missing Entries 확인란을 선택하면 Read-Through 캐싱이 활성화됩니다. 이 모드에서는 캐시 누락이 발생하면 캐시가 CacheThing의 LoadEntry 서비스를 사용하여 누락된 엔트리 값을 자동으로 로드합니다.
이 동작을 지원하려면 CacheThing의 LoadEntry 서비스를 재정의하여 소스 데이터를 질의하고 적절한 값을 반환해야 합니다. GetEntry 서비스가 캐시에서 값을 찾지 못하면 캐시는 LoadEntry 서비스를 호출하여 값을 읽어들입니다.
Read-Through 캐싱은 썬더링 허드 시나리오를 완화하는 데 도움이 될 수 있습니다. 키가 채워지기 전에 동일한 키에 대해 여러 병렬 GetEntry 요청이 수행되는 경우 지원 LoadEntry 서비스는 한 번만 호출됩니다. LoadEntry 호출이 완료되면 보류 중인 모든 GetEntry 요청은 읽어들인 값을 반환합니다.
LoadEntry 서비스 구현에 대한 자세한 내용은 위의 LoadEntry 서비스 재정의 단원을 참조하십시오.
메트릭
여러 캐시 관련 메트릭을 사용할 수 있습니다. 고가용성(HA) 환경에서는 각 노드에 대해 캐시가 독립적입니다. 메트릭은 platformcache_name 레이블을 사용하여 노드별로 처리해야 합니다.
사용 가능한 캐시 메트릭은 다음과 같습니다.
캐시 메트릭
설명
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
모든 캐시에 대해 구성된 전역 최대 크기(바이트)
경고(PTC 클라우드 호스팅 환경에서 사용 가능)
PTC 클라우드 호스팅 환경에서 사용할 수 있는 경고는 다음과 같습니다.
thingworxEntityCacheMissRate - 캐시의 누락률이 1시간 동안 80%를 초과하면 경고합니다.
thingworxEntityCacheEvictions - 캐시의 제거 속도가 30분 동안 80%를 초과하면 경고합니다.
예 및 모범 사례
CacheThing을 효율적으로 사용하는 서비스를 구현하려면 세부 정보에 주의를 기울여야 합니다. 다음과 같은 모범 사례를 고려하십시오.
ThingWorx 노드를 재시작하면 캐시가 지워집니다.
사용자 작업 없이 엔트리가 캐시에서 제거되거나 만료될 수 있습니다. 엔트리가 캐시에 무기한 남아 있을 것이라고 가정하지 마십시오.
캐시 메트릭을 사용하여 캐시 크기 조정이 적절한지 확인합니다.
모든 데이터를 단일 엔트리 아래에 저장하거나 매우 큰 캐시 엔트리를 생성하지 않아야 합니다.
고가용성(HA) 환경에서는 각 노드에 독립적 캐시가 있습니다. 한 노드에서 캐시를 추가, 삭제 또는 제거해도 모든 ThingWorx 노드에 영향을 주지 않습니다.
Cache Performs Loading of Missing Entries가 비활성화된 경우(비 Read-Through 캐싱):
1. 기본 키를 사용하여 캐시에서 엔트리를 읽어들입니다.
2. 결과가 채워졌는지 확인합니다(반환된 인포테이블에 행이 하나 있음).
3. 결과가 있으면 사용합니다.
4. 결과를 찾을 수 없는 경우:
소스 데이터(예: DatabaseThing, DataTable 또는 ContentLoader)에서 필수 엔트리를 가져오거나 생성합니다.
PutEntry를 사용하여 캐시에 엔트리를 추가합니다.
새로 캐시된 엔트리를 반환합니다.
Cache Performs Loading of Missing Entries가 활성화된 경우(Read-Through 캐싱):
1. CacheThing에서 LoadEntry 서비스를 재정의합니다.
2. 기본 키를 사용하여 캐시에서 엔트리를 읽어들입니다.
엔트리가 없으면 LoadEntry가 자동으로 호출되어 값을 채웁니다.
TimesTwo 서비스 사용 예 
TimesTwo 서비스는 숫자를 가져와서 2를 곱한 다음 결과를 캐시에 저장합니다. 이를 설정하려면 다음 단계를 따르십시오.
1. operand: (LONG, primaryKey)timesTwoValue: (LONG) 필드를 사용하여 DataShape(TimesTwoDataShape)를 만듭니다.
2. 구성된 TimesTwoDataShape를 사용하여 CacheThing(MyTimesTwoCache)을 만듭니다.
3. MyTimesTwoCacheTimesTwo 서비스를 추가합니다.
입력 - x(LONG 숫자)
출력 - result(LONG 숫자)
// 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;
}
캐싱 서비스 래퍼 예제 
이 예에서는 QueryImplementingThingsV2 서비스의 결과를 수동으로 캐시하는 방법을 보여줍니다.
1. 다음 필드를 사용하여 Cached_QueryImplementingThingsV2_DataShape라는 데이터 셰이프를 생성합니다.
maxItems: (NUMBER, primaryKey)
nameMask: (STRING, primaryKey)
query: (QUERY, primaryKey)
isSortFirst: (BOOLEAN, primaryKey)
result: (INFOTABLE, DataShape RootEntityListV2)
2. Cached_QueryImplementingThingsV2라는 CacheThing을 생성하고 Cached_QueryImplementingThingsV2_DataShape를 해당 데이터 셰이프로 지정합니다.
3. 캐시 만료 동작을 관찰하려면 Cached_ReadThrough_QueryImplementingThingsV2 사물에서 ExpirationTime을 짧게 구성하고 ExpirationPolicyNever가 아닌 값으로 지정합니다.
4. 다음 매개 변수로 CachingQueryImplementingThingsV2 서비스를 생성합니다.
입력
maxItems: (NUMBER, required)
nameMask: (STRING, required)
query: (QUERY, required)
isSortFirst: (BOOLEAN, required)
출력 result: (INFOTABLE, DataShape RootEntityListV2)
/* 
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"
});
}
예: 캐싱 서비스 래퍼를 Read-Through 캐시로 활용 
이 예에서는 Read-Through 캐시를 사용하여 QueryImplementingThingsV2 서비스의 결과를 자동으로 캐시하는 방법을 보여줍니다. LoadEntry 서비스를 재정의한 후 GetEntry 서비스를 사용하여 QueryImplementingThingsV2 질의 결과를 캐시에 자동으로 로드할 수 있습니다.
1. 다음 필드를 사용하여 Cached_QueryImplementingThingsV2_DataShape라는 데이터 셰이프를 생성합니다.
maxItems: (NUMBER, primaryKey)
nameMask: (STRING, primaryKey)
query: (QUERY, primaryKey)
isSortFirst: (BOOLEAN, primaryKey)
result: (INFOTABLE, DataShape RootEntityListV2)
2. 이름이 Cached_ReadThrough_QueryImplementingThingsV2인 CacheThing을 생성합니다. Cached_QueryImplementingThingsV2_DataShape를 데이터 셰이프로 지정하고 Cache Performs Loading of Missing Entries 옵션을 활성화합니다.
3. 캐시 만료 동작을 관찰하려면 Cached_ReadThrough_QueryImplementingThingsV2 사물에서 ExpirationTime을 짧게 구성하고 ExpirationPolicyNever가 아닌 값으로 지정합니다.
4. 다음 예와 같이 서비스 탭으로 이동하고 LoadEntry 서비스 옆에 있는 재정의를 선택합니다.
/* 
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;
DataTableQuery를 사용한 예 
이 예에서는 작업 중인 BetweenDatesDataTableQuery 서비스를 사용하여 DataTable에서 두 날짜 사이의 가장 큰 값을 찾고 결과를 캐시에 저장하는 방법을 보여줍니다. CachingBetweenDatesDataTableQuery에 대한 고유한 호출은 처음에는 백업 DataTable을 질의하지만, 이후의 동일한 호출에서는 대신 캐시를 사용합니다.
이를 설정하려면 다음 단계를 따르십시오.
1. longKey: (LONG, primaryKey)dateKey: (DATETIME, primaryKey) 필드를 사용하여 DataShape(TimeValueDataShape)를 만듭니다.
2. TimeValueDataShape DataShape를 사용하여 DataTable(DemoDataTable)을 만듭니다.
3. startDate: (DATETIME, primaryKey), endDate: (DATETIME, primaryKey), longValue: (LONG) 필드를 사용하여 DataShape(TimeSpanValueCacheDataShape)를 만듭니다.
4. 구성된 TimeSpanValueCacheDataShape DataShape를 사용하여 CacheThing(CacheThingDemo)을 만듭니다.
5. CacheThingDemo에서 다음 서비스를 만듭니다.
CachingBetweenDatesDataTableQuery - 입력: StartDateEndDateDATETIME으로 지정합니다. 출력: resultLONG으로 지정합니다.
BetweenDatesDataTableQuery - 입력: StartDateEndDateDATETIME으로 지정합니다. 출력: resultLONG으로 지정합니다.
* 
이 서비스는 질의된 날짜 범위의 DataTable에 데모 데이터를 자동으로 채웁니다.
6. 선택 사항. CacheThingDemo에서 Never가 아닌 ExpirationPolicy가 있는 짧은 ExpirationTime을 구성하여 캐시에서 엔트리가 만료되는 것을 관찰합니다.
CachingBetweenDatesDataTableQuery
/* 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"
});
}
BetweenDatesDataTableQuery
/*  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);
}
도움이 되셨나요?