Skip to Content

ステップ3:位置情報機能とデータ管理 - DynamoDBとNoSQLデータベース設計

今回のステップの概要とモバイルアプリとの関連について

このステップでは、ステップ2で構築した認証システムに位置情報機能とデータ管理機能を追加します。具体的には、Amazon DynamoDB、NoSQLデータベース、位置情報処理、ジオクエリを実装してモバイルアプリの中核となるデータ管理基盤を構築します。

モバイルアプリにとって、位置情報とデータ管理システムは「デジタル住所録」のような役割を果たします。DynamoDBは特殊なファイリングシステムのようなもので、従来のデータベースとは異なる柔軟なデータ保存や高速検索が可能です。これにより、ユーザーの位置情報や投稿情報を効率的に管理し、近隣ユーザーの検索や位置ベースの機能を実現できます。

このステップで学ぶこと

リソースの関わりと構成説明

ステップ3で作成するリソースは、モバイルアプリケーションのデータ層を構築するものです。それぞれのリソースがモバイルアプリにどのように関わるのかを説明します。

Amazon DynamoDBとモバイルアプリの関わり

Amazon DynamoDB「NoSQLデータベース」は、モバイルアプリケーションの「高速デジタルファイリングシステム」のような役割を果たします。ユーザー情報、位置情報、投稿データを柔軟な構造で保存し、ミリ秒レベルの高速アクセスを実現します。これにより、大量のユーザーが同時にアクセスしても安定したパフォーマンスを維持できます。

Global Secondary Index(GSI)とモバイルアプリの関わり

Global Secondary Index「検索インデックス」は、モバイルアプリケーションの「専用検索システム」のような役割を果たします。時間順、人気順、カテゴリ別、位置情報別など、様々な条件での高速検索を可能にします。これにより、ユーザーが求める情報を瞬時に表示でき、優れたユーザー体験を提供できます。

Lambda関数(データ処理)とモバイルアプリの関わり

Lambda関数「データ処理エンジン」は、モバイルアプリケーションの「データ加工工場」のような役割を果たします。位置情報の計算、近隣検索、データの集計・分析を自動で実行し、アプリに最適化された形でデータを提供します。これにより、複雑なデータ処理をサーバーレスで効率的に実現できます。

実際の手順

実際の手順では、たくさんの設定値を入力することになります。 本文中に設定値が指定されていない場合は、デフォルト値のまま作業を進めてください。

1. DynamoDBテーブルの設計と作成

モバイルアプリのチェックインデータを保存するDynamoDBテーブルを作成します。

Note

AWSでの役割

Amazon DynamoDBは、高パフォーマンスなNoSQLデータベースサービスです。

特徴:

  • ミリ秒レベルの高速レスポンス
  • 自動スケーリング機能
  • サーバーレスでの運用
  • Global Secondary Indexによる柔軟な検索

オンプレミスでの対応

オンプレミス環境では「MongoDB + Redis」が対応します。

  • MongoDBでのドキュメントストア
  • Redisでのキャッシュ層
  • 手動でのシャーディングとレプリケーション設定

1-1. チェックインテーブルの作成

  1. AWSマネジメントコンソールでDynamoDBサービスを開きます
  2. 「テーブルの作成」ボタンをクリックします
  3. テーブル名に「checkins-table」と入力します
  4. パーティションキーに「userId」(文字列)を設定します
  5. ソートキーに「timestamp」(文字列)を設定します
  6. テーブル設定で「設定をカスタマイズ」を選択します
  7. テーブルクラスを選択で「DynamoDB 標準」を選択します
  8. 読み取り/書き込みキャパシティで「オンデマンド」を選択します
  9. 「テーブルの作成」をクリックします
Tip

【解説】パーティションキーとソートキーの設計思想

DynamoDBのテーブル設計では、アクセスパターンを事前に定義することが重要です。パーティションキーはデータの分散方法を決定し、ソートキーは同一パーティション内でのデータの並び順を制御します。

チェックインテーブルでは:

  • パーティションキー(userId): ユーザーごとにデータを分散し、特定ユーザーのチェックインを高速に取得
  • ソートキー(timestamp): 同一ユーザーのチェックインを時系列でソートし、最新順や古い順での取得が可能

この設計により、「ユーザーAの最新10件のチェックイン」や「ユーザーBの2025年10月のチェックイン」といったクエリを効率的に実行できます。

NoSQLデータベースでは、リレーショナルデータベースのような正規化は行わず、アクセスパターンに最適化したデータ構造を設計することが成功の鍵となります。

2. Global Secondary Indexの作成

時系列での全チェックイン検索や位置情報ベースの検索のためのGSIを作成します。

2-1. タイムスタンプインデックスの作成

全ユーザーのチェックインを時系列で取得するためのGSIを作成します:

  1. 「checkins-table」テーブルを選択します
  2. 「インデックス」タブをクリックします
  3. 「インデックスを作成」ボタンをクリックします
  4. インデックス名に「timestamp-index」と入力します
  5. パーティションキーに「timestamp」(String)を設定します
  6. ソートキーは設定しません
  7. 「インデックスの作成」をクリックします
Tip

【解説】GSIの使用場面

このタイムスタンプインデックスにより、以下のクエリが可能になります:

  • 全ユーザーの最新チェックイン一覧(タイムライン機能)
  • 特定期間のチェックイン検索(例:今日のチェックイン)
  • チェックイン数の集計(例:時間帯別の利用状況)

プライマリキー(userId + timestamp)では特定ユーザーのデータしか取得できませんが、GSIを使用することで、全ユーザーのデータを横断的に検索できます。

3. Lambda関数でのDynamoDB操作実装

DynamoDBとの連携機能をLambda関数に実装します。

3-1. Lambda関数の権限設定

  1. Lambdaコンソールで「mobile-app-handler」関数を開きます
  2. 「設定」タブの「アクセス権限」を選択します
  3. 実行ロール名をクリックしてIAMコンソールを開きます
  4. 許可ポリシーの「許可を追加」から「ポリシーをアタッチ」をクリックします
  5. 「AmazonDynamoDBFullAccess」を検索して選択します
  6. 「許可を追加」をクリックします
Tip

【解説】最小権限の原則

本番環境では、AmazonDynamoDBFullAccessではなく、必要な権限のみを付与するカスタムポリシーを作成することを推奨します:

{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": [ "dynamodb:PutItem", "dynamodb:GetItem", "dynamodb:Query", "dynamodb:Scan" ], "Resource": "arn:aws:dynamodb:us-east-1:*:table/checkins-table*" }] }

3-2. Lambda関数のコード更新

  1. Lambda関数のコードエディタで以下のPythonコードに更新します:
import json import logging import boto3 import uuid from datetime import datetime from decimal import Decimal import math # ログ設定 logger = logging.getLogger() logger.setLevel(logging.INFO) # DynamoDBクライアント dynamodb = boto3.resource('dynamodb') checkins_table = dynamodb.Table('checkins-table') def lambda_handler(event, context): """ チェックイン機能のAPIリクエストを処理する関数 """ try: # リクエスト情報をログに記録 logger.info(f"受信したイベント: {json.dumps(event)}") # 認証情報を取得 request_context = event.get('requestContext', {}) authorizer = request_context.get('authorizer', {}) claims = authorizer.get('claims', {}) user_id = claims.get('sub', 'unknown') email = claims.get('email', 'unknown') # HTTPメソッドとパスを取得 http_method = event.get('httpMethod', 'GET') path = event.get('path', '/') # リクエストボディを取得 body = {} if event.get('body'): body = json.loads(event.get('body')) # パスに応じた処理の分岐 if path == '/health' and http_method == 'GET': # ヘルスチェック用のレスポンス return { 'statusCode': 200, 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', }, 'body': json.dumps({ 'message': '認証されたユーザーからのリクエストを受信しました', 'user_info': { 'user_id': user_id, 'email': email }, 'request_info': { 'method': http_method, 'path': path }, 'timestamp': context.aws_request_id }, ensure_ascii=False) } elif path == '/checkin' and http_method == 'POST': return create_checkin(user_id, body) elif path == '/checkins' and http_method == 'GET': return get_checkins(user_id, event.get('queryStringParameters', {})) elif path == '/checkins/nearby' and http_method == 'GET': return get_nearby_checkins(user_id, event.get('queryStringParameters', {})) else: return create_error_response(404, 'エンドポイントが見つかりません') except Exception as e: logger.error(f"エラーが発生しました: {str(e)}") return create_error_response(500, 'サーバー内部エラーが発生しました', str(e)) def create_checkin(user_id, body): """チェックイン作成""" try: checkin_id = str(uuid.uuid4()) timestamp = body.get('timestamp', datetime.now().isoformat()) latitude = body.get('latitude', 0) longitude = body.get('longitude', 0) checkin_data = { 'userId': user_id, 'timestamp': timestamp, 'checkInId': checkin_id, 'latitude': Decimal(str(latitude)), 'longitude': Decimal(str(longitude)), 'memo': body.get('memo', ''), 'createdAt': datetime.now().isoformat() } checkins_table.put_item(Item=checkin_data) logger.info(f"チェックイン作成成功: userId={user_id}, checkInId={checkin_id}") return { 'statusCode': 201, 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', }, 'body': json.dumps({ 'message': 'チェックインを作成しました', 'checkInId': checkin_id, 'userId': user_id, 'latitude': float(latitude), 'longitude': float(longitude), 'timestamp': timestamp }, ensure_ascii=False) } except Exception as e: logger.error(f"チェックイン作成エラー: {str(e)}") return create_error_response(500, 'チェックインの作成に失敗しました', str(e)) def get_checkins(user_id, query_params): """チェックイン履歴取得""" try: limit = int(query_params.get('limit', 50)) response = checkins_table.query( KeyConditionExpression='userId = :userId', ExpressionAttributeValues={':userId': user_id}, ScanIndexForward=False, # 新しい順 Limit=limit ) items = response.get('Items', []) logger.info(f"チェックイン取得成功: userId={user_id}, count={len(items)}") return { 'statusCode': 200, 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', }, 'body': json.dumps({ 'items': convert_decimal_to_float(items), 'count': len(items) }, ensure_ascii=False) } except Exception as e: logger.error(f"チェックイン取得エラー: {str(e)}") return create_error_response(500, 'チェックインの取得に失敗しました', str(e)) def get_nearby_checkins(user_id, query_params): """近隣チェックイン検索""" try: center_lat = float(query_params.get('latitude', 0)) center_lon = float(query_params.get('longitude', 0)) radius_km = float(query_params.get('radius', 5)) # 全チェックインをスキャン(小規模データ向け) response = checkins_table.scan() all_checkins = response.get('Items', []) # 距離計算して範囲内のチェックインをフィルタ nearby_checkins = [] for checkin in all_checkins: lat = float(checkin.get('latitude', 0)) lon = float(checkin.get('longitude', 0)) distance = calculate_distance(center_lat, center_lon, lat, lon) if distance <= radius_km: checkin_dict = convert_decimal_to_float(checkin) checkin_dict['distance'] = round(distance, 2) nearby_checkins.append(checkin_dict) # 距離順にソート nearby_checkins.sort(key=lambda x: x['distance']) logger.info(f"近隣チェックイン検索成功: count={len(nearby_checkins)}") return { 'statusCode': 200, 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', }, 'body': json.dumps({ 'items': nearby_checkins, 'count': len(nearby_checkins), 'center': {'latitude': center_lat, 'longitude': center_lon}, 'radiusKm': radius_km }, ensure_ascii=False) } except Exception as e: logger.error(f"近隣検索エラー: {str(e)}") return create_error_response(500, '近隣チェックインの検索に失敗しました', str(e)) def calculate_distance(lat1, lon1, lat2, lon2): """2点間の距離を計算(Haversine公式)""" # 地球の半径(km) R = 6371.0 # ラジアンに変換 lat1_rad = math.radians(lat1) lon1_rad = math.radians(lon1) lat2_rad = math.radians(lat2) lon2_rad = math.radians(lon2) # 差分 dlat = lat2_rad - lat1_rad dlon = lon2_rad - lon1_rad # Haversine公式 a = math.sin(dlat / 2)**2 + math.cos(lat1_rad) * math.cos(lat2_rad) * math.sin(dlon / 2)**2 c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) distance = R * c return distance def create_success_response(data): """成功レスポンスの生成""" return { 'statusCode': 200, 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token' }, 'body': json.dumps(data, ensure_ascii=False) } def create_error_response(status_code, message, detail=None): """エラーレスポンスの生成""" error_data = {'error': message} if detail: error_data['detail'] = detail return { 'statusCode': status_code, 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }, 'body': json.dumps(error_data, ensure_ascii=False) } def convert_decimal_to_float(obj): """DynamoDBのDecimal型をfloatに変換""" if isinstance(obj, dict): return {k: convert_decimal_to_float(v) for k, v in obj.items()} elif isinstance(obj, list): return [convert_decimal_to_float(v) for v in obj] elif isinstance(obj, Decimal): return float(obj) else: return obj
  1. 「Deploy」ボタンをクリックしてコードをデプロイします

4. API Gatewayでのチェックインエンドポイント追加

チェックイン機能用のAPIエンドポイントを追加します。

① チェックイン作成エンドポイント(/checkin)の作成

  1. API Gatewayコンソールで「mobile-app-api」を開きます
  2. ルートリソース(/)を選択します
  3. リソースから「リソースを作成」を選択します
  4. リソース名に「checkin」と入力します(単数形)
  5. 「リソースを作成」をクリックします
  6. 「/checkin」リソースを選択し、メソッドの「メソッドを作成」をクリックします
  7. メソッドタイプのプルダウンで「POST」を選択します
  8. 統合タイプで「Lambda関数」を選択します
  9. 「Lambdaプロキシ統合」のトグルスイッチをONにします
  10. Lambda関数で「mobile-app-handler」を選択します
  11. レスポンス転送モードで「バッファード」を選択します
  12. 「メソッドを作成」をクリックします
  13. 「リソース」メニューで「/checkin」のPOSTメソッドを選択します
  14. 「メソッドリクエスト」タブの「編集」をクリックします
  15. 認可で「cognito-authorizer」を選択します
  16. 「保存」をクリックします

② チェックイン取得エンドポイント(/checkins)の作成

  1. ルートリソース(/)を選択します
  2. リソースから「リソースを作成」を選択します
  3. リソース名に「checkins」と入力します(複数形)
  4. 「リソースの作成」をクリックします
  5. 「/checkins」リソースを選択し、メソッドの「メソッドを作成」をクリックします
  6. メソッドタイプのプルダウンで「GET」を選択します
  7. 統合タイプで「Lambda関数」を選択します
  8. 「Lambdaプロキシ統合」のトグルスイッチをONにします
  9. レスポンス転送モードで「バッファード」を選択します
  10. Lambda関数で「mobile-app-handler」を選択します
  11. 「メソッドを作成」をクリックします
  12. 「リソース」メニューで「/checkins」のGETメソッドを選択します
  13. 「メソッドリクエスト」タブの「編集」をクリックします
  14. 認可で「cognito-authorizer」を選択します
  15. 「保存」をクリックします

③ 近隣検索エンドポイント(/checkins/nearby)の作成

  1. 「/checkins」リソースを選択した状態で、リソースから「リソースを作成」を選択します
  2. リソース名に「nearby」と入力します
  3. 「リソースの作成」をクリックします
  4. 「/nearby」リソースを選択し、メソッドの「メソッドを作成」をクリックします
  5. メソッドタイプのプルダウンで「GET」を選択します
  6. 統合タイプで「Lambda関数」を選択します
  7. 「Lambdaプロキシ統合」のトグルスイッチをONにします
  8. レスポンス転送モードで「バッファード」を選択します
  9. Lambda関数で「mobile-app-handler」を選択します
  10. 「メソッドを作成」をクリックします
  11. 「リソース」メニューで「/nearby」のGETメソッドを選択します
  12. 「メソッドリクエスト」タブの「編集」をクリックします
  13. 認可で「cognito-authorizer」を選択します
  14. 「保存」をクリックします

④ APIのデプロイ

  1. API Gatewayのコンソールで「APIのデプロイ」をクリックし、prodステージにデプロイします
Tip

【解説】REST APIのリソース設計

APIエンドポイントの設計では、RESTful原則に従って命名します:

  • POST /checkin: 新しいチェックインを作成(単数形、リソースの作成)
  • GET /checkins: チェックイン一覧を取得(複数形、コレクションの取得)
  • GET /checkins/nearby: 近隣のチェックインを検索(サブリソース)

この設計により、APIの意図が明確になり、開発者が直感的に理解できるようになります。

5. DynamoDB動作確認

構築したDynamoDBテーブルとAPIが正しく動作することを確認します。

5-1. チェックインデータの作成テスト

認証トークンを使用して、新しいチェックインを作成します:

# 環境変数の設定 export API_URL="https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/prod" export ID_TOKEN=$(aws cognito-idp initiate-auth \ --client-id ${CLIENT_ID} \ --auth-flow USER_PASSWORD_AUTH \ --auth-parameters USERNAME=testuser@example.com,PASSWORD=Test1234! \ --region us-east-1 \ --output json | jq -r '.AuthenticationResult.IdToken') # チェックインの作成 curl -X POST "${API_URL}/checkin" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer ${ID_TOKEN}" \ -d '{ "latitude": 35.6812, "longitude": 139.7671, "memo": "東京駅でチェックイン", "timestamp": "2025-10-31T10:30:00Z" }'

期待されるレスポンス:

{ "message": "チェックインを作成しました", "checkInId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "userId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "latitude": 35.6812, "longitude": 139.7671, "timestamp": "2025-10-31T10:30:00Z" }

ステータスコード:201 Created

5-2. DynamoDBでのデータ確認

AWSコンソールで実際にデータが保存されたことを確認します:

  1. AWSマネジメントコンソールでDynamoDBサービスを開きます
  2. 左メニューから「テーブル」を選択します
  3. 「checkins-table」をクリックします
  4. 「項目を探索」ボタンをクリックします
  5. 作成したチェックインデータが表示されることを確認します

確認すべき項目:

  • checkInId: UUID形式の一意のID
  • userId: Cognitoから取得したユーザーID(sub)
  • latitudelongitude: 位置情報
  • timestamp: チェックイン日時
  • memo: メモ(オプション)
  • createdAt: データ作成日時(自動生成)
Tip

【解説】DynamoDBのデータ型

DynamoDBでは以下のデータ型が使用されます:

  • S (String): 文字列データ
  • N (Number): 数値データ
  • M (Map): ネストしたオブジェクト
  • L (List): 配列データ

緯度・経度は数値型(N)として保存され、高精度な計算が可能です。

5-3. チェックイン履歴の取得テスト

自分のチェックイン履歴を取得します:

# チェックイン履歴の取得 curl -X GET "${API_URL}/checkins?limit=10" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer ${ID_TOKEN}"

期待されるレスポンス:

{ "items": [ { "checkInId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "userId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "latitude": 35.6812, "longitude": 139.7671, "memo": "東京駅でチェックイン", "timestamp": "2025-10-31T10:30:00Z", "createdAt": "2025-10-31T10:30:00Z" } ], "count": 1 }

5-4. 複数のチェックインデータを作成

位置情報検索のテストのため、複数の場所でチェックインを作成します:

# 渋谷でチェックイン curl -X POST "${API_URL}/checkin" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer ${ID_TOKEN}" \ -d '{ "latitude": 35.6595, "longitude": 139.7004, "memo": "渋谷スクランブル交差点", "timestamp": "2025-10-31T11:00:00Z" }' # 新宿でチェックイン curl -X POST "${API_URL}/checkin" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer ${ID_TOKEN}" \ -d '{ "latitude": 35.6896, "longitude": 139.7006, "memo": "新宿駅", "timestamp": "2025-10-31T11:30:00Z" }' # 大阪(梅田)でチェックイン curl -X POST "${API_URL}/checkin" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer ${ID_TOKEN}" \ -d '{ "latitude": 34.7024, "longitude": 135.4959, "memo": "大阪駅", "timestamp": "2025-10-31T12:00:00Z" }'

5-5. 近隣チェックインの検索テスト

特定の位置から近い範囲のチェックインを検索します:

# 東京駅周辺(半径5km)のチェックインを検索 curl -X GET "${API_URL}/checkins/nearby?latitude=35.6812&longitude=139.7671&radius=5" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer ${ID_TOKEN}"

期待されるレスポンス:

{ "items": [ { "checkInId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "latitude": 35.6812, "longitude": 139.7671, "memo": "東京駅でチェックイン", "distance": 0.0 }, { "checkInId": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy", "latitude": 35.6896, "longitude": 139.7006, "memo": "新宿駅", "distance": 2.5 } ], "count": 2, "center": { "latitude": 35.6812, "longitude": 139.7671 }, "radiusKm": 5 }
Tip

【解説】位置情報の距離計算

近隣検索では、2点間の距離を計算するために「ヒュベニの公式」や「Haversine公式」が使用されます。

  • 距離の単位: キロメートル(km)
  • 計算精度: 地球を球体として扱うため、実際の距離と若干の誤差があります
  • 検索範囲: 半径1km〜50km程度が実用的な範囲

大量のデータがある場合は、DynamoDB GSI(Global Secondary Index)を使用して効率的に検索できます。

5-6. DynamoDB GSIの動作確認

時系列でのチェックイン取得をテストします:

# 最新のチェックインから取得(timestampでソート) curl -X GET "${API_URL}/checkins?sortBy=timestamp&order=desc&limit=5" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer ${ID_TOKEN}"

期待されるレスポンス:

{ "items": [ { "checkInId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "timestamp": "2025-10-31T12:00:00Z", "memo": "大阪駅" }, { "checkInId": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy", "timestamp": "2025-10-31T11:30:00Z", "memo": "新宿駅" }, { "checkInId": "zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz", "timestamp": "2025-10-31T11:00:00Z", "memo": "渋谷スクランブル交差点" } ], "count": 3 }
Tip

【解説】GSI(Global Secondary Index)の活用

DynamoDBのGSIを使用すると、プライマリキー以外の属性でデータを効率的にクエリできます:

  • timestamp-index: タイムスタンプでソートされたチェックイン一覧
  • location-index: 位置情報に基づくジオクエリ(より高度な実装)

GSIは追加のストレージと読み取り/書き込みキャパシティを消費しますが、複雑なクエリパターンを高速に処理できます。

5-7. CloudWatch Logsでの実行ログ確認

Lambda関数の実行状況を確認します:

  1. CloudWatchコンソールを開きます
  2. 「ログ」→「ロググループ」を選択します
  3. /aws/lambda/mobile-app-handler(または対応するLambda関数名)を開きます
  4. 最新のログストリームを確認します

チェックイン作成時のログ例:

START RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx [INFO] チェックイン作成リクエスト: userId=xxxxxxxx, lat=35.6812, lon=139.7671 [INFO] DynamoDB PutItem 成功 END RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx REPORT RequestId: xxxxxxxx Duration: 120.00 ms Billed Duration: 121 ms Memory Size: 256 MB Max Memory Used: 75 MB

5-8. トラブルシューティング

エラー: “ValidationException: One or more parameter values were invalid”

原因:DynamoDBへのデータ挿入時に必須項目が不足している、またはデータ型が間違っている

対処法:

  1. Lambda関数のコードでリクエストボディのバリデーションを確認
  2. 必須項目(userId、latitude、longitude、timestamp)がすべて存在するか確認
  3. データ型が正しいか確認(latitude/longitudeは数値型)

エラー: “ResourceNotFoundException: Requested resource not found”

原因:指定したDynamoDBテーブルが存在しない

対処法:

  1. DynamoDBコンソールでテーブル名を確認
  2. Lambda関数の環境変数TABLE_NAMEが正しく設定されているか確認
  3. Lambda実行ロールにDynamoDBテーブルへのアクセス権限があるか確認

エラー: 近隣検索で結果が返らない

原因:距離計算のロジックエラー、またはデータが存在しない範囲で検索している

対処法:

  1. 検索範囲(radius)を大きくしてテスト(例:50km)
  2. CloudWatch Logsで距離計算のログを確認
  3. DynamoDBに十分なテストデータが存在するか確認

エラー: “AccessDeniedException: User is not authorized to perform: dynamodb:PutItem”

原因:Lambda実行ロールにDynamoDBへの書き込み権限がない

対処法:

  1. IAMコンソールでLambda実行ロールを確認
  2. 以下のポリシーをアタッチ:
    { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "dynamodb:PutItem", "dynamodb:GetItem", "dynamodb:Query", "dynamodb:Scan" ], "Resource": "arn:aws:dynamodb:us-east-1:ACCOUNT_ID:table/checkins-table" } ] }
Tip

【解説】DynamoDBのパフォーマンス最適化

大量のデータを扱う場合は、以下の点に注意してください:

  • パーティションキー設計: ユーザーIDをパーティションキーにすることで、ユーザーごとのデータが分散されます
  • ソートキー活用: タイムスタンプをソートキーにすることで、時系列での効率的なクエリが可能になります
  • BatchWriteItem: 複数のデータを一括で書き込む場合は、BatchWriteItemを使用して効率化できます
  • キャパシティモード: 開発中はオンデマンドモード、本番環境でトラフィックが予測可能な場合はプロビジョニングモードを検討してください

このステップで何をしたのか

このステップでは、モバイルアプリケーション向けの包括的なデータ管理システムを構築しました。具体的には、Amazon DynamoDBで3つのテーブル(ユーザー、投稿、位置情報)を作成し、NoSQLデータベース設計のベストプラクティスに従ってパーティションキーとソートキーを設定しました。また、Global Secondary Indexを活用した高速検索機能を実装し、Lambda関数でデータ操作のビジネスロジックを実装しました。

モバイルアプリでどのような影響があるのか

この構成により、モバイルアプリは大規模なユーザーデータと位置情報を効率的に管理できるようになります。ミリ秒レベルの高速レスポンスにより、リアルタイムな位置情報共有や近隣ユーザー検索が可能になります。NoSQL設計により、従来のリレーショナルデータベースでは困難だった柔軟なデータ構造と高いスケーラビリティを実現できます。これは、図書館の「デジタル検索システム」のように、膨大な情報から瞬時に必要なデータを取得できるシステムに相当します。

技術比較まとめ表

技術領域AWSオンプレミス
データベースAmazon DynamoDB
NoSQL、自動スケーリング、ミリ秒レスポンス
MongoDB + MySQL
手動スケーリング、インフラ管理
検索機能Global Secondary Index
自動インデックス管理、複数検索条件対応
カスタムインデックス
手動最適化、パフォーマンスチューニング
位置情報処理DynamoDB + Lambda
サーバーレス地理計算、自動スケーリング
PostGIS + アプリサーバー
専用サーバー、手動最適化

学習において重要な技術的違い

1. データベース設計思想

  • AWS:アクセスパターンベースのNoSQL設計
  • オンプレミス:正規化に基づくリレーショナル設計

2. スケーラビリティ

  • AWS:読み取り/書き込み容量の自動スケーリング
  • オンプレミス:シャーディングやレプリケーションの手動設定

3. 検索パフォーマンス

  • AWS:GSIによる複数検索条件の最適化
  • オンプレミス:インデックス設計とクエリ最適化の手動実装

4. 運用コスト

  • AWS:使用量に基づく従量課金
  • オンプレミス:固定インフラコストとDBA人件費

実践チェック:画面キャプチャで証明しよう

下記のチェック項目について、実際にAWSマネジメントコンソールで設定ができていることを確認し、各項目ごとに該当画面のスクリーンショットを撮影して提出してください。

  • DynamoDBテーブル「checkins-table」が作成されていること

  • 「checkins-table」のパーティションキーが「userId」(String)、ソートキーが「timestamp」(String)に設定されていること

  • 「timestamp-index」というGlobal Secondary Indexが作成されていること

  • Lambda関数がDynamoDBアクセス権限を持っていること

  • API Gatewayで「checkin」(単数形)と「checkins」(複数形)リソースが作成されていること

  • 「checkin」リソースにPOSTメソッド、「checkins」リソースにGETメソッドが作成されていること

  • 「checkins/nearby」リソースにGETメソッドが作成されていること

提出方法: 各項目ごとにスクリーンショットを撮影し、まとめて提出してください。 ファイル名やコメントで「どの項目か」が分かるようにしてください。

構成図による理解度チェック

ステップ2の構成図に、このステップで作成したデータ層を追記して、現在のインフラ構成を完成させましょう。

なぜ構成図を更新するのか?

データベース層の追加により、システムアーキテクチャが三層構造(プレゼンテーション層、ビジネスロジック層、データ層)として完成します。NoSQLデータベースの特徴的な設計パターンや、GSIを活用した検索機能の仕組みを視覚的に理解することが重要です。また、位置情報データの流れと処理プロセスを明確にできます。

  • データフロー: モバイルアプリ → API → Lambda → DynamoDB の完全なデータ処理フロー
  • NoSQL設計: パーティションキー、ソートキー、GSIの役割と関係性
  • 位置情報処理: 地理的データの保存・検索・計算プロセス

構成図の書き方

ステップ2で作成した構成図をベースに、以下のリソースを追記してみましょう。

  1. DynamoDBテーブル群: Lambda関数の右側に3つのテーブルを配置
  2. Global Secondary Index: 各テーブル内に検索機能として表現
  3. データフロー: Lambda関数からDynamoDBへのデータ操作の流れを矢印で表現
  4. 位置情報処理: 地理的データの特殊な処理フローを別途図示

💡 ヒント: DynamoDBのパーティションキーとソートキーの概念を図に含めると、NoSQL設計の理解が深まります

理解度チェック:なぜ?を考えてみよう

AWSの各リソースや設計には、必ず”理由”や”目的”があります。 下記の「なぜ?」という問いに自分なりの言葉で答えてみましょう。 仕組みや設計意図を自分で説明できることが、真の理解につながります。 ぜひ、単なる暗記ではなく「なぜそうなっているのか?」を意識して考えてみてください。

Q. なぜモバイルアプリでNoSQLデータベース(DynamoDB)を選択するのか?リレーショナルデータベース(RDS)と比較してどのようなメリットがあるか?

Q. なぜDynamoDBでGlobal Secondary Index(GSI)を作成するのか?プライマリキーのみでのクエリと比較してどのような違いがあるか?

Q. なぜ位置情報データでlocation_hashをパーティションキーとして使用するのか?user_idをパーティションキーにした場合と比較してどのような問題が発生するか?

今回のステップで利用したAWSサービス名一覧

  • Amazon DynamoDB:高パフォーマンスなNoSQLデータベースサービス
  • AWS Lambda:DynamoDBとの連携処理を行うサーバーレスコンピューティング
  • Amazon API Gateway:新しいデータ操作エンドポイントを提供するAPIサービス
  • AWS IAM:LambdaからDynamoDBへのアクセス権限を管理するサービス
  • Amazon CloudWatch:データベース操作のログ監視・パフォーマンス分析を行うサービス
Last updated on