初級読了 3 分

DynamoDBテーブルをCSVにエクスポートする方法(4つの方法)

DynamoDBにネイティブの「CSVにエクスポート」ボタンはありません。すべての値はDynamoDBのマーシャリングされたJSON{"S": "..."}{"N": "123"}{"M": {...}} — に包まれて返ってきますし、テーブルにはネストされたマップ、リスト、セットが入っていて、自明なフラットカラム表現がありません。つまり「DynamoDBをCSVにエクスポートする」は実際には2つの問題です:アイテムを取り出すこと、そして型付きJSONを行にフラット化することです。コンソールもマネージドエクスポートも、2つ目のステップは代行してくれません。

このガイドはまず両方のステップを代行してくれる方法から始め、その後3つのAWSツーリングのルートと、それぞれが正しい選択になる場面を扱います。

DynamoDBテーブルをCSVにエクスポートするには?

最速の方法:DynoTableでテーブルを開き、欲しい行にフィルタして、結果をワンクリックでCSVにエクスポートします — 型記述子は展開され、ネストされた値はフラット化されます (方法1)。代わりにAWSツーリングで組み立てるなら:小さなテーブルはCLIでスキャンしてjqで整形し (方法2)、大きなテーブルはマネージドのS3エクスポートを使い(方法3)、カスタムな整形が必要なら短いスクリプトを書きます (方法4)。

  • フィルタ済み / 整形済みのCSV(一部のカラム、一部のアイテムだけ): GUIのエクスポート(方法1)かスクリプト。マネージドのS3エクスポートは、フィルタなしの_テーブル全体_を出力します。
  • 小さなテーブル、アドホック、ターミナルだけ: AWS CLIのscan + jq (方法2)。ネストされた属性が現れるまでは十分です。
  • 大きなテーブル(GB以上): DynamoDBのS3へのエクスポート (方法3)を使い、その後ダンプを変換します。非同期で実行され、読み取りキャパシティを消費しません — ただし出力はCSVではなくDynamoDB JSONです。

方法1:DynoTableでのワンクリックエクスポート

DynoTableはエクスポートを閲覧の一部として扱います:クエリを実行またはフィルタし、⌘⇧E(またはタブのツールバーのExportボタン)を押して、何を出力するかを選びます:

  • フォーマット: CSV(1アイテムにつき1行、ヘッダー付き — セットはセル内のJSON配列としてシリアライズされます)、JSONNDJSON。JSON/NDJSONはアンマーシャリング済み(プレーンな"count": 3)か、大きな数値を保持するロスレスなラウンドトリップが必要なときはマーシャリングされたDynamoDB-JSONで出力できます。
  • スコープ: 現在ロード済みの行、選択範囲だけ、またはフィルタの全一致 — 画面に表示されているものだけでなく、クエリに一致するすべてのアイテムをDynamoDBから直接ストリームします。この最後のスコープが、マネージドのS3スナップショットにはできないフィルタ付きエクスポートです。
  • 出力先: クリップボードかファイル。単発の取り出しなら、行を右クリックしてCopy as…を使えば、ダイアログなしでCSV、JSON、NDJSON、DynamoDB-JSONをそのままクリップボードに置けます。
DynoTableのエクスポートダイアログ:フォーマット(CSV / JSON / NDJSON)、選択範囲からフィルタの全一致までのスコープ、クリップボードまたはファイルの出力先。
DynoTableのエクスポートダイアログ:フォーマット(CSV / JSON / NDJSON)、選択範囲からフィルタの全一致までのスコープ、クリップボードまたはファイルの出力先。

後述のDIYルートを壊すフラット化の問題は処理済みです:型記述子は展開され、ネストされたマップとリストはフラット化され、リネームしたカラムはCSVのヘッダーに反映されます。大きなエクスポートはデタッチしてバックグラウンドで実行されます — 1行ずつディスクにストリームされ、タブの切り替えやアプリのリロードさえも生き延びます — そのため数ギガバイトのエクスポートでもメモリに収まる必要はありません。

これはデスクトップのDynamoDBクライアントで、テーブルの閲覧にすでに使っているのと同じツールです。他のDynamoDB GUIとの比較も参照してください。では、いつ使わ_ない_のか?エクスポートをパイプラインの中で無人実行しなければならないときです — それがスクリプトのルート(方法4)の役目です。

方法2:AWS CLIのscan + jq

小さなテーブルなら、スキャンしてjqで出力を整形できます。Scanはテーブル内のすべてのアイテムを読み取り、最大1 MBのページで返します。CLIはページネーションを自動で辿ってくれます (AWSドキュメント: テーブルのスキャン)。

aws dynamodb scan --table-name MyTable --output json \
  | jq -r '.Items[] | [.id.S, .name.S, .price.N] | @csv' \
  > out.csv

罠はそのjqの行にあります:.id.S.name.S.price.Nを手で書かなければなりません — 各属性の型記述子(SNBBOOLMLSSNSBS)を越えて生の値に手を伸ばすのです。文字列カラムが3つのフラットなテーブルなら管理できます。しかし次のものが現れた瞬間に崩壊します:

  • ネストされたマップ/リスト{"M": {...}}{"L": [...]}にはフラット化する先の単一カラムがありません。@csvが詰まるか、セルを手でJSONエンコードすることになります。
  • セット{"SS": ["a","b"]}はスカラーではなく配列です。
  • スパース属性 — DynamoDBはスキーマレスなので、アイテムAにはpriceがあってアイテムBにはないかもしれません。固定のカラムリストは、黙ってカラムを落とすかずらします。

そして--output csvはそもそも存在しません — CLIの出力フォーマットはjsonyamltexttableoffで、どれもDynamoDBの型を理解しません。つまり型タグを剥がすには依然としてjq(かスクリプト)が必要です。これこそが「AWS CLIでDynamoDBテーブルをCSVにエクスポート」が、自明なケースを越えると決してワンライナーにならない核心的な理由です。

この方法でより大きなテーブル全体を一日がかりにせずエクスポートするには、--segment / --total-segmentsでスキャンを並列化し (AWSドキュメント: 並列スキャン — DynamoDBは「各アイテムのパーティションキーにハッシュ関数を適用してアイテムを_セグメント_に割り当てる」ため、セグメントは不均等になりえます)、最初の1 MBページで止まらないようにページネーションを読んでください。

方法3:DynamoDBのS3へのエクスポート(大きなテーブル)

ある程度の規模のテーブルには、マネージドのAmazon S3へのエクスポートが正しいツールです。ポイントインタイムリカバリ(PITR)のウィンドウ内の任意の時点のスナップショットをエクスポートするので、まずテーブルでPITRを有効化しておく必要があり(そうでなければエクスポートはPointInTimeRecoveryUnavailableExceptionで失敗します)、非同期で実行され、読み取りキャパシティユニットを消費しないため、テーブルのスループットや可用性にはゼロ影響です(AWSドキュメント: 「エクスポートは非同期であり、読み取りキャパシティユニット(RCU)を消費せず、テーブルのパフォーマンスと可用性に影響しません」、「エクスポート機能を使用するには、テーブルでPITRを有効にする必要があります」)。これはコンソールのExports to S3アクションが内部で起動するものでもあります:コンソールは同じAPIのフロントエンドにすぎないので、同じPITR要件と同じJSON出力を伴います。

aws dynamodb export-table-to-point-in-time \
  --table-arn arn:aws:dynamodb:us-east-1:123456789012:table/MyTable \
  --s3-bucket my-export-bucket \
  --export-format DYNAMODB_JSON

唯一の落とし穴:S3エクスポートはCSVを出力しません。 書き出すのはDynamoDB JSONAmazon Ionだけで、JSON-lines形式(1行1アイテム)のgzip圧縮ファイルとマニフェストファイルです(AWSドキュメント: エクスポート出力形式 — データファイルは.json.gzとして書き出され、「形式はJSON lines」で、manifest-summary.json / manifest-files.jsonが付随します)。そのため後段の変換ステップが依然として必要です:

  • Athena / GlueはエクスポートされたDynamoDB JSONを直接読めます — S3プレフィックスにテーブルを向け、SELECTからCSVを書き出します(これが定番の「DynamoDBをS3にエクスポートしてからCSVへ」のパイプラインです)。AWSは「AthenaやAWS Glueなど、多くのAWSサービスがこの形式を自動的に解析します」と述べています (エクスポート出力形式)。
  • 自前でやる.gzファイルを解凍し、各JSON行をパースし、フラット化します(他のすべての方法と同じフラット化の問題です)。

また、これはテーブル全体のスナップショットです:一部のアイテムだけをエクスポートするサーバー側のフィルタはありません。サブセットが必要なら、後からAthenaでフィルタするか、代わりにGUI(方法1)かスクリプトを使います。

方法4:短いスクリプト(boto3 / Node)

エクスポートを無人で実行しなければならないとき — 夜間ジョブ、CIステップ — は、小さなスクリプトが上のすべてに勝ります。利点は、AWS SDKが型付きJSONをアンマーシャリングしてくれることです:boto3のリソースインターフェースとJS SDKのDynamoDBDocumentClientは、{"price": {"N": "2000"}}の代わりにプレーンな{"price": 2000}を返します(boto3のリソースインターフェースは「データの型付けを暗黙的」にします — AWS Pythonガイドより。JSのDocumentClientは「注釈付きのレスポンスデータをネイティブのJavaScript型に変換します」— @aws-sdk/lib-dynamodbより)。

import boto3, csv

table = boto3.resource("dynamodb").Table("MyTable")
rows, resp = [], table.scan()
rows += resp["Items"]
while "LastEvaluatedKey" in resp:                  # paginate to the end
    resp = table.scan(ExclusiveStartKey=resp["LastEvaluatedKey"])
    rows += resp["Items"]

with open("out.csv", "w", newline="") as f:
    w = csv.DictWriter(f, fieldnames=["id", "name", "price"])
    w.writeheader()
    for r in rows:
        w.writerow({k: r.get(k) for k in w.fieldnames})

SDKが代わりに決められない2つの判断は依然としてあなたのものです:ネストされたマップ/リストをどうカラムにフラット化するか(セルをJSONエンコードする?キーをドットパスにする?)、そしてスパース属性をどうするか(ここでは欠けたキーはr.get(k)経由で空のセルになります)。そしてLastEvaluatedKeyのループを落とさないでください — 単一のscan()呼び出しは最初の1 MBページしか返さないので、ループなしではテーブルの一部だけを黙ってエクスポートすることになります。

方法2と同じ注意点です:ここでのテーブル全体のscanもやはり読み取りキャパシティを消費し、ライブトラフィックと競合します。大きなテーブルには方法3を使ってダンプを整形するほうを選んでください。

落とし穴:DynamoDB JSONとフラットなCSV

どの方法を選んでも、DynamoDBのデータモデルとフラットなCSVの間の同じ一握りの不一致に噛みつかれます:

  • 型記述子。 生のAPI / CLI / S3エクスポートの出力は、すべての値を包みます({"S": "..."}{"N": "123"})。SDKで展開するか、自分で記述子を剥がします。全セットはSNBBOOLNULLMLSSNSBSです — DynamoDBのデータ型を参照してください。
  • ネストされたマップとリスト(ML)は最大32レベルの深さまでネストでき (AWSドキュメント: データ型 — リストとマップは「互いにネストして、最大32レベルの深さの複雑なデータ構造を表現できます」)、自然な単一カラムの形がありません。前もって決めてください:セルをJSONエンコードするか、ネストされたキーをドットパスのカラム(address.city)に展開するか。
  • セット(SS/NS/BS)はスカラーではなく順序のないコレクションです — AWSは「セット内の値の順序は保持されません」と警告しています (データ型) — なので区切り文字付きの文字列にフラット化し、要素の順序に依存しないでください。
  • スパース属性。 DynamoDBはスキーマレスなので、2つのアイテムが異なる属性を持てます。固定のカラムセットは存在しません。すべてのアイテムにわたってキーの和集合を取らないと、カラムがずれます。これは1つのテーブルが複数のエンティティの形を保持するシングルテーブル設計の直接の帰結です。
  • ページネーション。 Scan(とQuery)は1回の呼び出しで最大1 MBしか返しません。LastEvaluatedKeyでループしなければ、最初のページだけを黙ってエクスポートすることになります。ページネーションを参照してください。
  • 数値の精度。 DynamoDBの数値は最大38桁の精度を持ち、文字列として転送されます(AWSドキュメント: データ型: 「数値は最大38桁の精度を持つことができます」、「すべての数値は文字列としてネットワーク経由でDynamoDBに送信されます」)。表計算ソフトは長い数値やIDをfloatに強制変換して桁を失うことがあります。テキストのまま保持してください。

よくある質問

DynamoDBテーブルをCSVにエクスポートする最速の方法は? フラット化を代行してくれるGUIです:DynoTableでテーブルをフィルタし、⌘⇧Eを押してCSVを選び、スコープを選択します — 選択した行から、フィルタに一致するすべてのアイテム(DynamoDBからストリーム)まで。型記述子とネストされた値は自動的に処理されます。

AWS CLIでDynamoDBテーブルをCSVにエクスポートするには? テーブルをスキャンしてjqで出力を整形します(方法2):aws dynamodb scanjqで各値の型記述子を剥がす → @csv。DynamoDBを理解する--output csvは存在しないため、型の除去は常に自分で行うことになり、ネストされたマップ、リスト、セットで壊れます。

AWSから直接DynamoDBテーブルをCSVにエクスポートできますか? 一段階では無理です。コンソールもマネージドのS3エクスポートも、DynamoDB JSONかAmazon Ionを出力し、CSVは決して出力しません。変換ステップが常に必要です — CLI + jq、スクリプト、S3ダンプに対するAthena/Glue、またはフラット化を代行するGUIです。

本番に影響を与えずにDynamoDBテーブル全体をエクスポートするには? S3へのエクスポート機能(方法3)を使います。非同期で実行され、読み取りキャパシティユニットを消費しないので、ライブトラフィックと競合しません — テーブルのスループットに対して計測されるScanとは異なります (AWSドキュメント)。PITRの有効化が必要で、フィルタしたサブセットではなくテーブル全体をエクスポートします。

DynamoDBをS3にCSVとしてエクスポートするには? マネージドエクスポートはS3にDynamoDB JSON / Ionしか書き出さないので、「CSVへ」は2ホップ目です:エクスポートのプレフィックスをAthena(またはGlue)のテーブルとして登録し、SELECTからCSVを書き出します。--export-format CSVは存在しません。

DynamoDBをExcelにエクスポートするには? まず(上のいずれかの方法で)CSVにエクスポートし、そのCSVをExcelで開きます — 長い数値のIDがfloatに強制変換されないよう、テキストとして保持してください。DynamoDBからの直接の.xlsxエクスポートはありません。DynoTableは現在のビューをスプレッドシートでそのまま使えるCSVに保存します。

エクスポートしたJSONのあちこちに{"S": ...}{"N": ...}があるのはなぜですか? それはDynamoDBのワイヤフォーマットです — 各値に型記述子がタグ付けされています。CSVを書く前に、SDK、DynamoDB JSONコンバータ、またはGUIでアンマーシャリングしてください。ワイヤフォーマットは、データがAPI、CLI、S3エクスポートのどこから来たかにかかわらず同じです。

DynoTableで自分のテーブルを閲覧、フィルタ、CSVエクスポートするか、まずJSONコンバータでDynamoDB JSONのサンプルを展開してみてください。

更新日