問題
Lambda + RDS Proxy + Aurora構成のアプリケーションで、Lambdaの同時接続数がたびたび上限(1000)を突破することがありました。サービスクォータで同時接続数の上限を引き上げてみたものの、Auroraの接続上限(db.r6g.xlarge
で2000)を超える設定をしても意味がないので、根本的な解決にはなりませんでした。
RDS Proxyのメトリクスをみたところ、DB接続上限の80 %(= 1600)に設定しているはずなのに4000を超えるほどのDatabaseConnections
がありました。あきらかにおかしい挙動だったので、ピン留めを疑いました。
ピン留めによる接続数増加の原因
RDS Proxyのドキュメントをみたら以下の記載がありました。
ステートメントのテキストサイズが 16 KB を超える場合、プロキシはセッションを現在の接続に固定します。
直近で追加したAPIが怪しいと感じ、内部のDB処理を調べたらほとんどのクエリのテキストサイズが100 KBを超えていました。これがピン留めの原因でした。
改善案
最近のトラフィック増加によってLambdaの使用に限界を感じてはいたのですが、置き換え先のECS on Fargateのスペックやタスク数のチューニングがまだ完了していないため、すぐに移行するのは難しい状況でした。そのため、問題のAPIが使用するDB接続のみを別のRDS Proxyに振り分けることで、ECS on Fargate移行までのつなぎとしての短期的な改善を図ることにしました。
実装手順
1. 新しいRDS Proxyの作成
問題のAPI用に新しいRDS Proxyを作成し、別の接続情報として設定します。これにより、特定のリポジトリでのみ新しいプロキシを使用し、他の処理には既存のプロキシを使用できるようにします。
2. NestJSの設定変更
NestJSのTypeOrmModule.forRootAsync
を利用し、2つのデータソースを設定します。既存のデータソースをデフォルトのまま使用し、新しいRDS Proxy用のデータソースをsecondDataSource
として追加します。
// NOTE: 既存のDB接続
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
dataSourceFactory: async (options: DataSourceOptions) => {
const AppDataSource = new DataSource(options);
return await AppDataSource.initialize();
},
useFactory: async (configService: ConfigService) => {
const env = configService.get<string>('ENV') ?? 'local';
const dbConfig = { ...dbConfigs[env] };
return dbConfig;
},
inject: [ConfigService]
}),
// NOTE: 新RDS Proxy用のデータソースを追加
TypeOrmModule.forRootAsync({
name: 'secondDataSource', // 新プロキシ用の名前を設定
imports: [ConfigModule],
dataSourceFactory: async (options: DataSourceOptions) => {
const SecondDataSource = new DataSource(options);
return await SecondDataSource.initialize();
},
useFactory: async (configService: ConfigService) => {
const env = configService.get<string>('ENV') ?? 'local';
const dbConfig = { ...secondDbConfigs[env] };
return dbConfig;
},
inject: [ConfigService]
}),
3. DB設定の追加
2つのプロキシ用の接続設定をそれぞれ用意し、dbConfigs
とsecondDbConfigs
として保持します。
export const dbConfigs: DBConfigs = {
dev: {
type: 'mysql',
host: '<endpoint>',
port: 3306,
username: '<username>',
password: '<password>',
database: '<database>',
entities,
synchronize: false
}
};
export const secondDbConfigs: DBConfigs = {
dev: {
type: 'mysql',
host: '<endpoint>',
port: 3306,
username: '<username>',
password: '<password>',
database: '<database>',
entities,
synchronize: false
}
};
4. 特定のリポジトリで新プロキシを使用
特定のリポジトリプロバイダで新しいプロキシを使用するため、getDataSourceToken('secondDataSource')
を使って新しいデータソースを指定します。既存のプロキシを使うリポジトリでは、getDataSourceToken()
のままにすることでデフォルト設定が使用されます。
export const SecondRepositoryProvider: FactoryProvider<ISecondRepository> = {
provide: SecondRepositoryToken,
useFactory: factory,
inject: [getDataSourceToken('secondDataSource')]
}
結果
この変更により、ピン留めが発生していたクエリは新しいプロキシにルーティングされるため、既存のRDS ProxyでのDatabaseConnections
が適切に抑えられるようになりました。Lambdaの同時接続数も安定し、Auroraの接続上限を超えることなく一旦運用ができるようになりました。