私は普段、Python on Lambda でバックエンドを開発することが多いのですが、ネットで色々なエンジニアの方の記事を読んでいるとTypeScriptを利用している方が多いように感じます。
職場でもTypeScriptの採用事例が増えている為、TypeScriptにキャッチアップするメリットは感じつつも、これまでのPythonでの開発経験を活かしたいという思いもあり、なかなかキャッチアップに積極的になれなかったのですが、少し触ってみると思った以上にコールドスタートが短いことが分かり、ちゃんとキャッチアップした方が良いなと考えるようになりました。
特に、Pythonの場合はSDKであるboto3のimport時間がネックになることが多いのですが、JavaScript版のSDKは相対的にimport時間が短く済む為、REST APIをLambdaで書く場合などではTypeScriptを選択した方が良いケースも多いと思います。
そこで、本記事ではAWS X-Rayを使用してTypeScriptとPythonでのLambdaの実行時間を3つの観点から比較検証し、検証時点(2025年9月)でTypeScriptとPythonのどちらの方が優れているか、また使い分けの方針を検討したいと思います。
TypeScriptはトランスパイル後にJavaScriptとしてNode.jsランタイムで実行されます。本記事では開発者視点での言語選択をテーマとしている為、TypeScriptと表記しますが、実際の性能比較はNode.jsランタイムとPythonランタイムを比較している点にご注意ください。
事前知識
この記事では、以下の概念について基本的な知識があることを前提としています。
- AWS Lambda の基本概念
- コールドスタートとウォームスタートの違い
- AWS X-Rayトレースの仕組み
検証の前提条件
Lambda設定
今回の検証では、以下の設定で検証を行います。
項目 | 設定内容 |
---|---|
Runtime | Python 3.13、Node.js 22 |
Lambdaの形式 | zip形式 |
VPC内/VPC外 | VPC外 |
CPUアーキテクチャ | arm64 |
メモリサイズ | 512MB(明記がない場合) |
測定環境
項目 | 設定値 |
---|---|
リージョン | ap-northeast-1(東京) |
デプロイツール | AWS SAM |
測定ツール | AWS X-Ray(AWS SAMプロジェクト作成時のオプションで設定) |
測定回数 | 各条件毎に30回 |
測定値 | 30回の平均値(AWS::Lambdaセグメント) |
※ 測定した値の30回平均値に加えて、標準偏差をエラーバーでグラフに表示しています。
検証対象外の観点について
下記の観点は、本記事では検証対象外とします。
- リージョン差異や測定する時間帯による性能影響
- CPUアーキテクチャの違いによる性能影響
- IO依存の処理(DB接続など)による性能影響
検証結果
検証1 初期状態でのコールドスタート比較
最初の検証では、AWS SAMプロジェクト作成直後の状態でPythonとTypeScriptのコールドスタート時間を測定します。メモリサイズを256MB〜1769MBまで変化させて、各条件でのDurationを測定します。
検証結果
結果として、TypeScriptよりもPythonの方が約50msほどコールドスタートが短く、メモリサイズにはほとんど依存しないという結果になりました。※ Invokeフェーズの処理がほとんどないケースでは処理時間がメモリサイズに依存しない点については、以前の記事で検証しています。
検証2 CPU集約処理での比較
2つ目の検証では、ウォームスタート状態でCPU集約的な処理を実行し、言語間の性能差を測定します。メモリサイズを256MB〜1769MBまで変化させて、各条件でのDurationを測定します。※ アルゴリズムには「エラトステネスの篩」を採用し、公平を期す為にコードはClaude Codeを利用して作成しました。
TypeScript実装
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
function calculatePrimes(n: number): number[] {
const sieve = Array(n + 1).fill(true);
sieve[0] = sieve[1] = false;
for (let i = 2; i <= Math.sqrt(n); i++) {
if (sieve[i]) {
for (let j = i * i; j <= n; j += i) {
sieve[j] = false;
}
}
}
return Array.from({ length: n + 1 }, (_, i) => i).filter((i) => sieve[i]);
}
export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
const primes = calculatePrimes(5000000);
return {
statusCode: 200,
body: JSON.stringify({
message: 'ok!',
}),
};
};
Python実装
import json
def calculate_primes(n):
sieve = [True] * (n + 1)
sieve[0] = sieve[1] = False
for i in range(2, int(n**0.5) + 1):
if sieve[i]:
for j in range(i*i, n + 1, i):
sieve[j] = False
return [i for i in range(2, n + 1) if sieve[i]]
def lambda_handler(event, context):
primes = calculate_primes(5000000)
return {
'statusCode': 200,
'body': json.dumps({
'message': 'ok!',
}),
}
検証結果
結果として、TypeScriptはPythonと比べてメモリサイズのどの値においても約60%程度の時間で処理を完了させることが出来ました。
この点について調べたところ、TypeScriptはV8(Node.jsが採用しているJavaScriptの実行エンジン)によって、ループ部分の処理をプログラム実行中に最適化(JITコンパイル)しているのに対して、Pythonはインタープリタ実行の為に最適化が行われず、今回の差が生じていると考えられます。
※ Python3.13では実験的にJITコンパイルが組み込まれていますが、現時点のPythonランタイムでは利用することが出来ません。この点については、今後の改良が期待されます。
検証3 AWS SDK import時のコールドスタート時間測定
3つ目の検証では、AWS SDKクライアントのimport数を段階的に増やし、コールドスタートへの影響を測定します。※ サンプルコードはどちらもクライアント最大数import時のコードを記載しています。
検証で利用するライブラリ
今回の検証では、以下のライブラリを利用して検証を行います。
言語 | ライブラリ | バージョン |
---|---|---|
TypeScript | @aws-sdk/client-dynamodb | ^3.848.0 |
TypeScript | @aws-sdk/client-rds-data | ^3.848.0 |
TypeScript | @aws-sdk/client-s3 | ^3.848.0 |
TypeScript | @aws-sdk/client-secrets-manager | ^3.848.0 |
TypeScript | @aws-sdk/client-sqs | ^3.854.0 |
Python | boto3 | 1.39.9 |
※ ^記号は「このバージョン以上の互換バージョン」を示します。
TypeScript実装
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { S3Client } from '@aws-sdk/client-s3';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { RDSDataClient } from '@aws-sdk/client-rds-data';
import { SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
import { SQSClient } from '@aws-sdk/client-sqs';
const s3: S3Client = new S3Client({});
const dynamodb: DynamoDBClient = new DynamoDBClient({});
const rdsData: RDSDataClient = new RDSDataClient({});
const secretsManager: SecretsManagerClient = new SecretsManagerClient({});
const sqs: SQSClient = new SQSClient({});
export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
return {
statusCode: 200,
body: JSON.stringify({
message: 'ok!',
}),
};
};
Python実装
import json
import boto3
s3 = boto3.client('s3')
dynamodb = boto3.client('dynamodb')
rds_data = boto3.client('rds-data')
secrets_manager = boto3.client('secretsmanager')
sqs = boto3.client('sqs')
def lambda_handler(event, context):
return {
'statusCode': 200,
'body': json.dumps({
'message': 'ok!',
}),
}
検証結果
結果として、SDK import数の増加に伴い、両言語ともコールドスタート時間が長くなりますが、TypeScriptの方が影響を受けにくいということが分かりました。
この結果を解釈する為に、各SDKの特徴を簡単にまとめます。
SDK | 設計思想 | 特徴 |
---|---|---|
boto3 | オールインワン | boto3をimport後、任意のクライアント生成 |
AWS SDK for JavaScript (v3) | モジュラーアーキテクチャ | 必要なクライアントのみを個別import |
上記の設計の違いにより、TypeScriptは必要最小限のコードのみが読み込まれる為、SDK数増加時のコールドスタート時間が短くなっています。実際のサーバーレスアプリケーションでは複数のAWSサービスと連携することが多く、この特性はTypeScriptの大きな優位点と言えます。※ この設計の違いを考えると、クライアント数が増加するにつれてimport時間の差が小さくなっていくと思っていましたが(TypeScriptの方はimportするライブラリが増える為)、実際にはimport時間の増加は数十ms程度に留まりました。
考察
AWS Lambda のユースケースは膨大な為、それと比べると限定的な条件下での検証ではありますが、とても興味深い検証結果になったと思います。
検証1はPythonの方がコールドスタートが50ms程度短いという結果になりましたが、本番環境におけるコールドスタートの発生頻度が全体の1%程度という点を考えると、この差は極めて軽微だと言えます。
検証2では、CPU集約処理においてはTypeScriptの方が40%近く短い時間で処理を完了出来るという結果になりました。これはREST APIよりはバッチ処理のようなループ回数の多い処理で効果を発揮すると思われますが、少なくともTypeScriptを採用することで、CPU依存の処理がボトルネックになることはないと考えられます。
また、今回の検証の中で検証3の結果が最も重要だと考えています。実際のサーバーレスアプリケーションではLambdaのみで完結することは稀で、複数のAWSサービス(S3、DynamoDB、SQS等)と組み合わせることが一般的です。この場合、コールドスタートの影響を受けやすいREST APIのようなユースケースでは、SDKのimport時間が短いTypeScriptはパフォーマンス効率という観点で優位性が高いと言えます。
結論
以上、今回の検証の範囲で評価すると、REST APIのようにパフォーマンス要件が厳しいユースケースでは、まずはTypeScriptの利用を検討し、特定のライブラリ(NumPy、pandas、機械学習関連など)を利用する必要があるケースでは、非機能要件を満たすか検討した上でPythonを選択するというのが現時点での最善策と言えそうです。
最後に
この記事を最後まで読んで頂き、ありがとうございました。
今回の検証ではTypeScriptの方が優位という結果になりましたが、PythonもPython 3.13で実験的にJITコンパイルが導入されるなど進化を続けています。AWS LambdaでPythonのJITコンパイルが利用可能になった際には、また改めて比較検証してみたいと思います。