CacheThing
CacheThing は、キーと値のデータを保存および取得するための迅速かつ効率的なインタフェースを提供します。これは、低速のクエリーなど、コストが高い操作や時間のかかる操作のキャッシュに最適です。
* 
CacheThing はメモリ内キャッシュであるため、ThingWorx を再起動した場合は再構築する必要があります。高可用性 (HA) システムでは、各 ThingWorx ノードに独立したキャッシュがあるため、あるノードで設定または更新されたキャッシュエントリがほかのノードで自動的に更新されることはありません。
CacheThing を使用して、次のようなさまざまなタイプのデータをキャッシュできます。
データベース Thing に対する高コストまたは高頻度のクエリーの結果
値ストリームまたはデータテーブルに対する高コストまたは高頻度のクエリーの結果
低速なネットワーク呼び出しの結果 (ContentLoaderFunctions での呼び出しなど)
高コストの計算値
頻繁に使用され、非常に小さい FileRepositories のファイルのコンテンツ
CacheThing はメモリ内で動作するため、データベースやネットワークリソースなどの外部システムにアクセスする場合と比較して、結果出力が大幅に高速化され、リソース消費が低減します。
使用上のガイドライン
CacheThing は永続的なデータストアとして扱うべきではありません。CacheThing にキャッシュされているデータはすべて、元のソースから再作成できる必要があります。
CacheThing 内のデータは、期限切れまたは追い出しのいずれかの状態です。CacheThing を使用するサービスは、キャッシュが空の結果を返した場合にソースデータを読み取ることができる必要があります。詳細については、を参照してください。
コンフィギュレーション
CacheThing はプライマリキーを持つデータシェイプで設定する必要があります。プライマリキーは、エントリをキャッシュからフェッチするために使用されます。
* 
複合プライマリキーを使用することはできますが、これを使用すると、以下にリストされている *ByKey コンビニエンスサービスを使用できなくなります。
有効期限ポリシー
有効期限ポリシーは、キャッシュ内の各エントリの有効期間 (TTL) を決定します。有効期限は、Cache Entry Expiration Time (Seconds) 設定を使用して指定されます。エントリの有効期限が経過した後にその値を取得すると、キャッシュから削除された場合と同様に、空の結果が返されます。
次のコンフィギュレーションが可能です。
キャッシュエントリの有効期限ポリシー
説明
なし
キャッシュエントリは、キャッシュ内での滞在時間の長さに基づいて除去されることはありません。ただし、キャッシュが最大サイズコンフィギュレーションに達した場合の追い出しによって、または DeleteEntry サービスの使用によって除去される可能性は残っています。有効期限ポリシーが Never として設定されている場合、Cache Entry Expiration Time (Seconds) 設定は無視されます。
最後のアクセス以来の有効期限 (時間)
指定された有効期限内にエントリが読み取られなかった場合に、そのエントリはキャッシュから除去されます。
作成以来の有効期限 (時間)
指定された有効期限が経過すると、エントリはキャッシュから除去されます。
最終修正後の有効期限 (時間)
指定された有効期限内に修正されなかった場合、エントリは除去されます。
最後に使用して以来の有効期限 (時間)
指定された有効期限内にアクセスまたは修正されなかった場合、エントリは除去されます。
インタフェースの有効期限ポリシーの詳細については、「Interface ExpiryPolicy」を参照してください。
キャッシュ最大サイズ (追い出し)
エントリは、Cache Maximum Size (MB) 設定で指定されたサイズに達すると、キャッシュから自動的に追い出され (削除され) ます。より古く、使用頻度が最も低い (LFU) エントリが、新しいエントリ用のスペースを確保するために追い出されます。エントリが追い出された後にその値を取得すると、キャッシュから削除された場合と同様に、空の結果が返されます。
大きなエントリがキャッシュに追加される場合、そのスペースを確保するために複数の小さなエントリが追い出されることがあります。
非常に大きなエントリによって多数の小さなエントリの追い出しが発生するのを回避するために、EstimateEntrySize サービスを使用して、キャッシュに追加する前に潜在的なエントリのサイズを確認できます。
キャッシュの追い出しと有効期限は独立して動作し、必要に応じてキャッシュからエントリを除去します。追い出しのメカニズムは、設定されているキャッシュサイズと、当該エントリとほかのエントリの使用頻度の比較に基づきます。有効期限のメカニズムは、設定された有効期限ポリシーに応じた経過時間に基づきます。
グローバル最大サイズ
プラットフォームサブシステムの Maximum Size Shared between all CacheThings per ThingWorx Node (MB) コンフィギュレーションによって、すべての CacheThing の Cache Maximum Size (MB) の合計サイズが制限されます。この保護機構は、誤って設定された CacheThing によって ThingWorx のすべてのシステムメモリが誤って消費されるのを防ぐように設計されています。非アクティブ (無効) の CacheThing は、この制限にカウントされません。
このコンフィギュレーションにより、以下が防止されます。
最大サイズを超える新規 CacheThings を作成する
CacheThing の Cache Maximum Size (MB) を最大サイズを超えるサイズに更新する
(エンティティのインポートとして、または拡張の一部として) 最大サイズを超える CacheThing をインポートする (インポート全体が失敗します)
最大サイズを、現在のすべての CacheThing の Cache Maximum Size (MB) の合計を下回るサイズに減らす
グローバル制限を超える CacheThing をアクティブ化 (有効化) する
キャッシュコンフィギュレーションの変更
キャッシュのコンフィギュレーションに変更を加えると、内部の一貫性を維持するためにキャッシュが自動的にパージされます。以下のいずれかを編集すると、キャッシュがパージされます。
DataShape - これには、現在設定されているデータシェイプ自体の編集 (フィールドの追加や除去など) が含まれます。
Cache Maximum Size (MB)
Cache Entry Expiration Policy
Expiration Time (Seconds)
サービス
CacheThing では次のサービスを使用できます。高可用性 (HA) 環境では、各サービスが実行されている ThingWorx ノード上のキャッシュに影響を及ぼします。
以下にリストされている *ByKey コンビニエンスサービスは、キャッシュのデータシェイプが単一のプライマリキーを持っている場合にのみ使用できます。キャッシュのデータシェイプが複数のプライマリキーで設定されている場合は、サービスの完全なインフォテーブル入力バージョンを使用します。
サービス名
説明
PutEntry
キャッシュにエントリを追加します。
入力値 - CacheThing の設定済みデータシェイプを使用した 1 行のインフォテーブル。
GetEntry
キャッシュから 1 行のインフォテーブル結果を返します。エントリがキャッシュ内に見つからない (キャッシュミス) 場合、空のインフォテーブルが返されます。キャッシュミスは、エントリがキャッシュに追加されていない場合、追い出された場合、または期限切れになった場合に発生する可能性があります。
入力値 - CacheThing の設定済みデータシェイプを使用した 1 行のインフォテーブル。Primary Key 値のみを指定する必要があります。
GetEntryByKey
キャッシュから 1 行のインフォテーブル結果を返します。キャッシュミスの場合、空のインフォテーブルが返されます。キャッシュミスは、エントリがキャッシュに追加されていない場合、追い出された場合、または期限切れになった場合に発生する可能性があります。
入力値 - 取得するエントリのプライマリキーの文字列バージョン。これは文字列のプライマリキーで最適に機能しますが、明確な文字列変換に対応したほとんどのタイプでも機能します。このサービスは、設定されたデータシェイプが単一のプライマリキーを持つ場合にのみ使用できます。
DeleteEntry
キャッシュからエントリを削除します。
入力値 - CacheThing の設定済みデータシェイプを使用した 1 行のインフォテーブル。Primary Key 値のみを指定する必要があります。
DeleteEntryByKey
キャッシュからエントリを削除します。
入力値 - 取得するエントリのプライマリキーの文字列バージョン。これは文字列のプライマリキーで最適に機能しますが、明確な文字列変換に対応したほとんどのタイプでも機能します。このサービスは、設定されたデータシェイプが単一のプライマリキーを持つ場合にのみ使用できます。
PurgeCache
すべてのキャッシュエントリを除去します。
GetDataShape
キャッシュの設定済みデータシェイプを返します。
SetDataShape
キャッシュのコンフィギュレーションテーブルのデータシェイプフィールドを設定します。この値を変更すると、キャッシュが自動的にパージされます。
GetEstimatedEntryCount
キャッシュ内の推定エントリ数を取得します。実行中の PUT サービスや DELETE サービスがあると、カウントが不正確になる可能性があります。これは、期限切れプロセスや追い出しプロセスが実行されている場合も同様です。
EstimateEntrySize
エントリがキャッシュに追加された場合に使用されるサイズを返します。これによってエントリがキャッシュに追加されるわけではなく、キャッシュ内にすでにエントリが存在している必要もありません。このサービスは、適切な Cache Maximum Size (MB) を推定したり、大きなエントリがキャッシュに追加されるのを回避したりするのに役立ちます。
入力値 - CacheThing の設定済みデータシェイプを使用した 1 行のインフォテーブル。
判定基準
キャッシュ関連の判定基準がいくつか用意されています。高可用性 (HA) 環境では、キャッシュはノードごとに独立しています。したがって、キャッシュ判定基準は、platform ラベルと cache_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) 環境では、各ノードに独立したバッキングキャッシュがあります。1 つのノードでキャッシュを追加、削除、パージしても、すべての ThingWorx ノードに影響するわけではありません。
キャッシュを使用する際の標準パターンは次のとおりです。
1. プライマリキーを使用してキャッシュからエントリを取得します。
2. 結果が取り込まれているかどうか (返されたインフォテーブルに 1 行あるかどうか) を確認します。
3. キャッシュからの結果が存在する場合は、それを使用します。
4. キャッシュからの結果が存在しない場合は、次の手順を実行します。
ソースデータ (データベース Thing、データテーブル、コンテンツローダーなど) から必要なエントリをフェッチまたは生成します。
エントリをキャッシュに追加します。
ここでキャッシュされたエントリを返します。
TimesTwo サービスの使用例 
TimesTwo サービスは、数値を受け取り、その数値を 2 倍にし、その結果をキャッシュに保存します。これを設定するには、次の手順を実行します。
1. operand: (LONG, primaryKey) フィールドと timesTwoValue: (LONG) フィールドを含むデータシェイプ (TimesTwoDataShape) を作成します。
2. 設定した TimesTwoDataShape を含む CacheThing (MyTimesTwoCache) を作成します。
3. TimesTwo サービスを MyTimesTwoCache に追加します。
入力 - 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;
}
DataTableQuery の使用例 
この例では、動作している BetweenDatesDataTableQuery サービスを使用して、2 つの日付間のデータテーブル内の最大値を見つけて、結果をキャッシュに保存する方法を示します。CachingBetweenDatesDataTableQuery の一意の呼び出しでは、最初はバッキングデータテーブルに対してクエリーが実行されますが、それ以降の同じ呼び出しでは、代わりにキャッシュが使用されます。
これを設定するには、次の手順を実行します。
1. longKey: (LONG, primaryKey) フィールドと dateKey: (DATETIME, primaryKey) フィールドを含むデータシェイプ (TimeValueDataShape) を作成します。
2. TimeValueDataShape データシェイプを含むデータテーブル (DemoDataTable) を作成します。
3. startDate: (DATETIME, primaryKey) フィールド、endDate: (DATETIME, primaryKey) フィールド、および longValue: (LONG) フィールドを含むデータシェイプ (TimeSpanValueCacheDataShape) を作成します。
4. 設定した TimeSpanValueCacheDataShape データシェイプを含む CacheThing (CacheThingDemo) を作成します。
5. CacheThingDemo に次のサービスを作成します。
CachingBetweenDatesDataTableQuery - 入力: StartDateEndDate (DATETIME 型)。出力: result (LONG 型)
BetweenDatesDataTableQuery - 入力: StartDateEndDate (DATETIME 型)。出力: result (LONG 型)
* 
このサービスは、クエリー対象の日付範囲のデータテーブルに、デモデータを自動的に取り込みます。
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);
}
これは役に立ちましたか?