6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

mysql2/promise を使用し、VPC内のLambdaから RDS Proxy 経由で RDSに接続してみた [Node.js]

Last updated at Posted at 2022-07-23

はじめに

RDS Proxy 経由で MySQL にアクセスするLambdaをVPC内で実行させましたので、手順をまとめます。

以前、mysqlモジュールを使用して同様の処理をしましたが、mysql2モジュールの方がコード量が少ないため、今回はmysql2を使用してみます。

スクリーンショット 2022-06-23 22.00.58.png

RDS Proxyの作成

RDS Proxyは、以前作成した記事がありますので、こちらを参考にしてください。

private subnetにLambdaを作成

設定

  • IAMロール

    • AWSLambdaENIManagementAccessを付与します.これによってVPC上でLambdaが実行できます。
  • タイムアウト時間

    • 30秒にします。実行に数秒かかります。
  • 環境変数

    • RDS_HOSTNAME:proxyのライターエンドポイント
    • RDS_DB_NAME:データベース名
    • RDS_USERNAME:ユーザー名
    • RDS_PASSWORD:パスワード
  • VPC

    • private subnetに設置
    • sgの設定は以下の通り
設定 タイプ ポート ソース
Lambda All All 0.0.0.0/0
Proxy MySQL/Aurora 3306 LambdaのSG
Aurora MySQL/Aurora 3306 ProxyのSG

mysql2モジュール

Lambdaは、Nodejsを使用します。

mysql2モジュールを使用するため、ローカルでmysql2モジュールをインストールします。

LambdaLayerでアップロード

$ mkdir nodejs
$ cd nodejs
$ npm init -y
$ ls
package.json

$ npm install mysql2

$ tree
.nodejs
├── node_modules
├── package-lock.json
└── package.json]

$ cd ..

$ zip -r nodejs.zip nodejs

nodejsフォルダごとzip化すること。
zip化したnodejs.zipをLambdaLayerにアップしましょう。

具体的なアップロード方法は、以前作成した記事を参考にしてください

Lambdaに直接アップロード

LambdaLayerを使用しない場合、以下のように作成します。

$ mkdir nodejs
$ cd nodejs
$ tree
.nodejs
├── package-lock.json
└── package.json

// package name以外はデフォルトにしました
$  npm init -y

$ npm install mysql

$ touch index.js

$ tree
.nodejs
├── index.js
├── node_modules
├── package-lock.json
└── package.json

$ zip -r nodejs.zip *

nodejs.zipをLambdaにアップロードします
スクリーンショット 2022-11-04 12.56.40.png

Lambdaのコード

INSERTの場合

const hostname = process.env.RDS_HOSTNAME;
const user_name = process.env.RDS_USERNAME;
const password = process.env.RDS_PASSWORD;
const databaseName = process.env.RDS_DB_NAME;
const mysql = require('mysql2/promise');

exports.handler = async (event) => {
  // console.log('event:', JSON.stringify(event, null, 2));

  const connection = await mysql.createConnection({
    host: hostname,
    user: user_name,
    password: password,
    database: databaseName,
  });

  const now = new Date().toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' });
  const sql = 'INSERT INTO database_table (title, created_at) VALUES (?, ?);';
  const inserts = ['test', now];

  try {
    await connection.beginTransaction();

    const results = await connection.execute(sql, inserts);
    console.log(results);

    await connection.commit();
  } catch (error) {
    await connection.rollback();
    console.log(error.message);
    throw error;
  } finally {
    connection.end();
  }
};

今回は、database_tableというテーブルに、タイトルと作成日時を登録してみました。
'INSERT INTO databaseTable (title, created_at) VALUES (?, ?);'

以前、下記の記事でmysqlモジュールで書いたコードよりもコード量が少なかったため、mysql2モジュールの使用をおすすめします。

mysql2の使用メソッド

  • createConnection
    • RDSのmysqlと接続します
  • beginTransaction
    • トランザクションを開始します
  • query
    • クエリを実行します
  • commit
    • トランザクション中のデータ処理を確定します
  • rollback
    • トランザクション中のデータ処理を取り消され、トランザクション実行前の状態に戻りトランザクションを終了します。
  • end
    • データベースと接続を終了します。

SELECT文の場合 ①

INSERTではなく、SELECT文を使った場合、以下のようになります。
usersテーブルからデータを全て取得する、シンプルな内容です。

const dbInfo = {
  host: process.env.RDS_HOSTNAME,
  user: process.env.RDS_USERNAME,
  password: process.env.RDS_PASSWORD,
  database: process.env.RDS_DB_NAME,
};
const mysql = require('mysql2/promise');

exports.handler = async (event) => {
  const connection = await mysql.createConnection(dbInfo);

  const usersSql = 'SELECT * FROM `users`';
  try {
    await connection.beginTransaction();

    const usersSqlResults = await connection.query(usersSql);
    console.log('Received usersSqlResults[0]:', JSON.stringify(usersSqlResults[0], null, 2));

    await connection.commit();
  } catch (error) {
    await connection.rollback();
    console.log(error.message);
    throw error;
  } finally {
    connection.end();
  }
};

SELECT文の場合 ②

複数のお店のIDからそれぞれのユーザーIDを配列に収め、配列を返すという内容になります。
条件は、お店のオープン時間が12時より早く、かつ、閉店時間が20時より遅いことです。

// db情報は、変数にしてもよい
const dbInfo = {
    host: process.env.RDS_HOSTNAME,
    user: process.env.RDS_USERNAME,
    password: process.env.RDS_PASSWORD,
    database: process.env.RDS_DB_NAME,
};
const mysql = require('mysql2/promise');

const getStoreIds = [23, 25, 29];
const startTime = 'start_time_Monday';
const endTime = 'end_time_Monday';

exports.handler = async (event) => {
  let userId = [];
  for (const storeId of getStoreIds) {
    const connection = await mysql.createConnection(dbInfo);

    const storesInserts = [storeId];
    const storesSql =
      'SELECT `user_id` FROM `stores` WHERE `store_id` = ? AND ' + startTime + ' < 12:00 AND ' + endTime + ' > 20:00;';
    try {
      await connection.beginTransaction();

      const storesSqlResults = await connection.query(storesSql, storesInserts);
      console.log('Received storesSqlResults[0]:', JSON.stringify(storesSqlResults[0], null, 2));

      // 値がなければスキップ
      if (!storesSqlResults[0][0]) continue;

      // 返ったuser_idを配列に加える
      storesSqlResults[0].map((item, index) => {
        userId.push(item.token);
      });

      await connection.commit();
    } catch (error) {
      await connection.rollback();
      console.log(error.message);
      throw error;
    } finally {
      connection.end();
    }
  }
  console.log('Received userId:', JSON.stringify(userId, null, 2));
  return userId;
};

SQL内のstoresSqlにある?には、storesInsertsが入ります。

対して、SQLのテーブル名やカラム名に変数を当てたい場合、SQL内に文字列として結合させます。
今回で言いますと、startTimeendTimeですね。

参考記事

6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?