🤖

Momento Cache 4つのコレクションデータ その4 Sorted Sets

2023/11/17に公開

今までの記事で3つのコレクションデータ型を触ってきました。
[Dictionary]
https://zenn.dev/momentobigfun/articles/7bd04650162aed
[List]
https://zenn.dev/momentobigfun/articles/9aed9469796e81
[Sets]
https://zenn.dev/momentobigfun/articles/78303c739c8cda

この記事ではいよいよ最後4つ目のSorted Setsを触っていきます。

Sorted Sets とは

https://docs.momentohq.com/ja/cache/develop/api-reference/sorted-set-collections
ご察しの通り前回のSetsの派生型です。Setsはユニークな要素の文字列による、順序のないコレクションとして定義されていますが、Sorted Setsはその名前の通りユニークな要素の順序付きコレクションです。
追加として、各要素の値はペアとなる符号付き64ビット浮動小数点数を持ち、スコアと呼ばれます。少しイメージしずらいかもしれませんが、やってみればすぐわかります。

さっそくやってみる

ややこしいので一度言葉を整理します。
キー:Sorted Setsのアイテムを保持します。キーは一意の識別子です。
アイテム:キーと紐づくSorted Setsの本体です。
要素:Sorted Setsが保持するデータで、複数のと、ペアとなるスコアを持ちます。

元々がネスト構造化されたデータフォーマットなので、日本語にするのはややこしいですね。

まずは以下を実行します。アイテムの書き込みにはsortedSetPutElementを使います。アイテムが存在していない場合は自動で作ってくれた後、要素を書き込みます。

test.js
// Declare the Momento SDK library
const {
    Configurations,
    CacheClient, CredentialProvider,
    CacheSortedSetPutElement,
} = require('@gomomento/sdk');

// Declate the dotenv library
const dotenv = require('dotenv');

// Run the config function to bring in the .env file
dotenv.config();

// Creates the Momento cache client object
async function createCacheClient() {
    return await CacheClient.create({
        configuration: Configurations.Laptop.v1(),
        credentialProvider: CredentialProvider.fromEnvironmentVariable({
            environmentVariableName: 'MOMENTO_API_KEY',
        }),
        defaultTtlSeconds: 600,
    });
}

// A simple function that calls all functions in order. You probably want more error handling.
async function run() {
    const cacheClient = await createCacheClient();

    const result = await cacheClient.sortedSetPutElement('demo-cache', 'test-sorted-set', 'test-value', 5);
    if (result instanceof CacheSortedSetPutElement.Success) {
        console.log("Value 'test-value' with score '5' added successfully to sorted set 'test-sorted-set'");
    } else if (result instanceof CacheSortedSetPutElement.Error) {
        throw new Error(
            `An error occurred while attempting to call cacheSortedSetPutElement on sorted set 'test-sorted-set' in cache 'test-cache': ${result.errorCode()}: ${result.toString()}`
        );
    }

}
run();
const result = await cacheClient.sortedSetPutElement('demo-cache', 'test-sorted-set', 'test-value', 5);
Value 'test-value' with score '5' added successfully to sorted set 'test-sorted-set'

demo-cacheというキャッシュに、test-sorted-setというキー持つアイテム作成し、test-valueという値が5というスコアを持っています。ややこしいですがJSONにすると簡単です。

SortedSetPutElementsを使うことで複数要素を一度にアイテムに書き込むことが可能です。

test.js
// Declare the Momento SDK library
const {
    Configurations,
    CacheClient, CredentialProvider,
    CacheSortedSetPutElement,
} = require('@gomomento/sdk');

// Declate the dotenv library
const dotenv = require('dotenv');

// Run the config function to bring in the .env file
dotenv.config();

// Creates the Momento cache client object
async function createCacheClient() {
    return await CacheClient.create({
        configuration: Configurations.Laptop.v1(),
        credentialProvider: CredentialProvider.fromEnvironmentVariable({
            environmentVariableName: 'MOMENTO_API_KEY',
        }),
        defaultTtlSeconds: 600,
    });
}
// A simple function that calls all functions in order. You probably want more error handling.
async function run() {
    const cacheClient = await createCacheClient();
    const result = await cacheClient.sortedSetPutElements(
        'demo-cache',
        'test-sorted-set',
        new Map ([
            ['key1', 10],
            ['key2', 20],
        ])
    );
    if (result instanceof CacheSortedSetPutElements.Success) {
        console.log("Elements added successfully to sorted set 'test-sorted-set'");
    } else if (result instanceof CacheSortedSetPutElements.Error) {
        throw new Error(
            `An error occurred while attempting to call cacheSortedSetPutElements on sorted set 'test-sorted-set' in cache 'test-cache': ${result.errorCode()}: ${result.toString()}`
        );
    }
}
run();
    const result = await cacheClient.sortedSetPutElements(
        'demo-cache',
        'test-sorted-set',
        new Map ([
            ['key1', 10],
            ['key2', 20],
        ])
    );

Dictionaryと同じようにMapが使えます。(MomentoのAPIドキュメントではTypescriptになっていたのでJavaScriptに直しています。)
先ほどの結果に対して値とスコアが追記され、アイテムが複数の要素を持っていることがわかります。

Get と Fetch

今までの3つのコレクションデータ型では要素の取得にはFetchを使っていました。Sorted SetsではGetFetchの2種類が明確に異なるき機能として定義されています。
(ちなみに通常のKey-Value型キャッシュデータはGetを使います)
簡単にまとめるとSorted SetsにおけるGetFetchは以下の違いがあります。

Get: 値を指定することでスコアを取り出します。
Fetch:スコアの順位や範囲指定により、値とスコアのペアを取り出します。

SetsとことなりSorted Setsが順序を保証しているのはこの辺りがミソです。
では早速やってみます。まずはGetから。SortedSetGetScoreを使います。

test.js
// Declare the Momento SDK library
const {
    Configurations,
    CacheClient, CredentialProvider,
    CacheSortedSetPutElement,
    CacheSortedSetPutElements,
    CacheSortedSetGetScore,
} = require('@gomomento/sdk');

// Declate the dotenv library
const dotenv = require('dotenv');

// Run the config function to bring in the .env file
dotenv.config();

// Creates the Momento cache client object
async function createCacheClient() {
    return await CacheClient.create({
        configuration: Configurations.Laptop.v1(),
        credentialProvider: CredentialProvider.fromEnvironmentVariable({
            environmentVariableName: 'MOMENTO_API_KEY',
        }),
        defaultTtlSeconds: 600,
    });
}

// A simple function that calls all functions in order. You probably want more error handling.
async function run() {
    const cacheClient = await createCacheClient();

    await cacheClient.sortedSetPutElements(
        'demo-cache',
        'test-sorted-set',
        new Map([
          ['key1', 10],
          ['key2', 20],
        ])
      );

    const result = await cacheClient.sortedSetGetScore('demo-cache', 'test-sorted-set', 'key1');
    if (result instanceof CacheSortedSetGetScore.Hit) {
        console.log(`Element with value 'key1' has score: ${result.score()}`);
    } else if (result instanceof CacheSortedSetGetScore.Miss) {
        console.log("Sorted Set 'test-sorted-set' was not found in cache 'test-cache'");
    } else if (result instanceof CacheSortedSetGetScore.Error) {
        throw new Error(
            `An error occurred while attempting to call cacheSortedSetFetchGetScore on sorted set 'test-sorted-set' in cache 'test-cache': ${result.errorCode()}: ${result.toString()}`
        );
    }
}
run();
const result = await cacheClient.sortedSetGetScore('demo-cache', 'test-sorted-set', 'key1');
Element with value 'key1' has score: 10

key1はスコア10を持っていることがわかります。SortedSetGetScoresでまとめて取り出すことも可能です。

次にSortedSetFetchByRankを試してみます。

test.js
// Declare the Momento SDK library
const {
    Configurations,
    CacheClient, CredentialProvider,
    CacheSortedSetPutElement,
    CacheSortedSetPutElements,
    CacheSortedSetGetScore,
    CacheSortedSetFetch,
} = require('@gomomento/sdk');

// Declate the dotenv library
const dotenv = require('dotenv');

// Run the config function to bring in the .env file
dotenv.config();

// Creates the Momento cache client object
async function createCacheClient() {
    return await CacheClient.create({
        configuration: Configurations.Laptop.v1(),
        credentialProvider: CredentialProvider.fromEnvironmentVariable({
            environmentVariableName: 'MOMENTO_API_KEY',
        }),
        defaultTtlSeconds: 600,
    });
}

// A simple function that calls all functions in order. You probably want more error handling.
async function run() {
    const cacheClient = await createCacheClient();

    await cacheClient.sortedSetPutElements(
        'demo-cache',
        'test-sorted-set',
        new Map ([
          ['key1', 10],
          ['key2', 20],
        ])
      );
      const result = await cacheClient.sortedSetFetchByRank('demo-cache', 'test-sorted-set');
      if (result instanceof CacheSortedSetFetch.Hit) {
        console.log("Values from sorted set 'test-sorted-set' fetched by rank successfully- ");
        result.valueArray().forEach(res => {
          console.log(`${res.value} : ${res.score}`);
        });
      } else if (result instanceof CacheSortedSetFetch.Miss) {
        console.log("Sorted Set 'test-sorted-set' was not found in cache 'test-cache'");
      } else if (result instanceof CacheSortedSetFetch.Error) {
        throw new Error(
          `An error occurred while attempting to call cacheSortedSetFetchByRank on sorted set 'test-sorted-set' in cache 'test-cache': ${result.errorCode()}: ${result.toString()}`
        );
      }
}
run();
const result = await cacheClient.sortedSetFetchByRank('demo-cache', 'test-sorted-set');

スコアの順位順に値が表示されます。

Values from sorted set 'test-sorted-set' fetched by rank successfully- 
key1 : 10
key2 : 20

SortedSetGetRankを使うことで、指定した値の順位が何以下?を知ることもできます。

次にSortedSetFetchByScoreを試してみます。

test.js
// Declare the Momento SDK library
const {
    Configurations,
    CacheClient, CredentialProvider,
    CacheSortedSetPutElement,
    CacheSortedSetPutElements,
    CacheSortedSetGetScore,
    CacheSortedSetFetch,
} = require('@gomomento/sdk');

// Declate the dotenv library
const dotenv = require('dotenv');

// Run the config function to bring in the .env file
dotenv.config();

// Creates the Momento cache client object
async function createCacheClient() {
    return await CacheClient.create({
        configuration: Configurations.Laptop.v1(),
        credentialProvider: CredentialProvider.fromEnvironmentVariable({
            environmentVariableName: 'MOMENTO_API_KEY',
        }),
        defaultTtlSeconds: 600,
    });
}

// A simple function that calls all functions in order. You probably want more error handling.
async function run() {
    const cacheClient = await createCacheClient();
    await cacheClient.sortedSetPutElements(
        'demo-cache',
        'test-sorted-set',
        new Map ([
            ['key1', 100],
            ['key2', 25],
        ])
    );
    const result = await cacheClient.sortedSetFetchByScore('demo-cache', 'test-sorted-set');
    if (result instanceof CacheSortedSetFetch.Hit) {
        console.log("Values from sorted set 'test-sorted-set' fetched by score successfully- ");
        result.valueArray().forEach(res => {
            console.log(`${res.value} : ${res.score}`);
        });
    } else if (result instanceof CacheSortedSetFetch.Miss) {
        console.log("Sorted Set 'test-sorted-set' was not found in cache 'demo-cache'");
    } else if (result instanceof CacheSortedSetFetch.Error) {
        throw new Error(
            `An error occurred while attempting to call cacheSortedSetFetchByScore on sorted set 'test-sorted-set' in cache 'test-cache': ${result.errorCode()}: ${result.toString()}`
        );
    }
}
run();
Values from sorted set 'test-sorted-set' fetched by score successfully- 
key2 : 25
key1 : 100

またオプションパラメーターでminScore,maxScoreが準備されており、指定した範囲のスコアを持つ値を出力することが可能です。orderにより昇順、降順の指定も可能になっています。

sortedSetPutElementで値とペアとなるスコアを増やしたり減らしたりすることが可能です。

test.js
// Declare the Momento SDK library
const {
    Configurations,
    CacheClient, CredentialProvider,
    CacheSortedSetPutElement,
    CacheSortedSetPutElements,
    CacheSortedSetGetScore,
    CacheSortedSetFetch,
    sortedSetPutElement,
    CacheSortedSetIncrementScore,
} = require('@gomomento/sdk');

// Declate the dotenv library
const dotenv = require('dotenv');

// Run the config function to bring in the .env file
dotenv.config();

// Creates the Momento cache client object
async function createCacheClient() {
    return await CacheClient.create({
        configuration: Configurations.Laptop.v1(),
        credentialProvider: CredentialProvider.fromEnvironmentVariable({
            environmentVariableName: 'MOMENTO_API_KEY',
        }),
        defaultTtlSeconds: 600,
    });
}

// A simple function that calls all functions in order. You probably want more error handling.
async function run() {
    const cacheClient = await createCacheClient();

    await cacheClient.sortedSetPutElement('demo-cache', 'test-sorted-set', 'test-value', 10);
    const result = await cacheClient.sortedSetIncrementScore('demo-cache', 'test-sorted-set', 'test-value', 1);
    if (result instanceof CacheSortedSetIncrementScore.Success) {
        console.log(`Score for value 'test-value' incremented successfully. New score - ${result.score()}`);
    } else if (result instanceof CacheSortedSetIncrementScore.Error) {
        throw new Error(
            `An error occurred while attempting to call cacheSortedSetIncrementScore on sorted set 'test-sorted-set' in cache 'test-cache': ${result.errorCode()}: ${result.toString()}`
        );
    }
}
run();
await cacheClient.sortedSetPutElement('demo-cache', 'test-sorted-set', 'test-value', 10);
const result = await cacheClient.sortedSetIncrementScore('demo-cache', 'test-sorted-set', 'test-value', 1);

'test-value'という値とペアとなる10というスコアを持つ要素を作成しています。
その後sortedSetIncrementScoreで1を足しています。

Score for value 'test-value' incremented successfully. New score - 11

マイナスの値を指定することで数字を減らすことも可能です。
これは処理の高速化とコードの簡素化を実現します。通常キャッシュ内の値を増減させる場合以下のステップを踏みます。
1.値の取り出し
2.値に対する数字の修正
3.値の上書き
これを1つの関数でまとめて行うことが可能です。

削除はいつも通りキーを指定して通常のアイテムとして削除が可能です。

Discussion