📌

Momento 入門 その1:Getting Started を Amazon Linux 2023でやってみる。

2023/09/02に公開

最近はやっているMomento、デベロッパーが真に欲しかった完全サーバレス型のフルマネージドキャッシュとして多くのデベロッパーがこぞって検証を始めているようです。このアカウントではそのMomentoを勉強していきます。

キャッシュとは?

もちろんお金のことではありません。一般的にコンピューター上で行われるさまざまな処理はキャッシュといわれるもので高速化されます
Web系のシステムであれば、ブラウザがDNS名を解決する際のDNSキャッシュ、コンテンツの読み込みを高速化させる、ブラウザキャッシュなどです。バックエンドであればアプリケーションからデータベースへの接続を効率化させるコネクションプーリングなどもキャッシュの一部と言えるかもしれません。
尚、お金のキャッシュは語尾が上がり➚、コンピューターのキャッシュは語尾が下がります➘。

では一般的なウェブ系のシステムで最も遅い部分はどこでしょうか。CPU、メモリ、ストレージ、ネットワークなどパフォーマンスに影響を与える部分は多く存在します。その中でも一番遅延が激しいのはストレージです。ストレージはデータの永続性を担いますから、書き込みという工程が発生します。その仕組み上データは安全に格納されますが、このため読み込み時においても、その他揮発性の高いCPUやメモリより多くのオーバーヘッドが発生します。このためデータベースが一番パフォーマンスを左右させます。

この問題を解決するため、多くのデータベース、例えばAmazon DynamoDBや Amazon Auroraはデータのキャッシュ機能を内部に包含し、よく呼び出されるデータをキャッシュさせることで、リクエストに対するパフォーマンスを向上させます。一方それらのデータベースキャッシュはクライアントアプリケーションから見た場合、フロントエンドアプリケーションから離れた別の筐体に存在していますのでデータの伝搬遅延などを考慮する必要があります。フロントエンドアプリケーションからしてみれば、ネットワーク通信を伴わない、ないしはデータベースより、より近しい場所にデータの一時保存場所があるほうが処理は高速化されます。それを実現する有名どころとしてはRedisやMemcachedというものが存在します。インメモリにデータを一時的に蓄積することでデータベースより高速に動作します。

AWSではこれらのマネージドサービスとしてAmazon ElastiCacheやAmazon MemoryDB for Redisといったものを提供していますが、クラスターという概念が存在しています。クラスターというのはその機能を支えるサーバ群であり、開発者がそのサーバ群を占有できるということです。これにはメリットとデメリットが伴います。サーバ群を占有できるということは、パフォーマンスなどかなり細かいコントロールを維持することができます。一方デメリットとしてサーバ群を支える個別サーバの障害の影響を受けます。

そのような運用上必要となる考慮を、より使いやすくフルマネージドのサーバレス型で提供しているのがMomentoです。圧倒的な使いやすさにより多くの開発者ファンを日本でも増やしているようで、ユーザーコミュニティも立ち上がっています。

やってみる

長々と書いてしまいましたがそれではさっそくやってみます。Momentoはドキュメントが最初から日本語で準備されているので、そのGetting Startedに従い作業していきます。
手順はこれです。
https://docs.momentohq.com/ja/getting-started

まずコンソールにアクセスします。

ログインはGoogleアカウントかGitHubアカウントどちらかが必要です。

まず、トークンの生成を押します。

トークンとはMomentoキャッシュにアクセス可能な認証識別子です。AWSでいうところのIAM Credentialと思ってください。
まずはAWSの東京リージョンにMomento Cacheを作ってみます。

Super User Tokenを選びます。

Fine-Grades Access Tokenの場合、細かい制御が可能なトークンを発行できますが、今回は一旦置いておきます。
トークンを生成するボタンを押します。

いろいろな文字列が出力されますが、JSONをダウンロードしておきます。

{
  "authToken": <文字列>,
  "refreshToken":<文字列>,
  "validUntil": 1696232296,
  "restEndpoint": "https://api.cache.cell-ap-northeast-1-1.prod.a.momentohq.com"
}

authTokenとrefreshTokenの2つがあります。データの操作で用いるのはauthTokenです。今回は
Super User Tokenで作成しましたので、キャッシュ本体の作成、書き込み、読み込みなどすべての操作が可能です。Momentoでは、このトークンには有効期限をセットし定期的に入れ替えることを推奨しています。その際、トークンの入れ替えリクエスト認証に用いるのがrefreshTokenです。

ではこれを開発環境に組み込んでいきます。開発環境はAmazon EC2 / Linux2023を使います。以下のコマンドでライブラリをインストールします。
まずはnvmなど必要ツールを整えます。

$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash

その後ターミナルに入りなおします。(OSの再起動は不要です)

nvm install 18
npm install @gomomento/sdk
npm install dotenv

次に先ほどダウンロードしたトークンの環境変数としてセットします。

export MOMENTO_AUTH_TOKEN=<your Momento token here>
export MOMENTO_TTL_SECONDS=300

次にMomentoが準備してくれているテストスクリプトをtest.jsで作成します。

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

// 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();

// Creates the Momento cache client object
async function createCacheClient() {
    return await CacheClient.create({
        configuration: Configurations.Laptop.v1(),
        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);
    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();

ファイルを保存したらnode test.jsで実行します。少しだけ待つと以下が出力されます、
この時点でdemo-cacheというこのアカウント専用のキャッシュ空間が作成され、テスト用に12345というデータを書き込んでいます。

[2023-09-02T07:53:07.337Z] INFO (Momento: CacheClient): Creating Momento CacheClient
[2023-09-02T07:53:07.697Z] INFO (Momento: ControlClient): Creating cache: demo-cache
Cache created.
Found caches:
 - demo-cache
Key stored successfully!
Cache hit:  12345

キャッシュは原則Key Value型で動作します。テストスクリプトを見てみると

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");
}

という部分があり、codeというKEYに対して12345というValueが書き込まれていることがわかります。試しにこの値を少し変えてみるとこうなります。

await writeToCache(cacheClient, CACHE_NAME, "code", "hello from momento");
Found caches:
 - demo-cache
Key stored successfully!
Cache hit:  hello from momento

キャッシュはSQLと異なりデフォルトではUpdateやInsertの区別なく動作することに注意してください。(データがあれば上書き、なければ新規作成)
また実際にAmazon ElastiCacheなどを利用したことのある方はこの新規Cache構築のスピードに驚くかもしれません。これはクラスターという概念をフルマネージド型サーバレスで抽象化した一つのメリットといえます。

技術的には、従来のクラスター型がサーバ起動&ミドルウェアインストールから始まるのに対し、Momentoではすでに存在している基盤にアカウント専用キャッシュを論理的に作成している様に思われます。

最後に

いかがでしたでしょうか。このアカウントでいろいろ検証を行いつつブログを上げていきたいと思います。

Discussion