🐥

Momento Cache 4つのコレクションデータ その2 List

2023/11/04に公開

今日は前回の記事に引き続いて2つめのコレクションデータ側であるListをやっていきます。
前回の記事はこちらです。
https://zenn.dev/momentobigfun/articles/7bd04650162aed

List とは

順序付けられた要素のコレクションで、各要素が挿入された順序でソートです。前回のDictionaryでは順番は保証されておらず、時と場合よって順番はばらばらにアイテムの中に格納されているFieldが出力されていましたが、Listは順番を挿入された順で維持するようです。

さっそくやってみる

まずtest.jsを以下に変更します。

test.js
// Declare the Momento SDK library
const {
  Configurations,
  CacheClient, CredentialProvider,
  CacheListPushFront
} = 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.listConcatenateFront('demo-cache', 'test-list', ['a', 'b', 'c']);
  const result = await cacheClient.listPushFront('demo-cache', 'test-list', 'x');
  if (result instanceof CacheListPushFront.Success) {
    console.log("Value 'x' added successfully to front of list 'test-list'");
  } else if (result instanceof CacheListPushFront.Error) {
    throw new Error(
      `An error occurred while attempting to call cacheListPushFront on list 'test-list' in cache 'test-cache': ${result.errorCode()}: ${result.toString()}`
    );
  }
}
run();

このサンプルでは

  await cacheClient.listConcatenateFront('demo-cache', 'test-list', ['a', 'b', 'c']);

demo-cacheというキャッシュ空間にtest-listというキー名でアイテムを作成し、その値が['a', 'b', 'c']となっています。
その後cacheClient.listPushFront関数で先頭にxを追加しています。

const result = await cacheClient.listPushFront('demo-cache', 'test-list', 'x');


反対にリストの末尾にデータを追記するのは

const result = await cacheClient.listPushBack('demo-cache', 'test-list', 'y');

です。

const result = await cacheClient.listRemoveValue('demo-cache', 'test-list', 'b');

で特定の値を持つ要素をListアイテムから削除させることもできます。

先頭の要素を取り出すのは以下です。

const result = await cacheClient.listPopBack('demo-cache', 'test-list');
test.js
const {
  Configurations,
  CacheClient, CredentialProvider,
  CacheListPushFront,
  CacheListPopFront
} = require('@gomomento/sdk');
<snip>
  await cacheClient.listConcatenateFront('demo-cache', 'test-list', ['a', 'b', 'c']);
  const result = await cacheClient.listPopFront('demo-cache', 'test-list');
  if (result instanceof CacheListPopFront.Hit) {
    console.log(`First value was removed successfully from list 'test-list': ${result.valueString()}`);
  } else if (result instanceof CacheListPopFront.Miss) {
    console.log("List 'test-list' was not found in cache 'test-cache'");
  } else if (result instanceof CacheListPopFront.Error) {
    throw new Error(
      `An error occurred while attempting to call cacheListPopFront on list 'test-list' in cache 'test-cache': ${result.errorCode()}: ${result.toString()}`
    );
  }
<snip>
First value was removed successfully from list 'test-list': a

同様にListPopBackでは最後の要素を取り出すことが出来ます。

まとめて要素をアイテムに書き込むことも可能です。

const {
  Configurations,
  CacheClient, CredentialProvider,
  CacheListPushFront,
  CacheListPopFront,
  CacheListConcatenateFront
} = require('@gomomento/sdk');
<snip>
  await cacheClient.listConcatenateFront('demo-cache', 'test-list', ['a', 'b', 'c']);
  const result = await cacheClient.listConcatenateFront('demo-cache', 'test-list', ['x', 'y', 'z']);
  if (result instanceof CacheListConcatenateFront.Success) {
    console.log(`Values added successfully to the front of the list 'test-list'. Result - ${result.toString()}`);
  } else if (result instanceof CacheListConcatenateFront.Error) {
    throw new Error(
      `An error occurred while attempting to call cacheListConcatenateFront on list 'test-list' in cache 'test-cache': ${result.errorCode()}: ${result.toString()}`
    );
  }
<snip>

x,y,z,a,b,cになります。

要素を専用ではなく末尾に追記する場合はListConcatenateBackを使えます。

Dictionary と どちらが早いのか?

前述の通りDictionaryは順番を維持しませんが、Listは挿入順を維持します。どちらの方が高速に動作するのか、気になったのでやってみました。

まずはListで100要素書き込みを行ってみます。

// Declare the Momento SDK library
const {
  Configurations,
  CacheClient, CredentialProvider,
  CacheListPushFront
} = 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();

  cacheClient.delete('demo-cache', 'test-dictionary');
  await cacheClient.listConcatenateFront('demo-cache', 'test-list', ['a', 'b', 'c']);

  let start = performance.now();
  const loopTime = 100;
  for (let i = 0; i < loopTime; i++) {
    const result = await cacheClient.listPushBack('demo-cache', 'test-list', 'x');
  }
  let end = performance.now();
  let duration = end - start;
  console.log('実行時間 = ' + duration + 'ミリ秒');
}
run();

1アイテムあたり8ms程度でした。なお標準系アイテムの読み書きは約2~4msでしたので少し時間はかかるそうですが、これは自然で当たり前のことに思えます。
また、listConcatenateFrontの代わりにlistConcatenateBackを使っても同じでしたし、 listPopFrontlistPopBackでも同じでした、読み書きでパフォーマンスが同じなのはキャッシュのMomentoならではでしょう。

では次にDictionaryも同様に100回書き込みを行います。

test.js
// Declare the Momento SDK library
const {
  Configurations,
  CacheClient, CredentialProvider,
  CacheListPushFront
} = 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.delete('demo-cache', 'test-dictionary');
  await cacheClient.dictionarySetField('demo-cache', 'test-dictionary', 'test-field', 'test-value');

  let start = performance.now();
  const loopTime = 100;
  for (let i = 0; i < loopTime; i++) {
    await cacheClient.dictionarySetField('demo-cache', 'test-dictionary', i, i);
  }
  let end = performance.now();
  let duration = end - start;
  console.log('実行時間 = ' + duration + 'ミリ秒');
}
run();

少し不思議なことが起きました。実際の書きこみは9ms程度と、ほとんどListと同様ですが、10回に1回程度2~3倍のレイテンシが発生します。これは技術特性としてDictionary利用時に抑えておいた方がよさそうです。
では読み込みはどうでしょうか。こちらも9ms程度と少しだけListより時間がかかりながら、同様にたまに長いレイテンシが発生しています。

結論と使い分け

List型は順番を維持する必要があるためアプリケーション側の考慮が必要です。Dictionary型は順番を意識する必要がないため雑多なデータストアとして利用可能です。開発上便利なのはDictionary型ですが、アプリケーションで考慮しないといけない分、少しだけList型が高速と言えます。

Discussion