AWSの一番好きなサービスはAWS Lambda な人です(再々掲)
注意書き
Preview版のサービスなので、GAになるまでに色々と変更される可能性は大いにあります。
AWS re:invent前後に発表されたVPC Lambda関係のアップデートやってみたシリーズラストかな?
ちょっと時間経っちゃいましたけど。
サンプルコード
サンプルソースはこんな感じ。汚くてすみません。
Node.js v12.xで確認済みです。
Airport, airline and route data にあるroute.csv を加工したものを、
RDSに放り込んで、乱数生成して、それにあった航空会社のIATA2レターコード※ を参照しているだけです。
'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ファイルも載せておきます。
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<-成田空港の空港コード)。