Amazon ElastiCacheのハンズオンをServerlessに変更してみる

記事タイトルとURLをコピーする

はじめに

こんにちは、荒堀です。

ElastiCacheの公式のハンズオンがあります。今回はこれをAmazon ElastiCache Serverlessでやってみます。

aws.amazon.com

このハンズオンは、RDSのデータをキャッシュするというシナリオになっています。 しかし少し試したいだけでRDSを作るのは大仰なので、今回はCloud9の中にSQLiteでデータベースを作ってそれを使います。

概要

  • 利用するサービスは以下です
    • Amazon ElastiCache Serverless
    • AWS Cloud9
      • この中にSQLiteでデータベースを作ります
  • VPCは、デフォルトVPCを使います
    • ElastiCacheにデフォルトのセキュリティグループを宛がうので、インバウンドルールを追加します

参考

blog.serverworks.co.jp

やってみた

環境作成

以前の記事と同じ環境を作ります。

blog.serverworks.co.jp

  1. Cloud9の起動
  2. ElastiCache Serverlessを作成
  3. セキュリティグループにインバウンドルールを追加

Cloud9にデータベース作成

ハンズオンでRDSに作成しているデータベースを、Cloud9上のSQLiteで作成します。 ディレクトリを作成し、seed.sqlで空のSQLファイルを作成します。

mkdir demo-elasticacheserverless && cd demo-elasticacheserverless

touch seed.sql

seed.sqlに、テーブルを作成・データを登録するSQLを記述します。

CREATE TABLE planet (
  id integer primary key autoincrement,
  name VARCHAR(30)
  );
INSERT INTO planet (name) VALUES ("Mercury");
INSERT INTO planet (name) VALUES ("Venus");
INSERT INTO planet (name) VALUES ("Earth");
INSERT INTO planet (name) VALUES ("Mars");
INSERT INTO planet (name) VALUES ("Jupiter");
INSERT INTO planet (name) VALUES ("Saturn");
INSERT INTO planet (name) VALUES ("Uranus");
INSERT INTO planet (name) VALUES ("Neptune");

SQLiteを実行し、SQLファイルを実行します。

sqlite3 tutorial.sqlite

sqlite> .read seed.sql

sqlite> SELECT * FROM planet;

Python実行

以下のサンプルコードを参考にしました。

github.com

redisをインストールして、Pythonを実行します。

pip install redis

python

まずは必要なパッケージをインポートします。

import json
import redis
import sqlite3

データベースクラスを作ります。dict_factoryは、SQLiteの戻り値を辞書型にする関数です。指定しないとtuple型になり、Redisの.hmsetで扱えなくなります。

# 戻り値を辞書型にする関数
def dict_factory(cursor, row):
  d = {}
  for idx,col in enumerate(cursor.description):
    d[col[0]] = row[idx]
  return d

# SQLiteを扱うクラス
class DB:
  def __init__(self, dbname):
    self.sqlite = sqlite3.connect(dbname)
    self.sqlite.row_factory = dict_factory
  
  def query(self, sql):
    cursor = self.sqlite.cursor()
    try:
      cursor.execute(sql)
      return cursor.fetchall()
    finally:
        cursor.close()
  
  def record(self, sql, values):
    cursor = self.sqlite.cursor()
    try:
      cursor.execute(sql, values)
      return cursor.fetchone()
    finally:
        cursor.close()

次に、SQLiteとElasticacheに接続します。

# Time to live for cached data
TTL = 10

# Initialize the database
Database = DB('tutorial.sqlite')

# Initialize the cache
Cache = redis.RedisCluster(
  host='xxx.cache.amazonaws.com'(ElastiCacheのエンドポイント)
  , ssl=True
  , decode_responses=True
  )

redis.RedisClusterdecode_responses=Trueは、.hgetallの戻り値をバイト型にしないため指定します。

fetch関数を定義して試してみます。

def fetch(sql):
  """Retrieve records from the cache, or else from the database."""
  res = Cache.get(sql)
  
  if res:
    print("キャッシュあり")
    return json.loads(res)
  
  print("キャッシュなし")
  res = Database.query(sql)
  Cache.setex(sql, TTL, json.dumps(res))
  return res

何回か実行してみます。

>>> print(fetch("SELECT * FROM planet"))
キャッシュなし
[{'id': 1, 'name': 'Mercury'}, {'id': 2, 'name': 'Venus'}, {'id': 3, 'name': 'Earth'}, {'id': 4, 'name': 'Mars'}, {'id': 5, 'name': 'Jupiter'}, {'id': 6, 'name': 'Saturn'}, {'id': 7, 'name': 'Uranus'}, {'id': 8, 'name': 'Neptune'}]
>>> print(fetch("SELECT * FROM planet"))
キャッシュあり
[{'id': 1, 'name': 'Mercury'}, {'id': 2, 'name': 'Venus'}, {'id': 3, 'name': 'Earth'}, {'id': 4, 'name': 'Mars'}, {'id': 5, 'name': 'Jupiter'}, {'id': 6, 'name': 'Saturn'}, {'id': 7, 'name': 'Uranus'}, {'id': 8, 'name': 'Neptune'}]
>>> print(fetch("SELECT * FROM planet"))
キャッシュあり
[{'id': 1, 'name': 'Mercury'}, {'id': 2, 'name': 'Venus'}, {'id': 3, 'name': 'Earth'}, {'id': 4, 'name': 'Mars'}, {'id': 5, 'name': 'Jupiter'}, {'id': 6, 'name': 'Saturn'}, {'id': 7, 'name': 'Uranus'}, {'id': 8, 'name': 'Neptune'}]

次はplanet関数を定義します。

def planet(id):
  """Retrieve a record from the cache, or else from the database."""
  key = f"planet:{id}"
  res = Cache.hgetall(key)
  
  if res:
    print("キャッシュあり")
    return res
  
  print("キャッシュなし")
  sql = "SELECT `id`, `name` FROM `planet` WHERE `id`=?"
  res = Database.record(sql, (id,))
  
  if res:
    Cache.hmset(key, res)
    Cache.expire(key, TTL)
  
  return res

何回か実行してみます。idが文字列型になってしまっているのは残念ですが、キャッシュに入っていることが確認できます。

>>> print(planet(3))
キャッシュなし
{'id': 3, 'name': 'Earth'}
>>> print(planet(3))
キャッシュあり
{'name': 'Earth', 'id': '3'}
>>> print(planet(3))
キャッシュあり
{'name': 'Earth', 'id': '3'}

おわりに

実装されたAmazon ElastiCache Serverlessを、ハンズオンで使ってみました。 サーバレスでも元のElastiCacheでも、使用感は変わらないと感じました。すぐに起動する分、サーバレスの方がいいかもしれません。

この記事がどなたかのお役に立てれば幸いです。