Amazon Web Services ブログ
時系列データの価値を引き出す Amazon Timestream データモデリングのベストプラクティス
Amazon Timestream は高速でスケーラブルなサーバレスの時系列データベースサービスで、1 日に数兆件のイベントの保存や分析が簡単に実現出来ます。Timestream は自動的にスケールアップ、スケールダウンを行い容量とパフォーマンスを調整する為、基盤となるインフラストラクチャを管理する必要がありません。時系列データの管理に関しては、従来のリレーショナルデータベースでは、大量のタイムスタンプ付きのデータという固有の要件を満たす事が出来ませんでしたが、Timestream は時系列データ専用に設計されたアーキテクチャと、高度な組み込み時系列分析機能を備えており、時系列データの真の可能性を引き出し、意味のある洞察を得る事が出来る理想的なソリューションとなります。
本投稿では Timestream の重要な概念を説明し、それらを使用して重要なデータモデリングの意思決定を行う方法を示します。まず、データモデリングがクエリのパフォーマンスやコスト効率にどう影響するかを説明します。次に、ビデオストリーミングに関するデータをモデリングする実践例を検討し、これらの概念がどう適用されるか、及び、その結果得られる利点を示します。最後にデータモデリングに直接的または間接的に関連するその他のベストプラクティスを説明します。
Timestream の重要な概念
以下に示した Timestream の重要な概念を理解する事は、最適なデータモデリングと効果的な取り込み、クエリ、分析の為に不可欠です。
- ディメンジョン – 時系列のメタデータを記述する属性です。例えば、証券取引所をディメンジョンとする場合、ディメンジョン名は証券取引所となり、対応するディメンジョン値は NYSE (ニューヨーク証券取引所) となります。
- メジャー – 実際に記録される値を示します。メジャーの一般的な例としては、温度測定値、株価、クリックまたはビデオストリーミングのメトリクス、そして製造装置や、IoT デバイス、自動車に関連したその他のメトリクス等が挙げられます。
- メジャー名 (デフォルトのパーティションキー) –
measure_name
は時系列データポイントに関連付けられた特定の測定値、又は、メトリクスの識別子を示しており、Timestream のテーブル内で様々なタイプのメジャー値を分類、及び区分する方法を提供します。この属性は顧客定義のパーティションキーが使用されない場合、デフォルトのパーティションキーとして動作します。 - 顧客定義のパーティションキー – Timestream はこの項目を元に各データをパーティションを区切って分割して配置し、ストレージとクエリパフォーマンスを改善します。カーディナリティが高く、クエリで頻繁に利用される属性を選択する事で、パフォーマンスの最適化を実現できます。多くの場合、ホスト ID、デバイス ID、顧客 ID 等のディメンジョンがパーティションキーとして適切な選択肢となります。
- タイムスタンプ – 特定のレコードのメジャーがいつ収集されたのかを示しています。また、Timestream はナノ秒単位のタイムスタンプをサポートしています。例えば、患者のバイタルサインを追跡する為のセンサーデータを収集する場合、UNIX 時間のフォーマットを利用して、このフィールドにデータ収集のタイムスタンプを格納します。
- テーブル – 関連する一連の時系列レコードのコンテナです
- データベース – テーブルの最上位のコンテナです
次の図は、2 つのディメンジョン (device_id
と location
) と、measure_name
、time
、そして 2 つのメジャー (quality
と value
) を含む Timestream のテーブルを示しています。device_id
のカーディナリティが高く、クエリのフィルタリングによく利用されると仮定した場合、それを顧客定義のパーティションキーとして選択すると効果的です。
Timestream は柔軟なスキーマレスの構造であり、厳格なスキーマの制約を受ける事は無く、テーブル作成時に列を定義する必要はありません。また、Timestream は時系列データ専用の NoSQL であり、情報をリレーショナルテーブルには保存しませんが、SQL をサポートしています。SQL に精通したユーザは、高度な時系列関数を利用して時刻ベースのデータセットの分析を簡単に実行できます。
最適なデータモデリングはデータ品質の改善、パフォーマンス向上、ストレージコスト削減に役立ちます。Timestream での効果的なデータモデリングはクエリパターンを理解する事から始まり、パフォーマンスとコスト最適化に役立ちます。クエリパターンを理解し、ディメンジョン、メジャー、パーティション化キーを見極める事で、Timestream 内のデータを効率的に構造化出来ます。データモデリングに加えて、クエリに適切なフィルターを使用する事で、クエリを迅速かつコスト効率よく実行出来るようになります。
ビデオストリーミングデータのモデリング
ビデオストリーミングアプリケーションのデータモデリングの例を通じて、これらの要素がコストとパフォーマンスにどの程度寄与するのかを見てみましょう。アプリケーションは次のデータを収集していると考えて下さい。
- video_id – 各ビデオの一意の識別子
- viewer_id – 動画の視聴者を識別する ID
- device_type – モバイル、Web、スマート TV 等、視聴者がストリーミングに使用するデバイスの種類
- region – 視聴者の位置情報
- session_id – 各ストリームセッションの一意となる ID
- start_time – 視聴者がビデオの視聴を開始した時間
- playback_duration – 視聴者がビデオの視聴に費やした時間
- video_resolution – ビデオの解像度
- playback_quality – 720p, 1080p, 4K 等、ビデオ再生の品質
クエリを詳しく説明する前に、ビデオストリーミングアプリケーションが実行する必要がある重要なタスクを明らかにしましょう。まず、特定の動画がどの位の頻度でどの位視聴されているかを確認する事で、コンテンツの人気と視聴者のエンゲージメントを明らかにする事を目的としています。また、ユーザ体験を最適化する為に、優先するデバイスを特定し、顧客の品質の好みを評価する必要があります。さらには、地域毎の傾向を理解する事で戦略を組みなおしたり、個別のセッションの継続時間と維持率を分析する事で視聴者の行動に対する洞察を得る事が出来ます。また、エンゲージメントの高い視聴者を特定し、動画の趣向に関する傾向を見出す事も目的としています。
以下の例を使って、時系列データとクエリについて考えていきましょう。データは test データベース配下の videostreaming
テーブルに格納されているとします。
session_id | viewer_id | device_type | region | video_id | time | start_time | video_resolution | playback_quality | playback_duaration |
session_87 | viewer_38 | tablet | Australia | video_148428 | 2023-05-17 20:54:39.000000000 | 2023-05-17 20:49:39.000000000 | 4K | Excellent | 2820 |
session_52 | viewer_86 | computer | Australia | video_5982 | 2023-05-17 20:54:31.000000000 | 2023-05-17 20:49:31.000000000 | 4K | Fair | 1020 |
session_96 | viewer_89 | smart_tv | Australia | video_77868 | 2023-05-17 20:54:30.000000000 | 2023-05-17 20:49:30.000000000 | 720p | Excellent | 2340 |
session_45 | viewer_41 | computer | Europe | video_21191 | 2023-05-17 20:54:27.000000000 | 2023-05-17 20:49:27.000000000 | 720p | Excellent | 600 |
session_54 | viewer_51 | computer | US | video_115903 | 2023-05-17 20:54:18.000000000 | 2023-05-17 20:49:18.000000000 | 720p | Good | 420 |
クエリの例をいくつか見てみましょう。
- Query 1 – 次のクエリは
region
でデータをフィルタリングし、過去 1 日間で米国地域で視聴されたビデオの合計回数をカウントしています (Timestream の ago() 関数を利用) 。指定された地域でのビデオ消費量の全体像を示します。
SELECT COUNT(*) AS video_count
FROM "test"."videostreaming"
WHERE time >= ago(1d) AND region = 'US'
- Query 2 – 次のクエリは
device_type
に基づいてデータをグループ化し、過去 1 日分の各デバイスタイプ毎のビデオストリーミングセッションの平均時間を計算します。こうする事でデバイス毎に平均時間がどのように変化するか分析出来ます。この情報は様々なデバイスでのユーザの行動や好みを理解し、それに応じてストリーミングサービスを最適化するのに役立ちます。
SELECT "device_type", AVG("playback_duration") AS "avg_duration"
FROM "test"."videostreaming"
WHERE time >= ago(1d)
GROUP BY "device_type" order by "avg_duration"
- Query 3 – 次のクエリは動画視聴時間に焦点を当て、4K 再生品質で視聴されたビデオの合計時間を算出します。この情報から帯域幅の消費量を把握したり、高品質ビデオコンテンツの需要を評価したりする事が出来ます。
SELECT SUM("playback_duration") AS "total_duration"
FROM "test"."videostreaming" WHERE time >= ago(1h)
and "video_resolution" = '4K'
- Query 4 – 次のクエリでは最も長時間視聴されているビデオを特定出来ます。この情報からビデオの品質を向上させたり、より大々的に宣伝したりできます。
SELECT "video_id", AVG("playback_duration") AS
"average_playback_duration" FROM
"test"."videostreaming" WHERE time >= ago(7d)
GROUP BY "video_id" limit 10
- Query 5 – 次のクエリで低品質で視聴されているビデオを識別出来ます。
SELECT video_id
FROM "test"."videostreaming"
where time >= ago(1d) and video_resolution= "720p"
- Query 6 – 次のクエリは
viewer_id
に基づいてデータをグループ化し、過去 1 日間に各ユーザが視聴したビデオの合計数を計算します。結果は降順で並べ替えられて合計数が最も多い上位 1,000 名の視聴者を特定出来ます。この情報からパワーユーザを特定したり、視聴者のエンゲージメントを判断する事が出来ます。
SELECT "viewer_id", COUNT(*) AS "video_count"
FROM "test"."videostreaming"
WHERE time >= ago(1d)
GROUP BY "viewer_id" ORDER BY "video_count" DESC LIMIT 1000
- Query 7 – 次のクエリは過去 7 日間の各視聴者の平均再生時間を計算し、上位 1,000 名の視聴者を特定します。エンゲージメントが高く、動画の視聴に多くの時間を費やしている視聴者の特定出来る為、パーソナライズされた推奨事項やターゲットを絞った広告に使用できます。
SELECT "viewer_id", AVG("playback_duration") AS "avg_duration"
FROM "test"."videostreaming"
WHERE time >= ago(7d)
GROUP BY "viewer_id" ORDER BY "avg_duration" DESC LIMIT 1000
適切なディメンジョンとメジャーの選択
従来のデータベースから Timestream に移行する場合、既存のデータベースから Timestream にテーブルと列をそのまま移行すれば機能すると考えている方は多いと思います。ですが、本当の課題はクエリパターンを理解した上で、適切なディメンジョン、メジャー、及びオプションでパーティションキーを選択する事にあります。
レコードのタイムスタンプを含むディメンジョンは、各レコードで誰が、何を、いつ、どこで記録したかを特定するのに役立ちます。またディメンジョンはデータを整理・分類し、クエリの一部としてデータをフィルタリングする為に使用されます。ここでは、video_id
、viewer_id
、device_type
、region
及び session_id
は、ビデオストリーミングを整理、分類する為の理想的な選択肢となります。これらの列を利用すると、様々な要素に基づいてデータをフィルター及びグループ化し、様々な観点で分析出来るようになります。例えば、ディメンションを使用して、デバイスの種類ごとに視聴者の趣向を理解したり、地域の視聴パターンを明らかにしたりできます。このようにディメンションを使用すると、データのクエリと分析が柔軟になり、ビデオストリーミング分析のための貴重な洞察が得られます。
メジャーは、データの数学的計算 (合計、平均、変化率の差など) と定量的分析を実行するための基礎を提供します。この例ではメジャーである start_time
、playback_duration
、video_resolution
、playback_quality
は、時間の経過とともに変化する視聴者のストリーミング体験に関連する重要な指標をキャプチャします。これらのメトリクスを使用すると、ビデオセッションの平均継続時間、時間の経過に伴うビデオ品質の傾向の追跡、視聴者が好むビデオ解像度の特定など、さまざまな分析を実行できます。こうして、視聴者のストリーミング行動に関する貴重な洞察が得られ、データに基づいた意思決定を行って全体的なエクスペリエンスを向上させることができます。
ただ、ディメンションまたはメジャーの説明のみに頼るだけでは不十分な場合があり、ディメンションがメジャーになる場合もあります。したがって、クエリパターンから考え始めると、何をどの属性に基づいて計算しているのかを理解しやすくなり、メジャーなのかディメンションなのかを判断するのに役立ちます。例えば、属性がデータのフィルタリングや計算にも使用される場合には、それはメジャーになります。また、ディメンションを決定する際は、特定のレコードのディメンションは更新できないこと、およびすべてのディメンションがレコードを一意に識別することを考慮することが重要です。
Timestream は、データを更新/挿入する機能 (upsert) を提供します。 即ち、レコードが存在しない場合はシステムにレコードを挿入し、レコードが存在する場合はレコードを更新する操作です。ただし、更新は、API 内のすべてのディメンションを使用して、新しいメジャーを追加するか、既存のレコードのメジャーを更新することに限定されます。
ディメンションの数、メジャー (レコードごとの最大メジャー数、テーブル全体の一意のメジャー数)、およびレコードの最大サイズには制限がある為、データモデルを設計する際には、これらの要素を考慮する必要があります。多くの場合、Timestream に取り込まれるデータは、時系列分析に必要な属性以外の追加の属性を含むイベントまたはメトリクスを通じて発生します。制限に達しないようにするには、必要な属性のみをターゲットにします。データに関連性がなく、一緒にクエリを実行しない場合は、1 つの統合テーブルよりも個別のテーブルを使用する方が適しています。
パーティションキーの選択
Timestream でパーティション化する場合、パーティションキーを自身で決定するか、measure_name
列に基づくデフォルトのパーティションを使用するかを選択できますが、カーディナリティの高い列を持ち、クエリの述語として頻繁に使用されるディメンションに基づいてパーティションキーを自身で選択することを推奨します。そうする事で、パーティション間でデータが均等に分散され、パフォーマンスの問題を回避出来ます。このビデオストリーミングの例では、カーディナリティの高い列 (session_id
、viewer_id
、video_id
等) がパーティションキーとして適している可能性があります。ただし、パーティションキーの選択については、どの列がクエリ実行時のフィルタリングに頻繁に使用され、カーディナリティが高いのかを事前にユースケースから検討する必要があります。
場合によっては、データの分散に役立つ属性が無く、顧客定義のパーティションキーを使用できないことがあります。この場合、measure_name
はデータを分割するデフォルトのキーとなります。必ずmeasure_name
属性の設計を慎重に計画してください。一例として言うと、デバイスから圧力と温度のメトリクスを収集している場合は、次のデータ例に示すように、それら ( pressure と temperature) を measure_name
列に配置します。これはデータを均等に分散するのに役立ちます。
device_id | measure_name | Time | Quality | Value |
sensor-123 | temperature | 2023-08-01 19:21:32 | 85 | 43 |
sensor-123 | temperature | 2023-08-01 19:22:32 | 86 | 44 |
sensor-123 | pressure | 2023-08-01 19:23:32 | 83 | 31 |
sensor-123 | pressure | 2023-08-01 19:24:32 | 34 | 123 |
各テーブルは、measure_name
列に対して最大で 8,192 個の個別の値を格納できます。measure_name
列の最適な値が見つからない場合、または設計段階で制限 (8,192 個の一意の値) を超えることに気づいた場合は、こちらでさらなる推奨事項を参照してください。
timestamp、measure_name、および少なくとも 1 つのディメンションとメジャーは、Timestream にデータを取り込む際の必須の列です。顧客定義のパーティションキーが使用されている場合でも、measure_name 列は必須であり、テーブルの作成時にレコードのパーティションキーを強制するオプションが無効になっている場合はパーティションキーとして機能します。
コストとパフォーマンスの最適化
Timestream の価格設定は使用量に基づいており、そのコストの 1 つはクエリ処理中にサーバーレス分散クエリエンジンによってスキャンされるデータ量によって計算されます。新しくタイムスタンプ付きデータが取り込まれると、データは複数のパーティションに分散され、時間、ディメンション、および顧客定義のパーティションキーまたは measure_name
によって構成されます。可能な限り、クエリは時間でフィルタリングを行う事をお勧めします (時間は Timestream のディメンジョンです)。これは、クエリエンジンが定義された時間間隔内のデータが配置されたパーティションのみをスキャンする事で、コスト削減とパフォーマンスの向上に直接影響を与えるためです。
さらにクエリ内で可能な場合は、時間フィルターに加えて、顧客定義のパーティションキーまたは measure_name
(デフォルトのパーティション分割が使用されている場合) でのフィルターを使用することをお勧めします。これにより、Timestream は無関係なパーティションを効率的に取り除き、特定の時間ウィンドウとパーティションフィルター値のパーティションのみをスキャンすることで、クエリのパフォーマンスを向上させ、コストを削減します。クエリを実行する際、すべてのディメンション (顧客定義のパーティションキーを含む) と measure_name
を時間とともにフィルターに使用すると、クエリを最大 30% 高速化できます。
パーティション化キーと時間をフィルターとして使用せずにデータをクエリすると、多数のパーティションがスキャンされることになり、クエリの応答が遅くなり、コストが高くなる可能性があります。Timestream の他のコストの 1 つはストレージです。ディメンション、メジャー、パーティション キーを決定した後は、全体的なコストを節約するために、不要なデータは Timestream に格納しないようにして下さい。
Timestream にデータを保存する
ディメンションとメジャーを定義したら、データ モデリングの作業として次に実施するのは、Timestream にデータを保存する方法を検討する事です。データ型は、書き込みとクエリのために Timestream にどのように保存できるかに基づいて選択する必要があります。
アプリケーションが JSON オブジェクトを出力する場合、それらを JSON 文字列に変換し、VARCHAR 型として保存できます。ダウンストリームのコードまたはアプリケーションはこのエンコードを認識し、デコードを適切に処理する必要があることに注意しましょう。ただし、Timestream は時系列データ用に設計されているため、サービスの機能を最大限に活用するには、各データを別々の列として格納することがベスト プラクティスであることに注意してください。
たとえば、自動車用のアプリケーションが、車体番号、測定値 (燃料消費量、速度、経度、緯度)、および時間の属性を持つデータを扱うとします。その場合、アプリケーションはこの JSON データを Timestream テーブルの個別の列に変換する必要があります。
元の JSON データは以下の通りです。
{
"car_vin_number": "1234567",
"time": "2023-07-20T12:34:56.789Z"
"state": "in_motion"
"speed": "65"
"longitude”: "0.01",
"latitude”: "3.02"
"fuel_consumption": "80 percent"
}
Timestream 用に変換されたデータは以下の通りです。
car_vin_number | state | time | fuel_consumption | speed | longitude | latitude |
1234567 | in_motion | 2023-07-20T12:34:56.789Z | 80 | 65 | 0.01 | 3.02 |
データを個別の列に変換することで、Timestream が時系列データを効率的に保存およびクエリできるようになります。各属性は専用の列になり、Timestream が時間ベースのクエリと集計を実行しやすくなります。
シングルメジャー vs マルチメジャー
Timestream ではレコードを保存する方法として、シングルメジャー方式とマルチメジャー方式があります。
シングルメジャー方式の場合、レコードは 1 つのメジャーしか持ちませんが、マルチメジャー方式の場合、レコードに複数のメジャーを格納する事が出来ます。シングルメジャー方式は、異なる期間で異なるメトリクスをキャプチャする場合、または異なる期間でメトリクスとイベントを発行するカスタム処理ロジックを使用する場合に適しています。しかし、実際には、デバイスまたはアプリケーションは同じタイムスタンプで複数のメトリクスまたはイベントを発行する場合が多いです。
このような場合、同じタイムスタンプで発行されたすべてのメトリクスを同じマルチメジャーレコードに保存する事で、クエリの柔軟性と効率が向上します。多くの場合では、シングルメジャー方式よりもマルチメジャー方式が推奨されます。このアプローチにより複数のメジャーの同時取り込みとクエリが可能になり、全体的なコストが削減され、パフォーマンスが向上します。
次のテーブルはシングルメジャーレコードの例です。
device_id | measure_name | time | measure_value::double | measure_value::bigint |
sensor-123 | temperature | 2022-01-01 08:00:00 | 25.3 | NULL |
sensor-123 | humidity | 2022-01-01 08:00:00 | NULL | 50 |
sensor-123 | pressure | 2022-01-01 08:00:00 | 1014.2 | NULL |
sensor-456 | temperature | 2022-01-01 08:00:00 | 23.8 | NULL |
sensor-456 | humidity | 2022-01-01 08:00:00 | NULL | 55 |
sensor-456 | pressure | 2022-01-01 08:00:00 | 1013.7 | NULL |
以下はマルチメジャーレコードの例です。
device_id | measure_name | time | temperature | humidity | pressure |
sensor-123 | metric | 2022-01-01 08:00:00 | 25.3 | 50 | 1014.2 |
sensor-456 | metric | 2022-01-01 08:00:00 | 23.8 | 55 | 1013.7 |
ベストプラクティス
Timestream でデータモデリングを行う時には、データ保持ポリシー、暗号化キー、アクセス制御、制限、クエリワークロード、アクセスパターン等がアプリケーションのパフォーマンスとコストにどのような影響を与えるかを考慮することが重要です。
- 暗号化キーはデータベースレベルで設定されるため、暗号化要件が異なるデータは異なるデータベースに保存する必要があります
- データ保持ポリシーはテーブルレベルで構成されるため、異なるデータ保持要件を持つデータは別のテーブルに保存する必要があります。
- アクセス制御はデータベースおよびテーブルレベルで設定されるため、アクセス要件が異なるデータは異なるテーブルに保存する必要があります。
- 頻繁にクエリされるデータを同じテーブルに格納することで、クエリの待ち時間を改善しつつ、クエリ作成がやりやすくなります。テーブルが同じ AWS アカウントおよびリージョン内に作成されている場合、Timestream で複数テーブルを結合してクエリ実行することは可能ですが、単一のテーブルをクエリする場合と複数テーブルを結合してクエリする場合とでは、パフォーマンスに顕著な違いが生じる可能性があります。
バッチ書込と CommonAttributes の利用は、Timestream でのデータ取り込みの最適化とコスト削減の達成に重要な役割を果たします。バッチ書込を使用すると、1 回の API 呼び出しで複数のレコードを効率的に取り込むことができ、リクエスト数が減り、全体的な取り込みパフォーマンスが向上します。このアプローチにより、大量のデータをより効率的に処理して保存し、コストを節約できます。 また、CommonAttributes を使用すると、バッチ書込で共有する属性をまとめて定義できるため、データ転送と取り込みのコストが削減されます。 尚、バッチ書込で利用する WriteRecords API リクエストの最大レコード数は 100 です。
さらに、ここでは詳細の説明は行いませんが、データモデリングの決定に役立つ Timestream に関連する他のいくつかの重要な側面と機能があります。
- ストレージ層 – Timestream は、メモリストアと磁気ストアという 2 つのストレージ層を提供します。メモリストアは、高スループットのデータ書き込みと高速なポイントインタイムクエリ向けに最適化されています。磁気ストアは、低スループットの遅延到着データ書き込み、長期データ保存、および高速分析クエリ向けに最適化されています。テーブルの作成時に両方の層の保持ポリシーを構成可能で、テーブル作成後にそれらを変更することもできます。最新のタイムスタンプ付きデータはメモリストアに送信され、設定されたメモリストアの保持期間に基づいて、古いタイムスタンプ付データは磁気ストアに移動されます。
- スケジュールドクエリ – スケジュールドクエリを使用すると、ソーステーブルの Timestream データに対して集計、計算、変換を実行し、それを別テーブルにロードするクエリの実行を自動化できます。別テーブルに格納されたデータは既に集計が完了している為、データ量が削減されており、ダッシュボードや視覚化用のデータとして最適です。より詳細な情報はこちらを参照して下さい。
追加のリソース
詳細については以下のリソースを参照して下さい。
結論
本ポストでは、Timestream の主要な概念と、データモデリングが重要な理由を説明しました。 Timestream でのビデオストリーミングの例を取り上げ、データモデリングがコストの最適化とパフォーマンスにどのように役立つかを詳しく掘り下げました。 まずは 1 か月の無料トライアルを使って、 Timestream を試して頂ければと思います。
翻訳はテクニカルアカウントマネージャーの西原が担当しました。原文はこちらをご覧下さい。