このハンズオンの目的
急激なアクセスに対応できる、AWSの非同期処理(イベント駆動)
アーキテクチャを構築します
Amazon SQS をバッファとして挟むことで、DynamoDB等の負荷を分散し、
システムの耐障害性を高める設計です
システム構成図

事前準備(AWSリソースの作成)
ハンズオンを始める前に、データを保存・転送するための土台を作成
■ 1. SQSキューの作成- アクセス手順: AWSコンソール > SQS > キューを作成
- タイプ: 標準キュー
- キュー名:
OJT_OrderQueue_[name] - メモ事項: 発行された「URL」(例:
https://sqs.ap-northeast-1.amazonaws.com/123456789012/OJT_OrderQueue_name)をメモ
■ 2. DynamoDBテーブルの作成- アクセス手順: AWSコンソール > DynamoDB > テーブルを作成
- テーブル名:
OJT_Orders_[name] - パーティションキー:
order_id(文字列)
ハンズオン手順
Step 1: 受付Lambdaの作成と単体テスト
APIからのリクエストを受け取り、データを作成してSQSにメッセージを投入する
- 1.1 関数の作成:
OJT_OrderReceiverFunction_[name] - 1.2 IAMロールの設定: 受付Lambdaの実行ロールに
AmazonSQSFullAccessを付与 - 1.3 環境変数の設定:
QUEUE_URLに作成した SQS の URL を設定
Python – OJT_OrderReceiverFunction
import json
import os
import uuid
import boto3
sqs = boto3.client('sqs')
# 環境変数から設定
QUEUE_URL = os.environ.get('QUEUE_URL')
def lambda_handler(event, context):
try:
# API Gateway または単体テストからの入力を受け取る
body = json.loads(event.get('body', '{}')) if isinstance(event.get('body'), str) else event.get('body', {})
# データの生成
order_id = str(uuid.uuid4())
order_data = {
'order_id': order_id,
'item': body.get('item', 'Unknown'),
'price': body.get('price', 0),
'status': 'PENDING'
}
# SQSへメッセージ送信
response = sqs.send_message(
QueueUrl=QUEUE_URL,
MessageBody=json.dumps(order_data)
)
return {
'statusCode': 200,
'headers': {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods': 'OPTIONS,POST'
},
'body': json.dumps({
'message': 'Order accepted',
'order_id': order_id,
'message_id': response['MessageId']
})
}
except Exception as e:
return {
'statusCode': 500,
'body': json.dumps({'error': str(e)})
}
単体テスト手順:
- Lambdaコンソールの「テスト」タブから新しいテストイベントを作成
- 以下のJSONを入力して「テスト」を実行
JSON – テストイベント
{
"body": "{\"item\": \"Apple\", \"price\": 150}"
}
statusCode: 200が返ることを確認し
SQSコンソールで「利用可能なメッセージ」が1に増えているか確認
Step 2: 処理Lambdaの作成と単体テスト
SQSから自動でメッセージを受け取り、DynamoDBに保存する処理
- 2.1 関数の作成:
OJT_OrderRegisterFunction_[name] - 2.2 IAMロールの設定: 処理Lambdaの実行ロールに以下を付与
AWSLambdaSQSQueueExecutionRole(SQS受信用)AmazonDynamoDBFullAccess(DynamoDB書き込み用)
- 2.3 環境変数の設定:
TABLE_NAMEにOJT_Orders_[name]を設定
Python – OJT_OrderRegisterFunction
import json
import os
import time # ※テスト用遅延処理の呼び出しに必要
import boto3
dynamodb = boto3.resource('dynamodb')
TABLE_NAME = os.environ.get('TABLE_NAME')
table = dynamodb.Table(TABLE_NAME)
def lambda_handler(event, context):
for record in event.get('Records', []):
body = record['body']
# SQSからの文字列、または手動テストの辞書型(dict)双方に対応
order_data = json.loads(body) if isinstance(body, str) else body
# ステータスを完了に変更
order_data['status'] = 'COMPLETED'
# DynamoDBへ書き込み
table.put_item(Item=order_data)
print(f"Successfully processed order: {order_data.get('order_id')}")
return {'statusCode': 200, 'body': 'Success'}
SQSトリガーの設定:
Lambdaの画面で「トリガーを追加」> SQS を選択し、
OJT_OrderQueue_[name]を割り当て
単体テスト手順:
テストイベントのイベントテンプレートで「SQS」を選択し、
以下のように body をJSON文字列で囲んでテスト実行。
(文字列にしないと TypeErrorになります)
JSON – テストイベント
{
"Records": [
{
"body": "{\"order_id\": \"test-123\", \"item\": \"Orange\", \"price\": 200}"
}
]
}
テスト実行後、DynamoDBの OJT_Orders_[name]
テーブルにデータが書き込まれたか確認
Step 3: API Gatewayの統合と結合テスト
受付LambdaをHTTP経由で呼び出せるようAPIを作成
■ API Gatewayの構築- API Gateway >「HTTP API」を作成
- 名前:
OJT_OrderAPI_[name] - ルートに
POST /ordersを追加 - 統合先に ステップ1の
OJT_OrderReceiverFunction_[name]を指定 - CORS設定 で
Access-Control-Allow-Origin: *を設定
■ 結合テスト(Postmanなど)- Method: POST
- URL:
https://<your-api-id>.execute-api.ap-northeast-1.amazonaws.com/orders - Header:
Content-Type: application/json - Body (raw JSON):
JSON – Request Body
{
"item": "Postmanテスト商品",
"price": 3000
}
送信後、200 OKが返ることと、
DynamoDB にアイテムが追加されることを確認
Step 4: HTMLフロントエンドとの結合および負荷テスト
クライアント画面から連続リクエストを送り、
SQSの「バッファ(緩衝材)」としての挙動を検証
⚠️ 注意: index.html をそのままダブルクリックで開かないこの index.html を file://形式で直接開くと、
ブラウザのCORSポリシーやオリジン制限により Failed to fetchや
接続拒否(Connection Refused)のエラーが発生し
API Gatewayへのリクエストが失敗する場合があります
VS Codeの拡張機能「Live Server」などを使い、http://127.0.0.1:5500
のようなローカルサーバー経由(http://オリジン)で
起動してからアクセスしてください
動作確認用フロントエンド
(index.html)
HTML – index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>注文処理テスト (API Gateway + SQS + DynamoDB)</title>
<style>
:root {
--primary-color: #232f3e; /* AWS Dark Blue */
--accent-color: #ff9900; /* AWS Orange */
--bg-color: #f2f3f3;
--card-bg: #ffffff;
--text-color: #16191f;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
margin: 0;
padding: 40px 20px;
display: flex;
justify-content: center;
}
.container {
background-color: var(--card-bg);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 30px;
width: 100%;
max-width: 500px;
}
h1 {
font-size: 1.5rem;
color: var(--primary-color);
margin-top: 0;
margin-bottom: 20px;
border-bottom: 2px solid var(--accent-color);
padding-bottom: 8px;
}
.form-group {
margin-bottom: 18px;
}
label {
display: block;
font-weight: 600;
margin-bottom: 6px;
font-size: 0.9rem;
}
input[type="text"],
input[type="number"],
input[type="url"] {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
font-size: 1rem;
}
input:focus {
outline: none;
border-color: var(--accent-color);
box-shadow: 0 0 0 2px rgba(255, 153, 0, 0.2);
}
.btn-group {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
button {
flex: 1;
background-color: var(--accent-color);
color: #ffffff;
border: none;
border-radius: 4px;
padding: 12px;
font-size: 0.95rem;
font-weight: bold;
cursor: pointer;
transition: background-color 0.2s ease;
}
button.burst-btn {
background-color: #d9534f; /* 赤系のボタン */
}
button.burst-btn:hover {
background-color: #c9302c;
}
button:hover {
background-color: #e68a00;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.response-box {
margin-top: 25px;
padding: 15px;
border-radius: 4px;
font-size: 0.9rem;
display: none;
}
.response-box.success {
display: block;
background-color: #e8f5e9;
border: 1px solid #a5d6a7;
color: #1b5e20;
}
.response-box.error {
display: block;
background-color: #ffebee;
border: 1px solid #ef9a9a;
color: #b71c1c;
}
.field-title {
font-weight: bold;
margin-top: 8px;
}
pre {
background: #f8f9fa;
padding: 10px;
border-radius: 4px;
overflow-x: auto;
white-space: pre-wrap;
word-wrap: break-word;
max-height: 250px;
}
.note {
font-size: 0.8rem;
color: #666;
margin-top: 15px;
line-height: 1.4;
}
</style>
</head>
<body>
<div class="container">
<h1>注文発行テスト</h1>
<div class="form-group">
<label for="apiUrl">API Gateway URL</label>
<input type="url" id="apiUrl" placeholder="https://xxxx.execute-api.ap-northeast-1.amazonaws.com/orders">
</div>
<div class="form-group">
<label for="item">商品名 (item)</label>
<input type="text" id="item" value="連打テスト商品">
</div>
<div class="form-group">
<label for="price">価格 (price)</label>
<input type="number" id="price" value="1000">
</div>
<div class="btn-group">
<button id="singleBtn" onclick="sendOrders(1)">1件送信</button>
<button id="burstBtn" class="burst-btn" onclick="sendOrders(20)">🚀 20件連続送信</button>
</div>
<div id="responseBox" class="response-box">
<div id="statusMessage"></div>
<div class="field-title">処理結果ログ:</div>
<pre id="jsonResult"></pre>
</div>
<div class="note">
※ 「20件連続送信」を押すと、20リクエストが並列で送信されます。<br>
※ SQSコンソールで「利用可能なメッセージ (Messages Available)」に一時的にキューが溜まることを確認できます。
</div>
</div>
<script>
async function sendOrders(count) {
const apiUrl = document.getElementById('apiUrl').value.trim();
const itemBase = document.getElementById('item').value.trim();
const priceBase = Number(document.getElementById('price').value);
const singleBtn = document.getElementById('singleBtn');
const burstBtn = document.getElementById('burstBtn');
const responseBox = document.getElementById('responseBox');
const statusMessage = document.getElementById('statusMessage');
const jsonResult = document.getElementById('jsonResult');
if (!apiUrl) {
alert('API Gateway の URL を入力してください');
return;
}
// ボタンの無効化と画面初期化
singleBtn.disabled = true;
burstBtn.disabled = true;
burstBtn.innerText = `送信中 (0/${count})...`;
responseBox.className = 'response-box';
responseBox.style.display = 'block';
statusMessage.innerHTML = '<strong>送信処理中...</strong>';
jsonResult.innerText = '';
let successCount = 0;
let failCount = 0;
let logs = [];
// 1件分のPOSTを行う関数
const sendSingleRequest = async (index) => {
try {
const res = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
item: `${itemBase}-${index}`,
price: priceBase + index
})
});
const data = await res.json();
if (res.ok) {
successCount++;
logs.push(`[#${index}] 成功 (200) - order_id: ${data.order_id}`);
} else {
failCount++;
logs.push(`[#${index}] エラー (${res.status}) - ${JSON.stringify(data)}`);
}
} catch (err) {
failCount++;
logs.push(`[#${index}] 通信失敗 - ${err.message}`);
}
};
// 並列でリクエストを生成
const requests = [];
for (let i = 1; i <= count; i++) {
requests.push(sendSingleRequest(i));
}
// 全リクエストの完了を待機
await Promise.all(requests);
// 結果の出力
if (failCount === 0) {
responseBox.classList.add('success');
statusMessage.innerHTML = `<strong>✅ 全 ${successCount} 件の送信が完了しました!(SQSへキューイング済)</strong>`;
} else {
responseBox.classList.add('error');
statusMessage.innerHTML = `<strong>⚠️ 処理完了: 成功 ${successCount} 件 / 失敗 ${failCount} 件</strong>`;
}
jsonResult.innerText = logs.join('\n');
// ボタン復元
singleBtn.disabled = false;
burstBtn.disabled = false;
burstBtn.innerText = '🚀 20件連続送信';
}
</script>
</body>
</html>
SQSバッファ実験
演習: 「処理遅延とSQSの滞留」を体験する- 処理Lambdaに遅延を追加:
OJT_OrderRegisterFunction_[name]内の先頭に
import timeを追加し、書き込み処理の前に time.sleep(2)
を挿入してデプロイを実行 - 20件の連続リクエストを送信: HTMLの「🚀 20件連続送信」をクリック。
- 結果の観察:
- フロント側: 20件のリクエスト自体は数秒で「すべて成功(200 OK)」で完了
- SQSコンソール:
「利用可能なメッセージ」の数字が一時的に数件〜20件に留まり、
数秒ごとに1件ずつメッセージが減っていく挙動を確認 - DynamoDBコンソール: 数十秒後、すべてのデータが1件も欠けることなく
COMPLETEDで保存完了したことを確認
つまずきやすいポイントとトラブルシューティング(FAQ)
⚠️ TypeError: the JSON object must be str…原因: Consumer Lambdaの単体テストでbodyを辞書型(dict)で書いてしまっている。
解決策: テストイベント内のbodyの値をダブルクォーテーションで囲む
⚠️ NameError: name ‘time’ is not defined原因: 擬似遅延処理で time.sleep()を使ったが import timeが漏れている。
解決策: コード先頭に import timeを追記
⚠️ メッセージが「処理中」に留まり動かなくなる原因: Consumer Lambdaの実行中に例外が発生し、
Ackされずに再試行ループになっている。
解決策: CloudWatch Logs でエラーログを確認してコードを修正するか、
SQS画面から「メッセージをパージ」して初期化
⚠️ HTMLから送信すると CORS エラーになる原因: API Gateway や Producer Lambda
のレスポンスヘッダー設定が不足している。
解決策: Producer Lambda の返却ヘッダーに
'Access-Control-Allow-Origin': '*'
が入っているか確認
おわりに / 後片付け(リソース削除)
学習が終了したら、不要な課金を防ぐために、必ず以下の順番でリソースを削除してください
- API Gatewayの削除(作成したHTTP APIを削除)
- Lambda関数の削除(
OJT_OrderReceiverFunction_[name] と
OJT_OrderRegisterFunction_[name] の両方) - SQSキューの削除(
OJT_OrderQueue_[name]) - DynamoDBテーブルの削除(
OJT_Orders_[name]) - IAMロールの確認(Lambda関数の削除に伴い
自動生成されたロールが残っている場合は、IAM > ロール から削除)
以上で、Step 1からStep 4までの一連のハンズオンは完了です
参考サイト
Amazon Simple Queue Service とは – AWS公式ドキュメント
Amazon API Gateway とは – AWS公式ドキュメント