日付によるDynamoDBのクエリ


102

私はリレーショナルデータベースのバックグラウンドから来ており、AmazonのDynamoDBを操作しようとしています

ハッシュキー "DataID"と範囲 "CreatedAt"を持つテーブルと、その中に多数のアイテムがあります。

特定の日付以降に作成され、日付順にソートされたすべてのアイテムを取得しようとしています。これは、リレーショナルデータベースでは非常に簡単です。

DynamoDBで私が見つけることができる最も近いものはクエリであり、フィルターより大きい範囲キーを使用しています。唯一の問題は、クエリを実行するために、目的を達成できないハッシュキーが必要になることです。

だから私は何が間違っているのですか?私のテーブルスキーマは間違っていますか?ハッシュキーは一意ではありませんか?またはクエリする別の方法はありますか?

回答:


34

更新された回答:

DynamoDBでは、この種のクエリに役立つセカンダリインデックスを指定できます。セカンダリインデックスは、グローバル、つまりインデックスがハッシュキー全体のテーブル全体にわたること、またはローカルインデックスが各ハッシュキーパーティション内に存在することを意味するため、クエリを作成するときにハッシュキーも指定する必要があります。

この質問のユースケースでは、「CreatedAt」フィールドでグローバルセカンダリインデックスを使用します。

DynamoDBセカンダリインデックスの詳細については、セカンダリインデックスのドキュメントをご覧ください。

元の答え:

DynamoDBでは、範囲キーのみのインデックス付きルックアップは許可されていません。ハッシュキーは、サービスがデータを見つけるために調べるパーティションを認識できるようにするために必要です。

もちろん、スキャン操作を実行して日付値でフィルタリングすることもできますが、これには全表スキャンが必要になるため、理想的ではありません。

複数の主キーにわたってレコードのインデックス検索を時間で実行する必要がある場合、DynamoDBは理想的なサービスではない可能性があります。または、(DynamoDBまたはリレーショナルストアの)別のテーブルを使用してアイテムを保存する必要がある場合があります。インデックス付きルックアップを実行できるメタデータ。


14
以下の回答に関するコメントを参照してください。現在、これを処理する方法はありません。少なくともOPが要求したものについてはそうではありません。GSIでは引き続きハッシュキーを指定する必要があるためCreatedAt、特定のポイントを超えるすべてのレコードをクエリすることはできません。
2015年

4
@pkaedingは正しいです。scanを使用して特定の日付よりも古いレコードを取得できますが、ソートされた順序で取得することはできません。この場合、GSIは役立ちません。パーティションキーを並べ替えることはできません。また、範囲キーのみをクエリすることもできません。
gkiko 2015年

15
混乱している人のために。この答えは間違っています。彼の最初の答えは正しいですが、彼の更新された答えは正しくありません。以下のWarren Paradの回答を読んでください。あたりです。
ライアン

1
@MikeBrant 大なり記号を使用して、テーブルのGSIハッシュキー(CreatedAt)のテーブルに対してクエリ実行ます(スキャンではなく、テーブル内のすべてのアイテムを参照するため、非常に非効率的でコストがかかります)。私の知る限り、これはできません。
Aziz Javed 2017年

4
日付プライマリパーティションとして使用しているときに発生する可能性のある問題は、ほとんどのデータストレージで古いデータよりも新しいデータが頻繁にクエリされるため、ピアの一部またはいずれかにホットスポットを作成する可能性があることです。
知識

53

現在のテーブル構造を考えると、これは現在DynamoDBでは不可能です。大きな課題は、テーブル(パーティション)のハッシュキーを別のテーブルを作成するものとして扱う必要があることを理解することです。いくつかの点でこれは非常に強力です(パーティションキーをユーザーまたは顧客ごとに新しいテーブルを作成するなどと考えてください)。

クエリは単一のパーティションでのみ実行できます。これで話は終わりです。つまり、日付でクエリを実行する場合(エポック以降はmsecを使用する必要があります)、単一のクエリで取得するすべてのアイテムは同じハッシュ(パーティションキー)を持つ必要があります。

これを修飾する必要があります。あなたscanが探している基準で絶対にできます、それは問題ありませんが、それはあなたがテーブルのすべての行を見て、そしてその行があなたのパラメータと一致する日付を持っているかどうかをチェックすることを意味します。これは、特に最初からイベントを日付で格納するビジネスをしている場合(つまり、行が多い場合)は、非常にコストがかかります。

問題を解決するためにすべてのデータを単一のパーティションに入れたいと思うかもしれませんが、絶対に可能ですが、各パーティションが設定された合計量の一部しか受け取らない場合、スループットは非常に低くなります。

最善の方法は、データを保存するために作成するより有用なパーティションを決定することです。

  • 本当にすべての行を確認する必要がありますか、それとも特定のユーザーによる行のみですか?

  • 最初にリストを月ごとに絞り込んで、複数のクエリを実行してもかまいませんか(毎月1つ)。または年によって?

  • あなたは時系列分析を行っている場合はオプションのカップルがあり、上のcomputated何かにパーティションキーを変更PUTするためにquery容易に、またはログのみを追加するために自分自身を貸すキネシスのような他のAWS製品を使用しています。


4
「年ごと」を検討することについて、最後の段落で述べたオプションを強調したいと思います。のような属性を作成してyyyyハッシュしcreatedますが、範囲キーとして使用できる日付も作成します。その後、年間10 GBのデータ(1日あたり27 MB)を取得します。これは、より多くの状況でおそらく問題ありません。ただし、日付クエリが年の境界を超える場合、年ごとにクエリを作成する必要がありますが、少なくとも機能し、ダミーハッシュキーを作成するよりも安全です。
ライアン


1
上記のリンクで説明されているように、厳密に時間ベースのパーティションキーはホットスポットにつながる可能性があります。時間ベースのパーティションキーを使用する必要がある場合は、パーティションキーに他の要素を追加して、期間を複数のパーティションに分散することをお勧めします。私は、0からnまでのプレフィックスを使用するだけの提案を見てきました。nは、バケットを分散する必要がある各パーティションの数です。
18年

@RyanShillington グローバルセカンダリインデックスに10GBの制限はありません。この制限は、ローカルセカンダリインデックスにのみ適用されます。
Simon Forsberg

18

この問題を解決するために私がたどったアプローチは、以下のようにグローバルセカンダリインデックスを作成することです。これが最善の方法であるかどうかはわかりませんが、うまくいけば誰かに役立つでしょう。

Hash Key                 | Range Key
------------------------------------
Date value of CreatedAt  | CreatedAt

データを取得する日数を指定するためにHTTP APIユーザーに課される制限。デフォルトは24時間。

このようにして、HashKeyを常に現在の日付として指定でき、RangeKeyは取得中に>および<演算子を使用できます。このようにして、データは複数のシャードにも分散されます。


8

ハッシュキー(主な種類)は一意である必要があります(他の人が述べたような範囲がない限り)。

あなたのケースでは、テーブルをクエリするには、セカンダリインデックスが必要です。

|  ID  | DataID | Created | Data |
|------+--------+---------+------|
| hash | xxxxx  | 1234567 | blah |

ハッシュキーはIDです。セカンダリインデックスは次のように定義されます:DataID-Created-index(DynamoDBが使用する名前です)

次に、次のようなクエリを作成できます。

var params = {
    TableName: "Table",
    IndexName: "DataID-Created-index",
    KeyConditionExpression: "DataID = :v_ID AND Created > :v_created",
    ExpressionAttributeValues: {":v_ID": {S: "some_id"},
                                ":v_created": {N: "timestamp"}
    },
    ProjectionExpression: "ID, DataID, Created, Data"
};

ddb.query(params, function(err, data) {
    if (err) 
        console.log(err);
    else {
        data.Items.sort(function(a, b) {
            return parseFloat(a.Created.N) - parseFloat(b.Created.N);
        });
        // More code here
    }
});

基本的に、クエリは次のようになります。

SELECT * FROM TABLE WHERE DataID = "some_id" AND Created > timestamp;

セカンダリインデックスは、必要な読み取り/書き込み容量の単位を増やすため、それを考慮する必要があります。それでも、スキャンを実行するよりもはるかに優れています。これは、読み取りと時間のコストが高くなります(信じられる100アイテムに制限されています)。

これは最良の方法ではないかもしれませんが、RDに慣れている人(私はSQLにも慣れています)にとっては、生産性を上げる最も速い方法です。スキーマに関して制約がないため、機能するものを作成し、最も効率的な方法で作業するための帯域幅があれば、状況を変えることができます。


1
制約はないと言いますが、このアプローチでは最大10 GBのデータ(最大で1つのパーティション)を保存できることを知っておく必要があります。
ライアン

これは、DataIDがわかっている場合のアプローチです。しかし、ここでは、作成された日付が一定以上のすべての行を取得する必要があります。
Yasith Prabuddhaka 2018年

3

「製品カテゴリ」IDの行に沿ってハッシュキーを作成し、範囲キーをタイムスタンプと一意のIDを最後に追加したものの組み合わせとして作成できます。こうすることで、ハッシュキーを知っていても、より大で日付を照会できます。


1

複数の同一のハッシュキーを使用できます。ただし、変動する範囲キーがある場合のみ。ファイル形式のように考えてください。形式が異なる限り、同じフォルダに同じ名前の2つのファイルを含めることができます。形式が同じ場合は、名前が異なっている必要があります。同じ概念がDynamoDBのハッシュ/範囲キーに適用されます。ハッシュを名前、範囲をフォーマットと考えてください。

また、OPの時点でこれらがあったかどうかは覚えていませんが(信じていません)、ローカルセカンダリインデックスを提供しています。

これらについての私の理解は、フルスキャンを実行しなくても目的のクエリを実行できるようになったことです。欠点は、これらのインデックスはテーブルの作成時に指定する必要があることと、アイテムの作成時に(私が思うに)空白にすることはできないことです。さらに、追加のスループット(通常はスキャンほどではありません)とストレージが必要になるため、完全なソリューションではなく、実行可能な代替策もあります。

ただし、DynamoDBを使用する好ましい方法として、マイクブラントの回答をお勧めします。その方法を自分で使用します。私の場合、IDとしてハッシュキーのみを持つ中央テーブルがあり、次にクエリ可能なハッシュと範囲を持つセカンダリテーブルがあり、アイテムはコードを中央テーブルの「対象アイテム」に直接ポイントします。 。

セカンダリインデックスに関する追加のデータは、AmazonのDynamoDBドキュメントにあります

とにかく、うまくいけば、これはこのスレッドで発生する他の人を助けるでしょう。


タイプハッシュのAWSDynamoDBKeySchemaElement 'createdAt'とタイプ範囲のAWSDynamoDBKeySchemaElement 'createdAt'があるDynamoDBテーブルを作成してみましたが、Error Domain = com.amazonaws.AWSDynamoDBErrorDomain Code = 0 "(null)" UserInfoと言うエラーが発生しました= {__ type = com.amazon.coral.validate#ValidationException、message = KeySchemaのハッシュキーと範囲キー要素の両方に同じ名前があります}。だから私はあなたの言っていることが正しいとは思わない。
user1709076

私はあなたが誤解していると思います(私も私の説明があまり明確ではなかったと思います)。テーブルに同じ名前の2つの異なる属性(列)を含めることはできませんが、範囲キーを使用してハッシュキーを作成すると、範囲が異なる限り、すべてが同じハッシュを使用する複数のアイテムを作成できます。その逆。例:ハッシュが「ID」で範囲が「日付」の場合、日付が異なる限り、ID「1234」のインスタンスを2つ持つことができます。
DGolberg、2015

ああDGoldberg!私は今あなたを得る。それは素晴らしいことです。したがって、私の場合、テキストメッセージを「日付= x以降」にのみ照会したいので、すべてのテキストメッセージに同じ「fake_hash = 1」を設定できるように見えます。次に、query.keyConditionExpression = @ "fake_hash = 1 and #Date>:val"を実行します。どうもありがとうございました。他の入力がある場合は、常に同じ値であるハッシュを持つのは奇妙に思われるので、それを聞いて喜んでいますか?
user1709076

もう一度確認する必要がありますが、ハッシュのみのテーブルでクエリを実行できると確信しています...ただし、日付/タイムスタンプをハッシュとして使用している場合は、ミリ秒やナノ/マイクロ秒など、可能な限り短い単位(コードが記録できる時間の最小単位が何であっても)。日付/時刻が重複する可能性を減らします。また、あなたはさらに重複の可能性を低減するために、楽観的ロックを追加することができます。docs.aws.amazon.com/amazondynamodb/latest/developerguide/...は 競合があるかどう単に別の時間を再試行してください。
DGolberg、2015

-11

更新された回答 予測可能なスループットでDynamo DBクエリを使用してこれを行う便利な方法はありません。1つの(準最適)オプションは、人工HashKeyとCreatedAtでGSIを使用することです。次に、HashKeyだけでクエリを実行し、ScanIndexForwardに言及して結果を並べ替えます。自然なHashKey(アイテムのカテゴリなど)を思い付くことができる場合、この方法が勝者です。一方、すべてのアイテムに同じHashKeyを保持すると、データセットが10GB(1つのパーティション)を超えたときに、スループットにほとんど影響します

元の答え: DynamoDBでは、GSIを使用してこれを行うことができます。「CreatedAt」フィールドをGSIとして作成し、(GT some_date)のようなクエリを発行します。この種類のクエリの日付(エポックからのミリ秒)として日付を格納します。

詳細はこちら:グローバルセカンダリインデックス-Amazon DynamoDB:http : //docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html#GSI.Using

これは非常に強力な機能です。クエリは(EQ | LE | LT | GE | GT | BEGINS_WITH | BETWEEN)条件に限定されていることに注意してください-Amazon DynamoDB:http ://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Condition.html


31
私の知る限り、あなたの答えは正しくないので、私は反対票を投じました。テーブルの主キーと同様に、GSIのハッシュキーはEQ演算子でのみクエリできます。あなたはそれが暗示された場合CreatedAt、あなたは上のクエリGTにできますので、あなたが開始し、その後、あなたはしているバック- GSIの範囲のキーである必要があり、その後、ハッシュキーを選択する必要がありますCreatedAt、特定の値に対してのみハッシュキー。
PaF 2014年

PaFに同意します。ハッシュキーを作成時間としてGSIを使用しても、OPでの質問には役立ちません。
4-8-15-16-23-42 2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.