📖

Cache 書き込みの圧縮について

2024/05/17に公開

公式ドキュメントにCache書き込みを行う際の圧縮オプションへの記載が増えていたので試しました。
https://docs.momentohq.com/sdks/nodejs/compression
Topからは遷移がまだないのでそのうち画面遷移ができると期待しています。
https://github.com/momentohq/client-sdk-javascript/tree/main/examples/nodejs/compression
に公式サンプルも準備されています。

サポートしているアルゴリズムとオペレーション

gzip形式とzstd形式の2種類をサポートしているようです。zstdはまだあまり聞きなじみがないかもしれないですがzipと同等の圧縮率を実現しながらより高速な圧縮・解凍を実現します。スピードにこだわるMomentoらしいサポートだと思います。
https://ja.wikipedia.org/wiki/Zstandard

さっそくやってみる。

過去の記事を参考にtest.jsが実行できるところまで準備します。
https://zenn.dev/momentobigfun/articles/aa24ff7817e06c

次にライブラリをインストールします。

gzip
npm install @gomomento/sdk-nodejs-compression
npm install @gomomento/sdk-nodejs-compression-zstd

この記事では以下gzipを使います。

ではtest.jsを修正していきます。

const {CompressorFactory} = require('@gomomento/sdk-nodejs-compression');

で宣言を行います。
その後いつも通りCache Clientを作成しますが、その際に引き渡すConfigurationパラメータを以下のように設定します。

作業前
 configuration: Configurations.Laptop.v1(),
作業後
const configuration = Configurations.Laptop.latest().withClientTimeoutMillis(90000).withCompressionStrategy(
  // この設定は圧縮を有効にし、サポートされている操作のために圧縮された値を自動的に解凍します。
  // 自動解凍を望まない場合は、compression strategyにautomaticDecompression: AutomaticDecompression.Disabledを追加します。
  {
    compressorFactory: CompressorFactory.default(),
  }
);
<snip>
configuration: configuration,

圧縮されてMomento Cacheへ書き込まれたものは、デフォルトでは読み込み時に自動解凍されます。それを行いたくない場合は、automaticDecompression: AutomaticDecompression.Disabledを追記することで圧縮された状態でアイテムがGetされます。
ではsetオペレーションを以下に書き換えます。

const setResponse = await client.set(cacheName, key, data);
const setResponse = await client.set(cacheName, key, data, {compress: true});

一度これでtest.jsを実行します。圧縮した状態で書き込まれ、読み込み時に自動で解凍されますので結果は何も変わりません。ただしマネージメントコンソール上では以下のように圧縮された値が保存されていることがわかります。

AutomaticDecompressionを有効化すると以下のようにバイナリがやり取りされていることがわかります。

node test.js
Cache already exists
Found caches:
 - demo-cache
 - default-cache
Key stored successfully!
Cache hit:  �
34261:��

テストコードは以下です。

// Declare the Momento SDK library
const {
  CacheGet, CacheSet, Configurations, ListCaches, CreateCache,
  CacheClient, CredentialProvider,AutomaticDecompression
} = require('@gomomento/sdk');

const {CompressorFactory} = require('@gomomento/sdk-nodejs-compression');

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

// Defines name of cache to use.
const CACHE_NAME = 'demo-cache';

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

const configuration = Configurations.Laptop.latest().withClientTimeoutMillis(90000).withCompressionStrategy(
  // この設定は圧縮を有効にし、サポートされている操作のために圧縮された値を自動的に解凍します。
  // 自動解凍を望まない場合は、compression strategyにautomaticDecompression: AutomaticDecompression.Disabledを追加します。
  {
    compressorFactory: CompressorFactory.default(),
    //compressionLevel: CompressionLevel.SmallestSize,
    automaticDecompression: AutomaticDecompression.Disabled,
  }
);

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

// Create a new cache
async function createCache(client) {
  const createCacheResponse = await client.createCache(CACHE_NAME);
  if (createCacheResponse instanceof CreateCache.Success) {
      console.log('Cache created.');
  } else if (createCacheResponse instanceof CreateCache.AlreadyExists) {
      console.log('Cache already exists');
  } else if (createCacheResponse instanceof CreateCache.Error) {
      throw createCacheResponse.innerException();
  }
}

// List all caches in Momento for this account.
async function listCaches(client) {
  const listResponse = await client.listCaches(client);
  if (listResponse instanceof ListCaches.Error) {
      console.log('Error listing caches: ', listResponse.message());
  } else if (listResponse instanceof ListCaches.Success) {
      console.log('Found caches:');
      listResponse.getCaches().forEach(cacheInfo => {
          console.log(' -', cacheInfo.getName());
      });
  } else {
      throw new Error('Unrecognized response: ', listResponse.toString());
  }
}

// A function to write to the cache
async function writeToCache(client, cacheName, key, data) {
  const setResponse = await client.set(cacheName, key, data, {compress: true});
  if (setResponse instanceof CacheSet.Success) {
      console.log('Key stored successfully!');
  } else if (setResponse instanceof CacheSet.Error) {
      console.log('Error setting key: ', setResponse.toString());
  } else {
      console.log('Some other error: ', setResponse.toString());
  }
}

// A function to read scalar items from the cache
async function readFromCache(client, cacheName, key) {
  const readResponse = await client.get(cacheName, key);
  if (readResponse instanceof CacheGet.Hit) {
      console.log('Cache hit: ', readResponse.valueString());
  } else if (readResponse instanceof CacheGet.Miss) {
      console.log('Cache miss');
  } else if (readResponse instanceof CacheGet.Error) {
      console.log('Error: ', readResponse.message());
  }
}

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

  await createCache(cacheClient);

  await listCaches(cacheClient);

  await writeToCache(cacheClient, CACHE_NAME, "code", "12345");
  await readFromCache(cacheClient, CACHE_NAME, "code");
}

run();

CompressionLevelgzipでは利用しません。複数段階の圧縮アルゴリズムをサポートしているzstd専用オプションです。

Discussion