RDS Proxy ちょっと試してみた。


AWSの一番好きなサービスはAWS Lambda な人です(再々掲)

---

## 注意書き
`Preview版のサービスなので、GAになるまでに色々と変更される可能性は大いにあります。`


---

AWS re:invent前後に発表されたVPC Lambda関係のアップデートやってみたシリーズラストかな？
ちょっと時間経っちゃいましたけど。

### サンプルコード
サンプルソースはこんな感じ。汚くてすみません。
Node.js v12.xで確認済みです。

[Airport, airline and route data](https://openflights.org/data.html) にあるroute.csv を加工したものを、
RDSに放り込んで、乱数生成して、それにあった航空会社のIATA2レターコード※ を参照しているだけです。

```javascript:handler.js
'use strict';
const mysql = require('mysql');
const util = require('util');

const dbHostName = process.env.DB_HOSTNAME;
const dbUserName = process.env.DB_USERNAME;
const dbPassword = process.env.DB_PASSWORD;
const dbDatabase = process.env.DB_DATABASE;
const airlineCodeList = ['JL','NH','LH','AA','DL','AF','BA','VS','UA','SQ','QF','EK']

async function createConnection() {
  const connectionString = {
    host:     dbHostName,
    user:     dbUserName,
    password: dbPassword,
    database: dbDatabase
  };

  return mysql.createConnection(connectionString);
}

module.exports.index = async (event, context) => {
  console.log('Received event: ', JSON.stringify(event, null, 2));
  console.log('Received context: ', JSON.stringify(context, null, 2));

  const connection = await createConnection();
  const randomNun = Math.floor( Math.random() * (10 + 1 - 1) ) + 1;

  const airLineCode = airlineCodeList[randomNun];
  const queryString = `select * from routes where airline = '${airLineCode}';`;
  connection.query = util.promisify(connection.query);

  try {
    const results = await connection.query(queryString);
    console.log(results);
    connection.end(function(err) {});
    return {
      statusCode: 200,
      body: JSON.stringify(
        {
          results,
        },
        null,
        2
      ),
    };
  } catch (err) {
    console.log(err);
    return {
      statusCode: 500 ,
      body: JSON.stringify(
        {
          message: 'Internal Server Error.',
        },
        null,
        2
      ),
    };
  }

};

```

デプロイ自体は、`Serverless Framework` を使っているのでそのyamlファイルも載せておきます。

```yaml:serverless.yml
service: lambda-rds-proxy
provider:
  name: aws
  runtime: nodejs12.x
  logs:
    restApi: true
  logRetentionInDays: 30
# you can overwrite defaults here
  stage: ${opt:stage, self:custom.defaultStage}
  region: ap-northeast-1
vpc:
  securityGroupIds:
    - sg-06efd08cfe1ed01cc
  subnetIds:
    - subnet-175d6bbaaee0104ed
    - subnet-03b20131a8153e368
    - subnet-d030023fdf82dffa9
iamRoleStatements:
  - Effect: "Allow"
    Action:
      - "ec2:CreateNetworkInterface"
      - "ec2:DescribeNetworkInterfaces"
      - "ec2:DetachNetworkInterface"
      - "ec2:DeleteNetworkInterface"
    Resource:
      - "*"
custom:
  defaultStage: dev
  environment:
    dev: ${file(conf/dev.yml)}
    dev2: ${file(conf/dev2.yml)}
functions:
  notSetRdsProxy:
    handler: handler.index
    description: 'Not Setting RDS Proxy'
    memorySize: 256
    timeout: 25
    environment: ${self:custom.environment.${self:provider.stage}}
    role: defaultRole
    vpc:
      securityGroupIds:
        - sg-06efd08cfe1ed01cc
      subnetIds:
        - subnet-175d6bbaaee0104ed
        - subnet-03b20131a8153e368
        - subnet-d030023fdf82dffa9
    events:
      - httpApi:
          method: get
          path: /notrdsproxy
          integration: lambda-proxy
  notSetRdsProxyWithPC:
    handler: handler.index
    description: 'Not Setting RDS Proxy. With Provisioned Concurrency'
    memorySize: 256
    timeout: 25
    environment: ${self:custom.environment.${self:provider.stage}}
    role: defaultRole
    vpc:
      securityGroupIds:
        - sg-06efd08cfe1ed01cc
      subnetIds:
        - subnet-175d6bbaaee0104ed
        - subnet-03b20131a8153e368
        - subnet-d030023fdf82dffa9
    events:
      - httpApi:
          method: get
          path: /notrdsproxy/provisionedconcurrency
          integration: lambda-proxy
  setRdsProxy:
    handler: handler.index
    description: 'Setting RDS Proxy'
    memorySize: 256
    timeout: 25
    environment: ${self:custom.environment.${self:provider.stage}2}
    role: rdsProxyRole
    vpc:
      securityGroupIds:
        - sg-06efd08cfe1ed01cc
      subnetIds:
        - subnet-175d6bbaaee0104ed
        - subnet-03b20131a8153e368
        - subnet-d030023fdf82dffa9
    events:
      - httpApi:
          method: get
          path: /rdsproxy
          integration: lambda-proxy
  setRdsProxyWithPC:
    handler: handler.index
    description: 'Setting RDS Proxy. With Provisioned oncurrency'
    memorySize: 256
    timeout: 25
    environment: ${self:custom.environment.${self:provider.stage}2}
    role: rdsProxyRole
    vpc:
      securityGroupIds:
        - sg-06efd08cfe1ed01cc
      subnetIds:
        - subnet-175d6bbaaee0104ed
        - subnet-03b20131a8153e368
        - subnet-d030023fdf82dffa9
    events:
      - httpApi:
          method: get
          path: /rdsproxy/provisionedconcurrency
          integration: lambda-proxy
resources:
  Resources:
    defaultRole:
      Type: AWS::IAM::Role
      Properties:
        Path: /
        RoleName: defaultRole
        AssumeRolePolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Principal:
                Service:
                  - lambda.amazonaws.com
              Action: sts:AssumeRole
        Policies:
          - PolicyName: defaultPolicy
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action:
                    - logs:CreateLogGroup
                    - logs:CreateLogStream
                    - logs:PutLogEvents
                    - ec2:CreateNetworkInterface
                    - ec2:DescribeNetworkInterfaces
                    - ec2:DetachNetworkInterface
                    - ec2:DeleteNetworkInterface
                  Resource: "*"
    rdsProxyRole:
      Type: AWS::IAM::Role
      Properties:
        Path: /
        RoleName: rdsProxyRole
        AssumeRolePolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Principal:
                Service:
                  - lambda.amazonaws.com
              Action: sts:AssumeRole
        Policies:
          - PolicyName: rdsProxyPolicy
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action:
                    - logs:CreateLogGroup
                    - logs:CreateLogStream
                    - logs:PutLogEvents
                    - ec2:CreateNetworkInterface
                    - ec2:DescribeNetworkInterfaces
                    - ec2:DetachNetworkInterface
                    - ec2:DeleteNetworkInterface
                  Resource: "*"

```

ソースは同じで、環境変数に接続情報セットしてますが、設定ファイル分けて、RDS Proxy利用するパターンと、RDS直指定するパターンを作ってます。
あ、RDS ProxyのオススメとしてはIAM接続のほうがいいっぽいですね。Secret Managerに接続情報を保存できるので。

検証ついでに、Serverless FrameworkがHTTP API対応したので、一緒に試してたりします。
https://twitter.com/horike37/status/1225234647514144769
ご利用の際は最新バージョン(1.63.0)にアップグレードをお願いします。

## 検証
検証としては、コネクションMAX数(max_connections) を絞り込んで、 `Too many connections` が出るような状況にしています。

 ```
mysql> SHOW VARIABLES LIKE 'max_connections';
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| max_connections | 30    |
+-----------------+-------+
1 row in set (0.00 sec)
```

テスト前のprocesslist。事前にRDS Proxy経由でアクセスして、コネクション使い切った状態
もともと、rdsadmin(RDS自体の管理？)が4つ、rdsproxyadmin(RDS Proxyの管理？)が8つはあるらしい。
なお、adminの1つはEC2上のMysqlクライアントからprocess確認してました。

 ```
mysql> show processlist;
+-------+---------------+---------------------+--------+---------+------+-------------+------------------+
| Id    | User          | Host                | db     | Command | Time | State       | Info             |
+-------+---------------+---------------------+--------+---------+------+-------------+------------------+
|     2 | rdsproxyadmin | 172.31.97.131:46653 | NULL   | Sleep   |    0 | cleaning up | NULL             |
|     3 | rdsproxyadmin | 172.31.49.89:59431  | NULL   | Sleep   |    0 | cleaning up | NULL             |
|     4 | rdsadmin      | localhost           | NULL   | Sleep   |    1 | cleaning up | NULL             |
|     5 | rdsproxyadmin | 172.31.97.167:1915  | NULL   | Sleep   |    0 | cleaning up | NULL             |
|     6 | rdsproxyadmin | 172.31.97.126:48277 | NULL   | Sleep   |    0 | cleaning up | NULL             |
|     7 | rdsproxyadmin | 172.31.97.212:28995 | NULL   | Sleep   |    0 | cleaning up | NULL             |
|     8 | rdsadmin      | localhost           | NULL   | Sleep   |    1 | cleaning up | NULL             |
|     9 | rdsproxyadmin | 172.31.50.204:56213 | NULL   | Sleep   |    0 | cleaning up | NULL             |
|    10 | rdsproxyadmin | 172.31.48.22:2191   | NULL   | Sleep   |    0 | cleaning up | NULL             |
|    11 | rdsproxyadmin | 172.31.58.152:16189 | NULL   | Sleep   |    0 | cleaning up | NULL             |
|    12 | rdsadmin      | localhost           | NULL   | Sleep   |   11 | cleaning up | NULL             |
|    33 | rdsadmin      | localhost           | NULL   | Sleep   |  166 | cleaning up | NULL             |
|   100 | admin         | 172.31.10.192:42264 | testdb | Query   |    0 | starting    | show processlist |
|  7303 | admin         | 172.31.97.131:32677 | testdb | Sleep   |    7 | cleaning up | NULL             |
|  7304 | admin         | 172.31.97.126:48705 | testdb | Sleep   |    7 | cleaning up | NULL             |
|  7305 | admin         | 172.31.49.89:32765  | testdb | Sleep   |    7 | cleaning up | NULL             |
|  7307 | admin         | 172.31.97.167:47005 | testdb | Sleep   |    7 | cleaning up | NULL             |
|  7308 | admin         | 172.31.48.22:4785   | testdb | Sleep   |    7 | cleaning up | NULL             |
|  7309 | admin         | 172.31.50.204:19771 | testdb | Sleep   |    7 | cleaning up | NULL             |
|  7312 | admin         | 172.31.58.152:3927  | testdb | Sleep   |    9 | cleaning up | NULL             |
|  7317 | admin         | 172.31.97.212:59499 | testdb | Sleep   |    7 | cleaning up | NULL             |
| 11342 | admin         | 172.31.97.126:30921 | testdb | Sleep   |    7 | cleaning up | NULL             |
| 11343 | admin         | 172.31.48.22:1091   | testdb | Sleep   |    7 | cleaning up | NULL             |
| 11344 | admin         | 172.31.97.212:7309  | testdb | Sleep   |    7 | cleaning up | NULL             |
| 11345 | admin         | 172.31.97.131:37623 | testdb | Sleep   |    7 | cleaning up | NULL             |
| 11347 | admin         | 172.31.58.152:61557 | testdb | Sleep   |    8 | cleaning up | NULL             |
| 11348 | admin         | 172.31.50.204:42713 | testdb | Sleep   |    7 | cleaning up | NULL             |
| 11349 | admin         | 172.31.49.89:40485  | testdb | Sleep   |    7 | cleaning up | NULL             |
| 11351 | admin         | 172.31.97.167:52009 | testdb | Sleep   |    7 | cleaning up | NULL             |
| 13337 | admin         | 172.31.58.152:55069 | testdb | Sleep   |    7 | cleaning up | NULL             |
+-------+---------------+---------------------+--------+---------+------+-------------+------------------+
30 rows in set (0.00 sec)
```

テスト自体はEC2上のApache Benchから実施しています。

まず、RDS Proxyを利用しないパターン。
全滅とは行かないにしても、1600リクエストでエラーになりました。

```

Server Software:
Server Hostname:        eddovdtqad.execute-api.ap-northeast-1.amazonaws.com
Server Port:            443
SSL/TLS Protocol:       TLSv1.2,ECDHE-RSA-AES128-GCM-SHA256,2048,128
Server Temp Key:        ECDH P-256 256 bits
TLS Server Name:        eddovdtqad.execute-api.ap-northeast-1.amazonaws.com

Document Path:          /dev/notrdsproxy
Document Length:        41 bytes

Concurrency Level:      20
Time taken for tests:   28.725 seconds
Complete requests:      2000
Failed requests:        356
   (Connect: 0, Receive: 0, Length: 356, Exceptions: 0)
Non-2xx responses:      1644
Total transferred:      112425376 bytes
HTML transferred:       112066771 bytes
Requests per second:    69.63 [#/sec] (mean)
Time per request:       287.249 [ms] (mean)
Time per request:       14.362 [ms] (mean, across all concurrent requests)
Transfer rate:          3822.13 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       28  157 382.5     61    4598
Processing:    25  121 189.7     53    1937
Waiting:       25   86  94.7     52    1057
Total:         53  277 433.1    126    4665

Percentage of the requests served within a certain time (ms)
  50%    126
  66%    190
  75%    281
  80%    344
  90%    601
  95%    889
  98%   1458
  99%   2075
 100%   4665 (longest request)

```

続いて、RDS Proxyあり。2XX以外のリクエストつまり、エラーになったリクエストはありませんでした。

```

This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking eddovdtqad.execute-api.ap-northeast-1.amazonaws.com (be patient)
Completed 200 requests
Completed 400 requests
Completed 600 requests
Completed 800 requests
Completed 1000 requests
Completed 1200 requests
Completed 1400 requests
Completed 1600 requests
Completed 1800 requests
Completed 2000 requests
Finished 2000 requests


Server Software:
Server Hostname:        eddovdtqad.execute-api.ap-northeast-1.amazonaws.com
Server Port:            443
SSL/TLS Protocol:       TLSv1.2,ECDHE-RSA-AES128-GCM-SHA256,2048,128
Server Temp Key:        ECDH P-256 256 bits
TLS Server Name:        eddovdtqad.execute-api.ap-northeast-1.amazonaws.com

Document Path:          /dev/rdsproxy
Document Length:        161295 bytes

Concurrency Level:      20
Time taken for tests:   75.913 seconds
Complete requests:      2000
Failed requests:        1805
   (Connect: 0, Receive: 0, Length: 1805, Exceptions: 0)
Total transferred:      645507837 bytes
HTML transferred:       645174215 bytes
Requests per second:    26.35 [#/sec] (mean)
Time per request:       759.127 [ms] (mean)
Time per request:       37.956 [ms] (mean, across all concurrent requests)
Transfer rate:          8304.00 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       34  156 138.5    128    1569
Processing:    88  596 469.7    461    3435
Waiting:       61  184  97.2    161    1062
Total:        155  752 515.5    605    3600

Percentage of the requests served within a certain time (ms)
  50%    605
  66%    840
  75%    950
  80%   1020
  90%   1259
  95%   1736
  98%   2505
  99%   2984
 100%   3600 (longest request)
```

あえて、接続可能数少なくして、エラーにあるか試してみましたが、
RDS Proxy側でコネクションプーリングしているので、エラーにならないのはさすがですね。

## 紐付け
紐付けってどこで管理してんのかなと思って、LambdaやRDSのAPIドキュメントを見てみたのですが、
最終的には、利用するLambdaのロールに、`AWSLambdaRDSProxyExecutionRole-xxxxxx` っていうロールができていて、以下のようなポリシーが定義されていました。

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "rds-db:connect",
            "Resource": "arn:aws:rds-db:ap-northeast-1:123456789012:dbuser:prx-d5b34b70d97eb0eed/*"
        }
    ]
}
```

この設定があるロールを持つLambdaはRDS Proxy利用になるということのようです。
なので、あるLambdaでRDS Proxyを設定した場合、同一のロールを使っているLambdaは自動的にRDS Proxyを利用するようになるようですね。
※設定しない想定のLambdaがRDS Proxy使うようになっていて、ええっって思った人です。

## 色々
Lambdaの起動改善、
Provisioned Concurrency for Lambda Functions
そして、このRDS Proxy
で、だいぶVPC Lambdaが使いやすくなった感はありますね。
個人的には RDBMS使いたいケースもあるので、使いやすくなって嬉しいですね。

あとは、早くGAするといいですね。
最後に
`Preview版のサービスなので、GAになるまでに色々と変更される可能性は大いにあります。`

---

※ 航空会社、空港はICAO(国際民間航空機関)とIATA(国際航空運送協会)によって、2桁から4桁のコードが割り振られています。(JAL:JL、ANA:NH、羽田:HND/RJTT)
ちなみに、CloudFrontのエッジロケーションにはIATA3レターコードに近いものが振られているらしいです（東京だと、NRT<-成田空港の空港コード）。