Serverless Operations, inc

>_cd /blog/id_3fgjxbqcki

title

Amazon Aurora DSQLの基礎について。AWS LambdaからDSQLへの接続を試す

今日はAmazon Aurora Distributed SQL(DSQL) がめでたく一般提供開始となったことを受けAWS Lambdaからの接続を行ってみます。

Amazon Aurora DSQL とは

https://docs.aws.amazon.com/ja_jp/aurora-dsql/latest/userguide/what-is-aurora-dsql.html

Amazon Aurora DSQL は、トランザクションワークロード用に最適化されたサーバーレスの分散リレーショナルデータベースです。Aurora DSQL は実質的に無制限のスケールを提供し、インフラストラクチャを管理する必要はありません。アクティブ/アクティブ高可用性アーキテクチャは、データに対して 99.99% の単一リージョンと 99.999% のマルチリージョンの可用性を提供します。

公式ドキュメントにはこのように記載されています。要はサーバレス型のマルチリージョンデータベースといわれているものです。通常のAuroraと異なりPostgreSQL互換のみが提供されています。

従来AWSではAuroraを用いたマルチリージョン構成では以下2つの選択肢が存在していました。

・クロスリージョンリードレプリカ

・Aurora Global Database

クロスリージョンリードレプリカ

セカンダリのリージョンには読み込み専用クラスターが起動され、レプリケーションは最大数十秒遅延します。

Aurora Global Database

クロスリージョンリードレプリカと同様にセカンダリリージョンでは読み込み専用となりますが、レプリケーション遅延は短く1秒未満にとどまり、プライマリリージョン障害時の切り替えも1分未満で行えます。

また、リリース当初にはなかった機能ですが、書き込み転送としてセカンダリリージョンへ発行された書き込みを伴うSQLは自動でプライマリリージョンで処理されます。(ただし数百msの遅延が生じます)

さらに、リリース直後はセカンダリーリージョンをプライマリに昇格させると、そのクラスターは独立してしまい、後ほど元のプライマリリージョンが復旧した際に戻すのが煩雑であるという課題がありましたが、スイッチバック機能が追加リリースされ、今はセカンダリとプライマリの入れ替えが簡単に行えるようになっています。

Amazon Aurora DSQL と NewSQL/分散SQL

今回GAとなったAurora DSQL は上記2つと異なりマルチマスター状態であり複数のリージョンでのデータ書き込みを可能とする分散SQLデータベースです。

NewSQL 的な特徴(マルチマスター・分散トランザクション・SQLサポートなど)を持ち、NewSQL として扱われることもあります。ただし AWS ではあえて NewSQL」ではなく「分散SQL(Distributed SQL)」という用語を公式に採用 しています。

https://aws.amazon.com/jp/blogs/aws/amazon-aurora-dsql-is-now-generally-available/

NewSQLという言葉はもともとNoSQLとの対比として生まれていますが、NoSQL自体がSQL以外(RBD以外)を指す言葉であることからその定義はベンダーや開発者によって千差万別です。実際調べてみると通常のAuroraもNewSQLの特徴を備えているという人と、AuroraはNewSQLのカテゴリではない、という人といろいろいるようです。

このためNoSQLの対比としてのNewSQLという用語を用いることを避け、分散SQLと呼称しています。実際、NoSQLかNewSQLかは開発者目線では実際にマルチマスターでSQLが扱えるかどうかが重要であり、その意味において分散SQLはよりユーザー目線の呼称でAmazonらしいなと感じました。

汎用PostgreSQLに対する制限

それより何より大事なことは、通常のAurora/PostgreSQLに比べて、Aurora DSQLならではの制限が複数存在しているということです。

例えばMySQL互換であるTiDBに代表されるような一般的なNewSQLデータベースも、MySQLでサポートされていることが、サポートされていなかったりするので、これは分散ストレージを用いたデータベースの宿命なのかもしれません。

https://docs.aws.amazon.com/ja_jp/aurora-dsql/latest/userguide/working-with-postgresql-compatibility-supported-sql-features.html

にSQLベースでその制限などがまとまっていますので、利用を検討されている方はぜひ目を通してみてください。またプライマリキーが特にDSQLにとっては重要です。

https://docs.aws.amazon.com/ja_jp/aurora-dsql/latest/userguide/working-with-primary-keys.html

例えば以下のようなテーブルはDSQLでは利用が推奨されません。

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

プライマリキーはデータのパーティションへの分散保存などに使われるため、単調増加の整数 ID は、Aurora DSQL の分散パーティション構造ではホットスポットを生みやすく、推奨されていません。代わりに UUID などのランダム性を持つキーを使用してください。

  • 書き込み量が多いテーブルでは、一定間隔で増加する整数をプライマリキーに使用しないでください。これが原因で、すべての新しい挿入を 1 つのパーティションに送信することで、パフォーマンスの問題が生じる可能性があります。代わりに、ランダム分散のプライマリキーを使用して、ストレージパーティション間で書き込みが均等に分散されるようにします。
  • テーブルの作成時にプライマリキーを定義します。このキーを後で変更したり、新しいプライマリキーを追加したりすることはできません。プライマリキーは、データのパーティショニングと書き込みスループットの自動スケーリングに使用されるクラスター全体のキーの一部になります。プライマリキーを指定しない場合、Aurora DSQL は合成非表示 ID を割り当てます。

さっそくやってみる

ではAurora DSQLを起動してLambdaから接続を行ってみます。

1.DSQLの起動

東京リージョンでクラスターの作成をクリックします。マネージメントコンソールは今までのRDS/Auroraとは異なり独立していますので注意してください。

シングルリージョンを選択します。

次の画面ではすべてデフォルトのままクラスターの作成をクリックします。

作成中となりますので数分待ちます。

状態がアクティブになれば無事起動が完了です。

2. Lambda からの接続

https://docs.aws.amazon.com/ja_jp/aurora-dsql/latest/userguide/SECTION-tutorials-lambda.html

にチュートリアルが記載されていますのでこちらをやっていきます。

関数を作成をクリックします。

適当な名前を付けて関数を作成をクリックします。

設定タブのアクセス権限から自動で作成されたロールをクリックします。

許可の追加、からインラインポリシーを作成をクリックします。

ポリシーをJSONモードにして以下を貼り付けます。

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Sid": "Statement1",
			"Effect": "Allow",
			"Action": ["dsql:DbConnectAdmin"],
			"Resource": ["*"]
		}
	]
}

適当な名前を付けてポリシーの作成をクリックします。

次に手元でLambda用zipパッケージを作成します。

mkdir myfunction
cd myfunction

以下の内容でpackage.jsonを作成します。

{
  "dependencies": {
    "@aws-sdk/dsql-signer": "^3.705.0",
    "assert": "2.1.0",
    "pg": "^8.13.1"
  }
}

次にindex.mjsを作成します。

import { DsqlSigner } from "@aws-sdk/dsql-signer";
import pg from "pg";
import assert from "node:assert";
const { Client } = pg;


async function dsql_sample(clusterEndpoint, region) {
  let client;
  try {
    // The token expiration time is optional, and the default value 900 seconds
    const signer = new DsqlSigner({
      hostname: clusterEndpoint,
      region,
    });
    const token = await signer.getDbConnectAdminAuthToken();
    // <https://node-postgres.com/apis/client>
    // By default `rejectUnauthorized` is true in TLS options
    // <https://nodejs.org/api/tls.html#tls_tls_connect_options_callback>
    // The config does not offer any specific parameter to set sslmode to verify-full
    // Settings are controlled either via connection string or by setting
    // rejectUnauthorized to false in ssl options
    client = new Client({
      host: clusterEndpoint,
      user: "admin",
      password: token,
      database: "postgres",
      port: 5432,
      // <https://node-postgres.com/announcements> for version 8.0
      ssl: true,
      rejectUnauthorized: false
    });

    // Connect
    await client.connect();

    // Create a new table
    await client.query(`CREATE TABLE IF NOT EXISTS owner (
      id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
      name VARCHAR(30) NOT NULL,
      city VARCHAR(80) NOT NULL,
      telephone VARCHAR(20)
    )`);

    // Insert some data
    await client.query("INSERT INTO owner(name, city, telephone) VALUES($1, $2, $3)", 
      ["John Doe", "Anytown", "555-555-1900"]
    );

    // Check that data is inserted by reading it back
    const result = await client.query("SELECT id, city FROM owner where name='John Doe'");
    assert.deepEqual(result.rows[0].city, "Anytown")
    assert.notEqual(result.rows[0].id, null)

    await client.query("DELETE FROM owner where name='John Doe'");

  } catch (error) {
    console.error(error);
    throw new Error("Failed to connect to the database");
  } finally {
    client?.end();
  }
  Promise.resolve();
}


// https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html
export const handler = async (event) => {
  const endpoint = event.endpoint;
  const region = event.region;
  const responseCode = await dsql_sample(endpoint, region);
  
  const response = {
    statusCode: responseCode,
    endpoint: endpoint,
  };
  return response;
};

npm installを実行し依存関係をインストールします。

その後zipを作成しアップロードします。

3. Lambdaからの接続テスト

次にLambda用テストイベントを作成します。

{
  "endpoint": "aeabufrvm3rqsuazqzukkes42e.dsql.ap-northeast-1.on.aws",
  "region": "ap-northeast-1"
}

AWSのドキュメントにregionの指定はありませんでしたが、実際には必要です。実行すると以下の様に200が戻れば成功です。

では最後にコードを以下に変更して再度テストを実行します。

import { DsqlSigner } from "@aws-sdk/dsql-signer";
import pg from "pg";
import assert from "node:assert";
const { Client } = pg;

async function dsql_sample(clusterEndpoint, region) {
  let client;
  try {
    const signer = new DsqlSigner({
      hostname: clusterEndpoint,
      region,
    });
    const token = await signer.getDbConnectAdminAuthToken();

    client = new Client({
      host: clusterEndpoint,
      user: "admin",
      password: token,
      database: "postgres",
      port: 5432,
      ssl: true,
      rejectUnauthorized: false
    });

    await client.connect();

    await client.query(`CREATE TABLE IF NOT EXISTS owner (
      id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
      name VARCHAR(30) NOT NULL,
      city VARCHAR(80) NOT NULL,
      telephone VARCHAR(20)
    )`);

    await client.query("INSERT INTO owner(name, city, telephone) VALUES($1, $2, $3)", 
      ["John Doe", "Anytown", "555-555-1900"]
    );

    const result = await client.query("SELECT id, city FROM owner WHERE name='John Doe'");
    assert.deepEqual(result.rows[0].city, "Anytown");
    assert.notEqual(result.rows[0].id, null);

    // 取得したデータを保持
    const responseData = {
      id: result.rows[0].id,
      city: result.rows[0].city
    };

    // クリーンアップ
    await client.query("DELETE FROM owner WHERE name='John Doe'");

    return responseData;

  } catch (error) {
    console.error(error);
    throw new Error("Failed to connect to the database");
  } finally {
    client?.end();
  }
}

export const handler = async (event) => {
  const endpoint = event.endpoint;
  const region = event.region;

  try {
    const result = await dsql_sample(endpoint, region);

    return {
      statusCode: 200,
      body: JSON.stringify({
        endpoint,
        result,
      }),
    };
  } catch (error) {
    return {
      statusCode: 500,
      body: JSON.stringify({
        message: error.message,
      }),
    };
  }
};
Response:
{
  "statusCode": 200,
  "body": "{\"endpoint\":\"aeabufrvm3rqsuazqzukkes42e.dsql.ap-northeast-1.on.aws\",\"result\":{\"id\":\"143707ec-965e-4eb4-a334-1be1b5ef66f8\",\"city\":\"Anytown\"}}"
}

DSQL 独自項目の解説

現時点でDSQLはpostgresというデータベースのみが利用可能となっておりその他のcreatedbは実行できません。また、

DBへの接続は以下の様に2種類が準備されています。RDSのIAM認証がベースとなっています。LambdaにアタッチされているIAMロールのポリシーが"Action": ["dsql:DbConnectAdmin"],となっており、それを介して一時トークンを入手しています。

Written by
編集部

亀田 治伸

Kameda Harunobu

  • Facebook->
  • X->
  • GitHub->

Share

Facebook->X->
Back
to list
<-