デジタルオーシャンアプリプラットフォームでExpressアプリケーションをデプロイし、MemCachierでスケールする方法
筆者は「書いて寄付するプログラム」の一環として、寄付金を受け取るために「Free and Open Source Fund」を選びました。
ご挨拶
Expressは、Nodeを使用して高速なWebアプリやAPIを構築するための人気のあるフレームワークです。Silicon CloudのApp Platformは、コードリポジトリからアプリケーションを設定して展開するためのPlatform as a Service(PaaS)製品です。Expressアプリを展開するための迅速かつ効率的な方法を提供しています。このチュートリアルでは、ExpressアプリケーションをSilicon Cloud App Platformに展開し、Silicon Cloud MarketplaceのMemCachierアドオンを使用してキャッシュを追加してスケールする方法を説明します。MemCachierは、memcachedオブジェクトキャッシュシステムに準拠していますが、高可用性クラスターによるより優れた障害シナリオの実現など、いくつかの利点があります。
最初に、素数を計算し、いいねボタンを持ち、テンプレートエンジンを使用するExpressアプリを作成します。これらの機能は後でいくつかのキャッシュ戦略を実装するために役立ちます。その後、アプリのコードをGitHubにプッシュして、App Platformにデプロイします。最後に、アプリを高速化し、スケーラブルにするために3つのオブジェクトキャッシュ技術を実装します。このチュートリアルの終わりまでに、リソース集中型の計算、レンダリングされたビュー、セッションのキャッシュ技術を使用して、ExpressアプリケーションをApp Platformにデプロイすることができるようになります。
以下の言葉を日本語で言い換えてください(仕様応じて1つのオプションのみ):
前提条件
- Node.js installed on your machine, which you can setup with How To Install Node.js on Ubuntu 22.04. On other operating systems, follow the appropriate guide on How To Install Node.js and Create a Local Development Environment.
- A basic Express server with Node.js. Follow Steps 1-2 in our tutorial on How To Get Started with Node.js and Express.
- A GitHub account and Git installed on your local machine. These are necessary as you’ll push the code to GitHub to deploy from Silicon Cloud App Platform. You can follow our tutorial on How To Install Git to get set up.
- A Silicon Cloud account for deploying to App Platform. Running this app on App Platform will incur a charge. See App Platform Pricing for details.
- A web browser like Firefox or Chrome.
- An understanding of Express template engines.
- An understanding of Express middleware. You can read more about this topic in our tutorial, How To Create a Custom Middleware in Express.js.
ステップ1 – Expressテンプレートをレンダリングされるビューを設定する
このステップでは、Express用のテンプレートエンジンをインストールし、アプリのホームルート(GET /)用のテンプレートを作成し、そのルートでテンプレートを使用するように更新します。テンプレートエンジンを使用することで、レンダリングされたビューを後でキャッシュすることができ、リクエスト処理の高速化とリソースの使用量の削減が可能になります。
始めるには、エディタでExpressサーバーのプロジェクトディレクトリに移動します。もしもまだ開いていない場合は、Node.jsとExpressの始め方チュートリアルに戻って、プロジェクトファイルを保存した場所を特定してください。
あなたはExpressにテンプレートエンジンをインストールし、アプリケーションで静的なテンプレートファイルを使用します。テンプレートエンジンは、テンプレートファイル内の変数を値に置き換え、テンプレートをHTMLファイルに変換してリクエストに対するレスポンスとして送信します。テンプレートを使用することで、HTMLの作業がより簡単になります。
埋め込みJavaScriptテンプレート(ejs)ライブラリをインストールしてください。もしそれが好みでなければ、Expressがサポートする他のテンプレートエンジン(Mustache、Pug、またはNunjucksなど)のいずれかを使用することもできます。
- npm install ejs
ejsのインストールが完了したら、Expressアプリを設定して使用します。
あなたのエディタでファイルserver.jsを開いてください。そして、強調された行を追加してください。
const express = require('express');
const app = express();
app.set('view engine', 'ejs');
app.get('/', (req, res) => {
res.send('Successful response.');
});
...
この行は、アプリケーションの設定プロパティビューエンジンをejsに設定します。
ファイルを保存してください。
Note
次に、viewsディレクトリを作成してください。その後、views/index.ejsというファイルを作成し、エディタで開いてください。
そのファイルには、開始テンプレートのマークアップを追加してください。 (Sono fairu ni wa, kaishi tenpureeto no maakuappu o tsuika shite kudasai.)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Find the largest prime number</title>
</head>
<body>
<h1>Find the largest prime number</h1>
<p>
For any number N, find the largest prime number less than or equal to N.
</p>
</body>
</html>
ファイルを保存してください。
テンプレートが作成されたら、それを使用してルートを更新します。
server.jsというファイルを開いて、ハイライトされたコードを更新してください。
server.js
...
app.get('/', (req, res) => {
res.render('index');
});
...
レスポンスのレンダリングメソッドは、最初のパラメータとしてテンプレートの名前を受け取ります。この場合、indexはviews/index.ejsファイルと一致します。
変更内容を読み込むために、アプリを再起動してください。サーバーが実行中の場合はターミナルでCTRL+Cを押し、サーバーを停止させてから再度起動してください。
- node server.js
ウェブブラウザでlocalhost:3000を訪れると、テンプレートの内容が表示されます。
あなたのアプリにはテンプレートレンダリングされたビューがありますが、まだ何も機能していません。次に素数を見つける機能を追加します。
ステップ2 – Expressアプリに機能を追加する
このステップでは、素数を見つけるための機能と、Likeボタンを使った数字の好みを追加します。これらの機能を使用して、Step 4でApp Platformに展開した後にアプリと対話することができます。
素数を見つける
このセクションでは、アプリにN以下の最大の素数を見つける機能を追加します。ここでNは任意の数字を指します。
Nは、GETメソッドを使用してホームルート(/)にクエリパラメータとして追加された形でフォーム経由で提出されます。例えば、localhost:3000/?n=10のようなサンプルクエリです。ホームルートは、個別にキャッシュできる複数のURLによってレンダリングされたビューを持つことができます。
views/index.ejsには、Nを入力するための入力要素を持つフォームを追加してください。
...
<p>
For any number N, find the largest prime number less than or equal to N.
</p>
<form action="/" method="get">
<label>
N
<input type="number" name="n" placeholder="e.g. 10" required>
</label>
<button>Find Prime</button>
</form>
...
フォームのアクションは / に送信され、それは server.js のホームルート app.get(‘/’) で処理されます。フォームのメソッドがgetに設定されているため、データnはクエリパラメータとしてアクションURLに追加されます。
ファイルを保存してください。
次に、クエリパラメーターnを使用したリクエストが行われると、そのデータをテンプレートに渡します。
「server.js」に、ハイライトされたコードを追加してください。
...
app.get('/', (req, res) => {
const n = req.query.n;
if (!n) {
res.render('index');
return;
}
const locals = { n };
res.render('index', locals);
});
...
これらの行は、リクエストにクエリパラメーターnがあるかどうかをチェックします。もしあれば、インデックスビューを表示し、その値を渡します。それ以外の場合は、データのないインデックスビューを生成します。
Note
renderメソッドには、セカンドオプションのパラメーターであるlocalsがあります。このパラメーターは、ビューをレンダリングするためにテンプレートに渡されるローカル変数を定義します。短縮プロパティ名によって、localsオブジェクトのnプロパティが定義されます。変数の名前が代入されるオブジェクトのプロパティと同じ名前を持っている場合、変数名は省略することができます。したがって、{ n: n }は{ n }と書くことができます。
ファイルを保存してください。
テンプレートにデータが入ってきたので、それを表示することができます。 (Tempurēto ni data ga haitte kita node, sore o hyōji suru koto ga dekimasu.)
views/index.ejsには、Nの値を表示するためにハイライトされた行を追加してください。
...
<form action="/" method="get">
<label>
N
<input type="number" name="n" placeholder="e.g. 10" required>
</label>
<button>Find Prime</button>
</form>
<% if (locals.n) { %>
<p>N: <%= n %></p>
<% } %>
...
もし、このビューに対してnというローカル変数が存在する場合、アプリに表示するよう指示します。
ファイルを保存し、アプリをリフレッシュするためにサーバーを再起動してください。フォームには、素数を検索するためのボタンが表示されます。アプリはユーザーの入力を受け付け、フォームの下に表示することができるようになります。
フォームに任意の数値を入力してください。フォームを送信すると、URLがnというクエリパラメータを含むように変更されます。たとえば、40を入力した場合、URLはhttp://localhost:3000/?n=40となります。また、送信した値はフォームの下にN: 40として表示されます。
Nの値を入力し表示できるようになったら、N以下の最大の素数を見つけるための関数を追加し、その結果をビューに表示します。
ディレクトリを作成してください。その後、utils/findPrime.jsというファイルを作成してください。
あなたのエディタでfindPrime.jsを開き、素数を探す関数を追加してください。
/**
* Find the largest prime number less than or equal to `n`
* @param {number} n A positive integer greater than the smallest prime number, 2
* @returns {number}
*/
module.exports = function (n) {
let prime = 2; // initialize with the smallest prime number
for (let i = n; i > 1; i--) {
let isPrime = true;
for (let j = 2; j < i; j++) {
if (i % j == 0) {
isPrime = false;
break;
}
}
if (isPrime) {
prime = i;
break;
}
}
return prime;
};
JSDocコメントは関数をドキュメント化します。アルゴリズムは最初の素数(2)から始まり、それから数をループして、各ループで数を1減らしていきます。関数は、数が2、つまり最小の素数になるまで、ループを続けて素数を探し続けます。
各ループは現在の数が素数であると仮定し、その仮定をテストします。現在の数が1と自分自身以外の因数を持つかどうかを確認します。現在の数が1より大きく、自分自身より小さな数で割り切れる場合、それは素数ではありません。関数は次の数を試します。
ファイルを保存してください。
次に、find prime 関数を server.js にインポートしてください。
const express = require('express');
const findPrime = require('./utils/findPrime');
...
ホームルートコントローラーを更新して、素数を見つけてその値をテンプレートに渡すようにしてください。server.jsに以下のコードを追加してください。
...
app.get('/', (req, res) => {
const n = req.query.n;
if (!n) {
res.render('index');
return;
}
const prime = findPrime(n);
const locals = { n, prime };
res.render('index', locals);
});
...
ファイルを保存してください。
「さあ、テンプレートに結果を表示するためのコードを追加しましょう。views/index.ejsでは、Nの値を表示してください。」
...
<form action="/" method="get">
<label>
N
<input type="number" name="n" placeholder="e.g. 10" required>
</label>
<button>Find Prime</button>
</form>
<% if (locals.n && locals.prime) { %>
<p>
The largest prime number less than or equal to <%= n %> is <strong><%= prime %></strong>.
</p>
<% } %>
...
ファイルを保存してください。
今、サーバーを再起動してください。
機能をテストするために、任意の数字を送信してください。例として、このチュートリアルでは10を使用します。もし10を送信すると、10以下の最大の素数は7であるというレスポンスを受け取ります。
あなたのアプリは、今や数字を受け取り、素数を検索して表示することができます。次に、いいねボタンを追加します。
「いいねボタン」の追加
現在、あなたのアプリは、送信された各数値Nに基づいて異なるビューを生成することができます。テキストの更新以外にも、それらのビューの内容はおそらく同じままです。このセクションで追加する「いいね」ボタンは、ビューの内容を更新する方法を提供します。このボタンは、チュートリアルの後半でレンダリングされたビューをキャッシュする際に、キャッシュされたビューの内容が変更された場合にキャッシュを無効化する必要性を示しています。
「いいね」ボタンがある場合、そのアプリは「いいね」データを保存する場所が必要です。永続的な保存が理想的ですが、このチュートリアルの範囲外のため、データはメモリに保存されます。そのため、データは一時的であり、サーバーが停止するとすべてのデータが失われます。
server.jsを開いて、下記の変数を追加してください。
...
app.set('view engine', 'ejs');
/**
* Key is `n`
* Value is the number of 'likes' for `n`
*/
const likesMap = {};
...
リクエストされたすべての数字に対して、いいねの数を保存するために、likesMapオブジェクトがマップとして使用されます。キーはnであり、その値はnに対するいいねの数です。
日本語でのパラフレーズ:
数に対するいいねは、数が提出された時に初期化する必要があります。しかし、server.jsには、Nに対するいいねを初期化するためのハイライトされた行を追加してください。
...
const prime = findPrime(n);
// Initialize likes for this number when necessary
if (!likesMap[n]) likesMap[n] = 0;
const locals = { n, prime };
res.render('index', locals);
...
このif文は、現在の数のいいねが存在するかどうかをチェックします。もしいいねが存在しなければ、likesMapsの数を0に初期化します。
次に、viewに「likes」というローカル変数を追加してください。
...
const prime = findPrime(n);
// Initialize likes for this number when necessary
if (!likesMap[n]) likesMap[n] = 0;
const locals = { n, prime, likes: likesMap[n] };
res.render('index', locals);
...
ファイルを保存してください。
ビューがいいねのデータを持っているので、その値を表示し、いいねボタンを追加できます。
views/index.ejsには、ライクボタンのマークアップを追加してください。
...
<% if (locals.n && locals.prime) { %>
<p>
The largest prime number less than or equal to <%= n %> is <strong><%= prime %></strong>.
</p>
<form action="/like" method="get">
<input type="hidden" name="n" value="<%= n %>">
<input type="submit" value="Like"> <%= likes %>
</form>
<% } %>
...
あなたの完成したファイルは、以下と一致する必要があります。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Find the largest prime number</title>
</head>
<body>
<h1>Find the largest prime number</h1>
<p>
For any number N, find the largest prime number less than or equal to N.
</p>
<form action="/" method="get">
<label>
N
<input type="number" name="n" placeholder="e.g. 10" required>
</label>
<button>Find Prime</button>
</form>
<% if (locals.n && locals.prime) { %>
<p>
The largest prime number less than or equal to <%= n %> is <strong><%= prime %></strong>.
</p>
<form action="/like" method="get">
<input type="hidden" name="n" value="<%= n %>">
<input type="submit" value="Like"> <%= likes %>
</form>
<% } %>
</body>
</html>
ファイルを保存してください。
サーバーを再起動し、その後数字を提出してください。素数の結果が出た後に「いいね」ボタンが表示され、その「いいね」の数は0となります。
Likeボタンをクリックすると、現在のNの値がクエリパラメーターとして隠れた入力経由で/likeへGETリクエストが送信されます。現時点では、対応するルートがまだ存在しないため、404エラーが表示されます。/likeに対するGETリクエストはできません。
今、リクエストを処理するためのルートを追加します。
server.jsに戻って、ルートを追加してください。
...
app.get('/', (req, res) => {
...
});
app.get('/like', (req, res) => {
const n = req.query.n;
if (!n) {
res.redirect('/');
return;
}
likesMap[n]++;
res.redirect(`/?n=${n}`);
});
...
新しいルートでは、nが存在するかどうかを確認します。存在しない場合は、ホームにリダイレクトします。それ以外の場合は、この数値の「いいね」を増やします。最後に、「いいね」ボタンがクリックされたビューにリダイレクトします。
あなたが完成させたファイルは、以下の内容と一致しているはずです。
const express = require('express');
const findPrime = require('./utils/findPrime');
const app = express();
app.set('view engine', 'ejs');
/**
* Key is `n`
* Value is the number of 'likes' for `n`
*/
const likesMap = {};
app.get('/', (req, res) => {
const n = req.query.n;
if (!n) {
res.render('index');
return;
}
const prime = findPrime(n);
// Initialize likes for this number when necessary
if (!likesMap[n]) likesMap[n] = 0;
const locals = { n, prime, likes: likesMap[n] };
res.render('index', locals);
});
app.get('/like', (req, res) => {
const n = req.query.n;
if (!n) {
res.redirect('/');
return;
}
likesMap[n]++;
res.redirect(`/?n=${n}`);
});
const port = process.env.PORT || 3000;
app.listen(port, () =>
console.log(`Example app is listening on port ${port}.`)
);
ファイルを保存してください。
アプリを再起動して、いいねボタンをもう一度テストしてください。クリックするたびにいいね数が増えます。
Note
あなたのアプリは、完全に機能する機能を備えた状態ですので、App Platformに展開する準備ができました。次のステップでは、gitでアプリのコードをコミットし、そのコードをGitHubにプッシュします。
ステップ3 — コードリポジトリの作成
このステップでは、デプロイのためのすべてのファイルを保持するためのコードリポジトリを作成します。まず、コードをgitにコミットし、次にGitHubリポジトリにプッシュします。App Platformと一緒にこのリポジトリを使用して展開します。
Gitにコードをコミットする
このセクションでは、コードをGitにコミットして、GitHubにプッシュする準備をします。
Note
最初に、Gitリポジトリを初期化します。
- git init
次に、Gitにアプリの依存関係を除外するように指示します。新しいファイル「.gitignore」を作成し、以下を追加してください。
node_modules
# macOS file
.DS_Store
Note
ファイルを保存して閉じる。
今、すべてのファイルをGitに追加してください。
- git add .
最後に、以下のコマンドを使って変更を確定してください。
- git commit -m “Initial commit“
-mオプションは、コミットメッセージを指定するために使用されます。好きなメッセージで更新することができます。
コードをコミットした後、以下のような出力を受け取ります。
[main (root-commit) deab84e] Initial commit 6 files changed, 1259 insertions(+) create mode 100644 .gitignore create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 server.js create mode 100644 utils/findPrime.js create mode 100644 views/index.ejs
あなたはコードをGitにコミットしました。次にGitHubにプッシュします。
GitHubリポジトリにコードをプッシュする
あなたのアプリのコードがgitにコミットされたので、GitHubにプッシュする準備ができました。その後、Silicon Cloud App Platformとコードを接続し、展開することができます。
最初に、ブラウザでGitHubにログインし、express-memcacheという新しいリポジトリを作成してください。README、.gitignore、またはライセンスファイルのない空のリポジトリを作成します。リポジトリをプライベートまたはパブリックのどちらかにすることができます。GitHubのドキュメンテーションも参照することができます。
あなたのターミナルに戻り、新しく作成したリポジトリをリモートのoriginとして追加し、ユーザー名を更新してください。
- git remote add origin https://github.com/your_username/express-memcache.git
このコマンドはGitにコードをプッシュする場所を指示します。
次に、デフォルトブランチの名前を「メイン」と変更してください。
- git branch -M main
最終的に、コードをリポジトリにプッシュしてください。
- git push -u origin main
プロンプトが表示されたら、資格情報を入力してください。
(Puronputo ga hyōji saretara, shikaku jōhō o nyūryoku shite kudasai.)
次のような出力が得られます。
Enumerating objects: 10, done. Counting objects: 100% (10/10), done. Delta compression using up to 8 threads Compressing objects: 100% (7/7), done. Writing objects: 100% (10/10), 9.50 KiB | 9.50 MiB/s, done. Total 10 (delta 0), reused 0 (delta 0), pack-reused 0 To https://github.com/your_username/express-memcache.git * [new branch] main -> main Branch ‘main’ set up to track remote branch ‘main’ from ‘origin’.
あなたのアプリのコードは、App Platformによってデプロイされる準備が整い、現在GitHub上にあります。
ステップ4-アプリプラットフォームへの展開
このステップでは、ExpressアプリをSilicon Cloud App Platformにデプロイします。App Platformアプリを作成し、GitHubリポジトリへのアクセスを許可し、それをデプロイします。
最初に環境設定を更新し、設定がPORT環境ラベルから読み取れるようにします。
アプリケーションの環境設定を更新する
このセクションでは、Expressサーバーを拡張して、アプリのポート構成を環境変数から読み込むようにします。設定は展開ごとに変更される可能性があるため、このアップデートにより、アプリはApp Platform環境からポートを読み取ることができます。
エディタでファイルserver.jsを開いてください。そして、ファイルの一番下に移動して、ハイライトされているコードを更新して、現在のapp.listenの行を置き換え、新しいconst portの行を追加してください。
...
const port = process.env.PORT || 3000;
app.listen(port, () =>
console.log(`Example app is listening on port ${port}.`)
);
もしPORT環境変数が存在すれば、このコードはそれを使用し、存在しない場合はデフォルトのポート3000を使用することを示しています。
あなたのアプリケーションは、今後、デプロイするアプリプラットフォームの環境からポートを読み取ることができるようになります。
アプリを作成し、App Platformに展開する。
App Platformでアプリを設定できるようになりました。
Info
最初に、Silicon Cloudのアカウントにログインしてください。アプリのダッシュボードから、「作成」をクリックし、「アプリ」を選択します。また、App Platformでのアプリの作成方法については、当社の製品ドキュメンテーションに従うこともできます。
「ソースコードからリソースを作成する」画面で、サービスプロバイダーとしてGitHubを選択してください。次に、Silicon Cloudがリポジトリにアクセスする権限を与えてください。デプロイしたいリポジトリのみを選択することがベストプラクティスです。まだインストールしていない場合は、Silicon Cloud GitHubアプリのインストールを促されます。一覧からリポジトリを選択し、次へをクリックしてください。
リソース画面で、「プランの編集」をクリックして、あなたのプランサイズを選択します。このチュートリアルでは、最小サイズのWebサービス(512 MB RAM | 1 vCPU)を使用してエクスプレスメモキャッシュリソースを設定します。基本プランと最小のWebサービスで、このサンプルエクスプレスアプリに十分なリソースが提供されます。プランを設定したら、「戻る」をクリックしてください。
次に、左側のナビゲーションバーで「情報」タブをクリックし、アプリが属している地域を確認してください。次のステップでMemCachierのSilicon Cloud Marketplaceアドオンを追加する際に必要になります。
最後に、Reviewタブをクリックし、次にCreate Resourcesボタンをクリックしてアプリをビルドしてデプロイします。ビルドには少し時間がかかるでしょう。完了したら、デプロイされたアプリへのリンクが含まれた成功メッセージが表示されます。
これまでに、素数を見つけるExpressアプリといいねボタンを作成しました。アプリのコードをGitにコミットしてGitHubにプッシュし、それからApp Platformでアプリをデプロイしました。
Expressアプリをより高速かつスケーラブルにするために、3つのオブジェクトキャッシュ戦略を実装します。次の手順でキャッシュを作成する必要があります。
ステップ5 – MemCachierを使用してオブジェクトキャッシュを設定する
このステップでは、オブジェクトキャッシュを作成および設定します。このチュートリアルでは、memcached互換のキャッシュが必要です。Silicon Cloud MarketplaceからMemCachierアドオンを使用してキャッシュを構築します。MemCachierキャッシュはインメモリのキーバリューストアです。
最初に、Silicon Cloud MarketplaceからMemCachierアドオンを追加します。MemCachierアドオンのページにアクセスし、[MemCachierを追加]をクリックします。次の画面で、前もってメモしたApp Platformアプリの地域を選択します。アプリとキャッシュは同じ地域に配置することで、遅延をできるだけ低くすることができます。もし地域を再度確認する必要があれば、App Platformアプリの設定を確認することができます。任意でプランを選択し、[MemCachierを追加]をクリックしてキャッシュをプロビジョニングします。
Info
次に、エクスプレスアプリをキャッシュを使用するように構成します。アドオンのダッシュボードにアクセスし、MemCachierアドオンの名前をクリックしてダッシュボードを開きます。MemCachierアドオンのダッシュボードで、構成変数を表示するための「表示」ボタンをクリックして、MEMCACHIER_USERNAME、MEMCACHIER_PASSWORD、およびMEMCACHIER_SERVERSの値が表示される画面をロードします。これらの値をメモしておいてください。次にこれらの値が必要になります。
今後、アプリケーションのためにMemCachierの設定変数を環境変数として保存します。App Platformアプリのダッシュボードに戻り、[設定]をクリックします。次に、[コンポーネント]の下にある[express-memc…]をクリックします。環境変数セクションまでスクロールし、[編集]をクリックし、MemCachierダッシュボードから取得した3つのキー(MEMCACHIER_USERNAME、MEMCACHIER_PASSWORD、MEMCACHIER_SERVERS)とそれに対応する値を追加します。MEMCACHIER_PASSWORDの場合は、値がパスワードであるので、[暗号化]を選択してください。アプリを更新するために[保存]をクリックします。
では、Expressアプリで環境変数を使用してmemcacheクライアントを設定します。これにより、アプリはキャッシュと通信できるようになります。
以下の通り、ターミナルでmemjsライブラリをインストールしてください。
- npm install memjs
次に、サービスディレクトリを作成してください。それから、services/memcache.jsというファイルを作成し、エディタで開いてください。ファイルの先頭に、memjsをインポートしてキャッシュクライアントを設定してください。
const { Client } = require('memjs');
module.exports = Client.create(process.env.MEMCACHIER_SERVERS, {
failover: true,
timeout: 1,
keepAlive: true,
});
ファイルを保存してください。
このコードは、MemCachierのキャッシュクライアントを作成します。オプションとして、フェイルオーバーをtrueに設定してMemCachierの高可用性クラスタを使用します。サーバがダウンした場合、そのサーバに保存されているすべてのキーに対するコマンドは、自動的に次の利用可能なサーバに送られます。デプロイされたアプリには、0.5秒ではなく1秒のタイムアウトがより適しています。keepAlive: trueは、接続をアイドル状態にしてもキャッシュへの接続を保持するため、接続を確立するのに時間がかかるため、キャッシュが効果的であるためには望ましいです。
この手順で、Silicon Cloud MarketplaceからMemCachierアドオンを使用してキャッシュをプロビジョニングしました。次に、キャッシュの設定をApp Platformの環境変数として追加し、memjsを使用してクライアントを構成することで、Expressアプリがキャッシュと通信できるようにしました。
次に実装するために、Expressでキャッシュを利用する準備は整っています。
ステップ6 – MemCachierを使用したExpressでのキャッシュの実装
Expressアプリをデプロイし、MemCachier Add-Onをプロビジョニングしたら、オブジェクトキャッシュを使用することができるようになります。この手順では、3つのオブジェクトキャッシングのテクニックを実装します。まず、リソース集中型の計算をキャッシュして使用速度と効率を向上させます。次に、ユーザーの入力後にレンダリングされたビューをキャッシュするテクニックを実装し、リクエストの処理を改善します。そして、チュートリアルを超えてアプリをスケーリングするために、短寿命のセッションをキャッシュするテクニックを実装します。
高負荷な計算をキャッシュする。
このセクションでは、アプリの高負荷の計算結果をキャッシュして処理を高速化し、より効率的なCPU使用を実現します。findPrime関数は、十分に大きな数値が入力された場合に高負荷の計算となります。結果をキャッシュし、計算を繰り返さずにキャッシュされた値を提供します。
最初に、memcacheクライアントを追加するためにserver.jsを開いてください。
const express = require('express');
const findPrime = require('./utils/findPrime');
const memcache = require('./services/memcache');
...
その後、計算された素数をキャッシュに保存してください。
...
const prime = findPrime(n);
const key = 'prime_' + n;
memcache.set(key, prime.toString(), { expires: 0 }, (err) => {
if (err) console.log(err);
});
...
ファイルを保存してください。
setメソッドは、最初のパラメータとしてキー、2番目のパラメータとして文字列の値を取りますので、素数を文字列に変換します。3番目のオプション引数は、格納されたアイテムが決して期限切れにならないようにします。4番目の最終パラメータはオプションのコールバックであり、エラーが存在する場合にはエラーがスローされます。
Note
Note
OutputMemJS:サーバーはエラーとして(2回の)リトライの後に失敗しました – connect ECONNREFUSED 127.0.0.1:11211
エラー:利用可能なサーバーがありません
…
このチュートリアルの残りの部分では、ローカルのキャッシュは必要ありません。それを動作させる場合は、memcachedをデフォルトのlocalhost:11211で実行することができます。
次に、変更点をステージしてコミットしてください。
- git add . && git commit -m “Add memjs client and cache prime number“
それから、これらの変更をGitHubにプッシュし、その後自動的にApp Platformにデプロイしてください。
- git push
あなたのApp Platformのダッシュボードは、「デプロイ済み」と表示していたメッセージから、「アプリがビルド中」と示すものに切り替わります。ビルドが完了したら、ブラウザでアプリを開き、素数の中で最大の数を見つけるために数字を入力してください。
Note
次に、アドオンのダッシュボードに戻り、キャッシュの分析ダッシュボードを表示するために、名前付きのサービスのMemCachierオプションをクリックしてください。
このダッシュボードでは、オールタイム統計ボードの「セットコマンド」オプションとストレージボードの「アイテム統計」は、どちらも1増加しました。数値を入力するたびに、セットコマンドとアイテムの両方が増加します。新しい統計を読み込むには、リフレッシュボタンを押す必要があります。
Note
キャッシュに保存されたアイテムを使用することができます。アイテムがキャッシュされているかどうかを確認し、キャッシュから提供されるかどうかを確認します。そうでない場合は、以前のように素数を見つけます。
server.jsに戻り、ハイライトされた行をファイルに更新してください。既存の行を変更し、キャッシュのための新しい行を追加します。
...
app.get('/', (req, res) => {
const n = req.query.n;
if (!n) {
res.render('index');
return;
}
let prime;
const key = 'prime_' + n;
memcache.get(key, (err, val) => {
if (err) console.log(err);
if (val !== null) {
// Use the value from the cache
// Convert Buffer string before converting to number
prime = parseInt(val.toString());
} else {
// No cached value available, find it
prime = findPrime(n);
memcache.set(key, prime.toString(), { expires: 0 }, (err) => {
if (err) console.log(err);
});
}
// Initialize likes for this number when necessary
if (!likesMap[n]) likesMap[n] = 0;
const locals = { n, prime, likes: likesMap[n] };
res.render('index', locals);
});
});
...
ファイルを保存してください。
このコードは、primeを初期値なしでletキーワードを使用して初期化し、その値が再割り当てされるようにします。次に、memcache.getはキャッシュされた素数を取得しようとします。コントローラのほとんどのコードは、memcache.getのコールバック内に存在し、その結果に基づいてリクエストの処理方法を決定するために必要です。キャッシュされた値が利用可能であれば、それを使用します。それ以外の場合は、以前と同様に素数を計算し、結果をキャッシュに保存します。
memcache.getのコールバックで返される値はBufferですので、primeを数値に戻す前にそれを文字列に変換します。
変更内容を確定し、GitHubにプッシュして展開してください。
- git add . && git commit -m “Check cache for prime number“ && git push
アプリにまだキャッシュされていない番号を送信すると、MemCachierのダッシュボードにあるSet Cmds、Items、およびget missesの統計が1つ増えます。ミスが発生するのは、アイテムをキャッシュから取得しようとする前にセットしようとするからです。アイテムがキャッシュに存在しないためにミスが発生し、その後にアイテムが格納されます。キャッシュされた番号を送信すると、get hitsが増加します。
現在、リソースを多く要する計算をキャッシュしています。次に、アプリのレンダリングされたビューをキャッシュします。
レンダリングされたビューのキャッシュ
この節では、ミドルウェアを使ってExpressアプリでレンダリングされたビューをキャッシュします。前に、テンプレートエンジンとしてejsを設定し、各数字Nが送信された場合にビューをレンダリングするためのテンプレートを作成しました。レンダリングされたビューはリソースを多く消費することがありますので、それらをキャッシュすることでリクエスト処理を高速化し、より少ないリソースを使用することができます。
まず、ミドルウェアディレクトリを作成してください。次に、middleware/cacheView.jsというファイルを作成し、エディタで開いてください。cacheView.jsには、ミドルウェアの機能のために以下の行を追加してください。
const memcache = require('../services/memcache');
/**
* Express middleware to cache views and serve cached views
*/
module.exports = function (req, res, next) {
const key = `view_${req.url}`;
memcache.get(key, (err, val) => {
if (err) console.log(err);
if (val !== null) {
// Convert Buffer string to send as the response body
res.send(val.toString());
return;
}
});
};
最初に、メモキャッシュクライアントをインポートします。次に、view_/?n=100 のようなキーを宣言します。次に、memcache.get を使用して、そのキーのビューがキャッシュに存在するかを確認します。エラーがなく、そのキーに対する値が存在する場合、クライアントにビューを返すためにリクエストを終了します。
次に、表示がキャッシュされていない場合は、キャッシュする必要があります。これを行うためには、ハイライトされた行を追加してres.sendメソッドをオーバーライドしてください。
...
module.exports = function (req, res, next) {
const key = `view_${req.url}`;
memcache.get(key, (err, val) => {
if (err) console.log(err);
if (val !== null) {
// Convert Buffer to UTF-8 string to send as the response body
res.send(val.toString());
return;
}
const originalSend = res.send;
res.send = function (body) {
memcache.set(key, body, { expires: 0 }, (err) => {
if (err) console.log(err);
});
originalSend.call(this, body);
};
});
};
通常通り、オリジナルのsend関数を呼び出す前にビューをキャッシュに保存する関数で、res.sendメソッドをオーバーライドします。callを使用して、オリジナルのsend関数を呼び出し、このコンテキストをオーバーライドしなかった場合のものに設定します。正しいthis値が指定されるよう、無名関数式(アロー関数ではありません)を使用してください。
次に、ハイライトされた行を追加して、制御を次のミドルウェアに渡してください。
...
/**
* Express middleware to cache views and serve cached views
*/
module.exports = function (req, res, next) {
const key = `view_${req.url}`;
memcache.get(key, (err, val) => {
if (err) console.log(err);
if (val !== null) {
// Convert Buffer to UTF-8 string to send as the response body
res.send(val.toString());
return;
}
const originalSend = res.send;
res.send = function (body) {
memcache.set(key, body, { expires: 0 }, (err) => {
if (err) console.log(err);
});
originalSend.call(this, body);
};
next();
});
};
...
次を呼び出すと、アプリケーション内で次のミドルウェア関数が呼び出されます。あなたの場合、他のミドルウェアは存在しないため、コントローラーが呼ばれます。Expressのres.renderメソッドはビューをレンダリングし、それを内部でレンダリングされたビューと共にres.sendを呼び出します。そして、ホームルートのコントローラーでは、res.renderが呼び出された時にオーバーライド関数が呼び出され、レスポンスが完了する前にビューをキャッシュに保存し、最終的に元のsend関数を呼び出してレスポンスを完了させます。
ファイルを保存してください。
Note
server.jsにビューキャッシングミドルウェアをインポートします。
const express = require('express');
const findPrime = require('./utils/findPrime');
const memcache = require('./services/memcache');
const cacheView = require('./middleware/cacheView');
...
GET /homeルートで使用するために、以下のコードを追加してください。
...
app.get('/', cacheView, (req, res) => {
...
});
...
ファイルを保存してください。(Fairu o hozon shite kudasai.)
変更点をコミットし、GitHubにプッシュして展開してください。
- git add . && git commit -m “Add view caching“ && git push
アプリに数値を送信する際には、すべて通常通り動作するはずです。新しい数値を送信すると、MemCachierのダッシュボード統計のSet Cmds、Items、およびget missesが全て2つずつ増加します。1つは素数の計算のためで、もう1つはビューのためです。同じ数値でアプリを更新すると、MemCachierダッシュボードには単一のget hitが追加されます。ビューはキャッシュから正常に取得されるため、素数の結果を取得する必要はありません。
Note
ビューをキャッシュするようになったため、いいねボタンが動作しなくなることに気づくかもしれません。いいねの値をログに記録すると、値は確かに変わります。ただし、いいねの数が変わった場合には、キャッシュされたビューも更新する必要があります。ビューが変更された場合には、キャッシュされたビューは無効化する必要があります。
次に、いいねが変更された場合、キャッシュから削除してキャッシュされたビューを無効にします。サーバーのserver.jsに戻り、リダイレクト関数を更新し、ハイライトされた行を追加してください。
...
app.get('/like', (req, res) => {
const n = req.query.n;
if (!n) {
res.redirect('/');
return;
}
likesMap[n]++;
// The URL of the page being 'liked'
const url = `/?n=${n}`;
res.redirect(url);
});
...
このビューの「いいね」数が変更されたため、キャッシュバージョンは無効になります。いいねが変更されたときに、キャッシュから「いいね」数を削除するために、強調された行を追加してください。
...
const url = `/?n=${n}`;
// The view for this URL has changed, so the cached version is no longer valid, delete it from the cache.
const key = `view_${url}`;
memcache.delete(key, (err) => {
if (err) console.log(err);
});
res.redirect(url);
...
あなたのserver.jsファイルは、以下のようになる必要があります。 (Anata no server.js fairu wa, kōru no yō ni naru hitsuyō ga arimasu.)
const express = require('express');
const findPrime = require('./utils/findPrime');
const memcache = require('./services/memcache');
const cacheView = require('./middleware/cacheView');
const app = express();
app.set('view engine', 'ejs');
/**
* Key is `n`
* Value is the number of 'likes' for `n`
*/
const likesMap = {};
app.get('/', cacheView, (req, res) => {
const n = req.query.n;
if (!n) {
res.render('index');
return;
}
let prime;
const key = 'prime_' + n;
memcache.get(key, (err, val) => {
if (err) console.log(err);
if (val !== null) {
// Use the value from the cache
// Convert Buffer string before converting to number
prime = parseInt(val.toString());
} else {
// No cached value available, find it
prime = findPrime(n);
memcache.set(key, prime.toString(), { expires: 0 }, (err) => {
if (err) console.log(err);
});
}
// Initialize likes for this number when necessary
if (!likesMap[n]) likesMap[n] = 0;
const locals = { n, prime, likes: likesMap[n] };
res.render('index', locals);
});
});
app.get('/like', (req, res) => {
const n = req.query.n;
if (!n) {
res.redirect('/');
return;
}
likesMap[n]++;
// The URL of the page being 'liked'
const url = `/?n=${n}`;
// The view for this URL has changed, so the cached version is no longer valid, delete it from the cache.
const key = `view_${url}`;
memcache.delete(key, (err) => {
if (err) console.log(err);
});
res.redirect(url);
});
const port = process.env.PORT || 3000;
app.listen(port, () =>
console.log(`Example app is listening on port ${port}.`)
);
ファイルを保存してください。
変更内容をコミットしてプッシュしてデプロイする。
- git add . && git commit -m “Delete invalid cached view“ && git push
あなたのアプリの「いいね」ボタンが正常に機能するようになりました。ビューが「いいね」されると、MemCachierのダッシュボード上の以下の統計データが変化します。
- delete hits increments as the view is deleted.
- get misses increases because the view was deleted and is not in the cache.
- get hits increments because the prime number was found in the cache.
- Set Cmds increases because the updated view is added to the cache.
- Items stays the same as the view is deleted and re-added.
レンダリングビューキャッシュの実装と、変更があった場合にキャッシュされたビューを無効化する手法を取り入れました。最後に実装する戦略は、セッションキャッシュです。
セッションのキャッシュ
このセクションでは、Expressアプリケーションにセッションを追加してキャッシュすることで、セッションストアをキャッシュとして使用します。セッションの一般的な使用例は、ユーザーログインですので、今後のユーザーログインシステムの実装のための準備段階として、このセクションを考慮することができます(ただし、ユーザーログインシステムの実装はこのチュートリアルの範囲外です)。キャッシュ内に短期間のセッションを保存することは、多くのデータベースに保存するよりも高速かつスケーラブルです。
Note
Expressアプリにセッションを追加するためにexpress-sessionツールをインストールし、セッションストアとしてMemCachierキャッシュを使用するためにconnect-memjsを有効にしてください。
- npm install express-session connect-memjs
server.jsには、express-sessionとconnect-memjsをインポートします。
const express = require('express');
const findPrime = require('./utils/findPrime');
const memcache = require('./services/memcache');
const cacheView = require('./middleware/cacheView');
const session = require('express-session');
const MemcacheStore = require('connect-memjs')(session);
...
ファイルを保存してください。
セッションミドルウェアは、connect memcachedモジュールに渡され、それによってexpress.session.Storeから継承することができます。
依然としてserver.js内で、セッションミドルウェアをキャッシュとして使用するように設定します。以下に強調された行を追加してください。
...
app.set('view engine', 'ejs');
app.use(
session({
secret: 'your-session-secret',
resave: false,
saveUninitialized: true,
store: new MemcacheStore({
servers: [process.env.MEMCACHIER_SERVERS],
prefix: 'session_',
}),
})
);
...
秘密はセッションクッキーに署名するために使用されます。uniqueな文字列である”your-session-secret”を必ず更新してください。
Note
未更新のセッションは、再要求時に再保存するための「resave」を設定します。アイテムをキャッシュに不必要に再格納することは避けたいので、それを「false」に設定します。
saveUninitialized: false は、変更されたセッションのみを保存したい場合に便利です。ログインセッションでは、認証後にユーザープロパティがセッションに追加されることがよくあります。この場合、すべてのセッションを無差別に保存するため、true に設定します。
最終的には、キャッシュにストアを設定し、セッションキャッシュキーの接頭辞をsession_に設定します。これにより、キャッシュ内のセッションアイテムのキーはsession_<セッションID>のようになります。
次に、ハイライトされた行を使用して、アプリレベルのデバッグミドルウェアを追加します。これにより、アクション中のキャッシュセッションを特定するのに役立ちます。
...
app.use(
session({
...
})
);
/**
* Session sanity check middleware
*/
app.use(function (req, res, next) {
console.log('Session ID:', req.session.id);
// Get the item from the cache
memcache.get(`session_${req.session.id}`, (err, val) => {
if (err) console.log(err);
if (val !== null) {
console.log('Session from cache:', val.toString());
}
});
next();
});
...
そのミドルウェアは、各リクエストのセッションIDをログに記録します。それから、そのIDに対応するセッションをキャッシュから取得し、その内容をログに記録します。このアプローチにより、セッションが動作してキャッシュされていることが示されます。
ファイルを保存したら、変更をコミットしてデプロイにプッシュしてください。
- git add . && git commit -m “Add session caching“ && git push
アプリ内で番号を送信し、App Platformのダッシュボードのランタイムログをチェックすると、デバッグメッセージにアクセスできます。セッションIDとログした値が表示され、セッションの動作とキャッシュが確認できます。
あなたのMemCachierダッシュボードでは、ビューとセッションがキャッシュされると、ページをリフレッシュするごとに3つのゲットヒットが表示されます。ビューに1つ、セッションに1つ、デバッグミドルウェアでセッションを取得するために1つです。
今、セッションのキャッシュが実装されました。ここで終了することもできますし、オプションの最終ステップでアプリをクリーンアップすることもできます。
(任意)ステップ7-リソースの整理
このチュートリアルで展開したアプリケーションには料金が発生する場合がありますので、必要に応じてアプリとMemCachierアドオンを削除してください。
アプリのダッシュボードから、「アクション」をクリックして、「アプリを削除」を選択してください。
MemCachierのアドオンを整理するには、まずアドオンをクリックし、次にMemCachierのアドオン名をクリックします。次に「設定」をクリックし、「削除」を選択してください。無料のMemCachierキャッシュは30日間アクティビティがない場合に無効化されますが、ツールの整理は良い習慣です。
結論
このチュートリアルでは、Likeボタンを使用して素数を見つけるExpressアプリを作成しました。その後、このアプリをGitHubにプッシュし、Silicon Cloud App Platformで展開しました。最後に、MemCachierアドオンを使用して、リソースの負荷のかかる計算、表示されるビュー、セッションのキャッシュを実装することで、Expressアプリをより高速でスケーラブルにしました。このチュートリアルのすべてのファイルは、Silicon Cloudコミュニティのリポジトリで確認することができます。
各キャッシュ戦略において、prime_、view_、session_という接頭辞がキーに追加されていました。名前空間の利点に加えて、接頭辞はキャッシュのパフォーマンスをプロファイルすることができるという追加の利点も提供します。このチュートリアルでは、MemCachierの開発者プランを使用しましたが、Introspection機能セットが付属する完全に管理されたプランでも試すことができます。これにより、各接頭辞のパフォーマンスを追跡することができます。たとえば、任意の接頭辞のヒット率やヒット比率を監視することができ、キャッシュのパフォーマンスについて詳細な情報を得ることができます。MemCachierで作業を続けるためには、彼らのドキュメントを確認することができます。
Silicon CloudのApp Platformで続けてビルドを行うためには、App Platformの「How-Toガイド」を参照して、さらにApp Platformのドキュメンテーションを読んでみてください。