Membuat kueri DynamoDB berdasarkan tanggal

103

Saya berasal dari latar belakang database relasional dan mencoba bekerja dengan DynamoDB amazon

Saya memiliki tabel dengan kunci hash "DataID" dan rentang "CreatedAt" dan banyak item di dalamnya.

Saya mencoba untuk mendapatkan semua item yang dibuat setelah tanggal tertentu dan diurutkan berdasarkan tanggal. Yang cukup mudah dalam database relasional.

Di DynamoDB, hal terdekat yang bisa saya temukan adalah kueri dan menggunakan kunci rentang yang lebih besar dari filter. Satu-satunya masalah adalah bahwa untuk melakukan kueri saya memerlukan kunci hash yang mengalahkan tujuan tersebut.

Jadi apa yang saya lakukan salah? Apakah skema tabel saya salah, bukankah kunci hash harus unik? atau adakah cara lain untuk menanyakan?

tepuk tangan
sumber

Jawaban:

34

Jawaban yang Diperbarui:

DynamoDB memungkinkan spesifikasi indeks sekunder untuk membantu kueri semacam ini. Indeks sekunder dapat bersifat global, artinya indeks menjangkau seluruh tabel di seluruh kunci hash, atau makna lokal bahwa indeks akan ada di dalam setiap partisi kunci hash, sehingga memerlukan kunci hash juga ditentukan saat membuat kueri.

Untuk kasus penggunaan dalam pertanyaan ini, Anda ingin menggunakan indeks sekunder global di bidang "CreatedAt".

Untuk informasi lebih lanjut tentang indeks sekunder DynamoDB, lihat dokumentasi indeks sekunder

Jawaban Asli:

DynamoDB tidak mengizinkan pencarian yang diindeks hanya pada kunci rentang. Kunci hash diperlukan agar layanan mengetahui partisi mana yang harus dicari untuk menemukan data.

Anda tentu saja dapat melakukan operasi pemindaian untuk memfilter berdasarkan nilai tanggal, namun hal ini memerlukan pemindaian tabel lengkap, jadi ini tidak ideal.

Jika Anda perlu melakukan pencarian rekaman yang diindeks berdasarkan waktu di beberapa kunci utama, DynamoDB mungkin bukan layanan yang ideal untuk Anda gunakan, atau Anda mungkin perlu menggunakan tabel terpisah (baik di DynamoDB atau penyimpanan relasional) untuk menyimpan item metadata yang dapat Anda lakukan pencarian terindeks.

Mike Brant
sumber
14
Lihat komentar pada jawaban di bawah ini; ada tidak cara untuk menangani hal ini sekarang, setidaknya tidak untuk apa OP bertanya. GSI masih mengharuskan Anda menentukan kunci hash, jadi Anda tidak dapat membuat kueri untuk semua record yang CreatedAtlebih besar dari titik tertentu.
pkaeding
4
@pkaeding benar. Anda bisa mendapatkan catatan yang lebih tua dari tanggal tertentu menggunakan pemindaian , tetapi Anda tidak bisa mendapatkannya dalam urutan yang diurutkan. GSI tidak akan membantu Anda dalam kasus ini. Tidak mungkin untuk mengurutkan kunci partisi , juga tidak mungkin untuk hanya meminta kunci rentang .
gkiko
15
Bagi anda yang bingung. JAWABAN INI SALAH. Jawaban aslinya benar tetapi jawaban terbarunya tidak. Baca jawaban Warren Parad di bawah ini. Itu benar.
Ryan Shillington
1
@MikeBrant Saya ingin menanyakan (bukan memindai, yang melihat setiap item dalam tabel, membuatnya sangat tidak efisien dan mahal) tabel pada kunci hash GSI tabel (CreatedAt) menggunakan simbol lebih besar dari. Sejauh yang saya tahu, ini tidak bisa dilakukan.
Aziz Javed
4
Masalah yang mungkin Anda dapatkan saat menggunakan tanggal sebagai partisi utama adalah Anda mungkin membuat hotspot di beberapa atau salah satu rekan, karena fakta bahwa di sebagian besar penyimpanan data, data baru lebih sering ditanyakan daripada data lama.
Pengetahuan
53

Mengingat struktur tabel Anda saat ini, hal ini saat ini tidak mungkin dilakukan di DynamoDB. Tantangan terbesarnya adalah untuk memahami bahwa kunci Hash dari tabel (partisi) harus diperlakukan seperti membuat tabel terpisah. Dalam beberapa hal, ini benar-benar hebat (pikirkan kunci partisi seperti membuat tabel baru untuk setiap pengguna atau pelanggan, dll ...).

Permintaan hanya dapat dilakukan di satu partisi. Itu benar-benar akhir dari cerita ini. Ini berarti jika Anda ingin melakukan kueri berdasarkan tanggal (Anda ingin menggunakan msec sejak epoch), maka semua item yang ingin Anda ambil dalam satu kueri harus memiliki Hash (kunci partisi) yang sama.

Saya harus memenuhi syarat ini. Anda benar-benar bisa scanberdasarkan kriteria yang Anda cari, itu tidak masalah, tetapi itu berarti Anda akan melihat setiap baris dalam tabel Anda, dan kemudian memeriksa apakah baris itu memiliki tanggal yang sesuai dengan parameter Anda. Ini sangat mahal, terutama jika Anda berada di bisnis menyimpan acara berdasarkan tanggal di tempat pertama (yaitu Anda memiliki banyak baris.)

Anda mungkin tergoda untuk meletakkan semua data dalam satu partisi untuk menyelesaikan masalah, dan Anda benar-benar bisa, namun throughput Anda akan sangat rendah, mengingat setiap partisi hanya menerima sebagian kecil dari jumlah total yang ditetapkan.

Hal terbaik yang harus dilakukan adalah menentukan partisi yang lebih berguna untuk dibuat guna menyimpan data:

  • Apakah Anda benar-benar perlu melihat semua baris, atau hanya baris oleh pengguna tertentu?

  • Apakah boleh mempersempit daftar berdasarkan Bulan, dan melakukan beberapa kueri (satu untuk setiap bulan)? Atau menurut tahun?

  • Jika Anda melakukan analisis deret waktu, ada beberapa opsi, ubah kunci partisi menjadi sesuatu yang dihitung PUTagar querylebih mudah, atau gunakan produk aws lain seperti kinesis yang cocok untuk pencatatan hanya-tambahan.

Warren Parad
sumber
4
Saya ingin menekankan opsi yang Anda kemukakan di paragraf terakhir Anda tentang mempertimbangkan "berdasarkan tahun". Buat atribut seperti yyyydan hash itu, tetapi juga buat createdtanggal yang dapat Anda gunakan sebagai kunci rentang Anda. Kemudian Anda mendapatkan 10GB data per tahun (27 MB per hari) yang mungkin bagus untuk lebih banyak situasi. Ini berarti bahwa Anda harus membuat kueri per tahun ketika kueri tanggal melewati batas tahun, tetapi setidaknya itu akan berfungsi dan itu lebih aman daripada membuat kunci hash tiruan.
Ryan Shillington
1
Opsi lain: stackoverflow.com/questions/35963243/…
Ryan Shillington
1
seperti yang dijelaskan tautan di atas, kunci partisi berbasis waktu yang ketat dapat menyebabkan hot spot. Jika Anda harus menggunakan kunci partisi berbasis waktu, lebih baik menambahkan beberapa elemen lain ke kunci partisi untuk menyebarkan jangka waktu di beberapa partisi. Saya telah melihat saran untuk hanya menggunakan awalan antara 0-n di mana n adalah jumlah partisi setiap kali bucket harus tersebar.
dres
@RyanShillington Tidak ada batasan 10 GB untuk indeks sekunder global . Batas itu hanya berlaku untuk indeks sekunder lokal .
Simon Forsberg
18

Pendekatan yang saya ikuti untuk mengatasi masalah ini adalah dengan membuat Indeks Sekunder Global seperti di bawah ini. Tidak yakin apakah ini pendekatan terbaik tapi semoga bermanfaat bagi seseorang.

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

Batasan diberlakukan pada pengguna HTTP API untuk menentukan jumlah hari untuk mengambil data, defaultnya adalah 24 jam.

Dengan cara ini, saya selalu dapat menentukan HashKey sebagai hari tanggal saat ini dan RangeKey dapat menggunakan operator> dan <saat mengambil. Dengan cara ini, data juga tersebar di beberapa bagian.

Gireesh
sumber
8

Kunci Hash Anda (jenis primer) harus unik (kecuali Anda memiliki rentang seperti yang dinyatakan oleh orang lain).

Dalam kasus Anda, untuk menanyakan tabel Anda, Anda harus memiliki indeks sekunder.

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

Hash Key Anda adalah ID Indeks sekunder Anda didefinisikan sebagai: DataID-Created-index (itulah nama yang akan digunakan DynamoDB)

Kemudian, Anda dapat membuat kueri seperti ini:

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
    }
});

Pada dasarnya kueri Anda terlihat seperti:

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

Indeks sekunder akan meningkatkan unit kapasitas baca / tulis yang diperlukan sehingga Anda perlu mempertimbangkannya. Masih jauh lebih baik daripada melakukan pemindaian, yang akan mahal dalam pembacaan dan pada waktunya (dan saya dibatasi hingga 100 item).

Ini mungkin bukan cara terbaik untuk melakukannya, tetapi bagi seseorang yang terbiasa dengan RD (saya juga terbiasa dengan SQL), ini adalah cara tercepat untuk menjadi produktif. Karena tidak ada batasan terkait skema, Anda dapat menyiapkan sesuatu yang berfungsi dan setelah Anda memiliki bandwidth untuk bekerja dengan cara yang paling efisien, Anda dapat mengubah berbagai hal.

ET
sumber
1
Anda mengatakan tidak ada batasan, tetapi Anda harus tahu bahwa pendekatan ini berarti Anda dapat menyimpan paling banyak 10GB data (maksimum satu partisi).
Ryan Shillington
Ini akan menjadi pendekatan jika DataID diketahui. Tapi di sini kita perlu mendapatkan setiap baris yang dibuat lebih dari beberapa tanggal.
Yasith Prabuddhaka
3

Anda dapat membuat kunci Hash menjadi sesuatu di sepanjang baris id 'kategori produk', kemudian kunci rentang sebagai kombinasi cap waktu dengan id unik yang ditambahkan di akhir. Dengan cara itu Anda mengetahui kunci hash dan masih dapat menanyakan tanggal dengan lebih dari.

greg
sumber
1

Anda dapat memiliki beberapa kunci hash yang identik; tetapi hanya jika Anda memiliki kunci rentang yang bervariasi. Anggap saja seperti format file; Anda dapat memiliki 2 file dengan nama yang sama di folder yang sama selama formatnya berbeda. Jika formatnya sama, namanya harus berbeda. Konsep yang sama berlaku untuk kunci hash / range DynamoDB; anggap saja hash sebagai nama dan rentang sebagai formatnya.

Juga, saya tidak ingat apakah mereka memiliki ini pada saat OP (saya tidak percaya mereka memilikinya), tetapi mereka sekarang menawarkan Indeks Sekunder Lokal.

Pemahaman saya tentang ini adalah bahwa sekarang memungkinkan Anda untuk melakukan kueri yang diinginkan tanpa harus melakukan pemindaian penuh. Sisi negatifnya adalah indeks ini harus ditentukan saat pembuatan tabel, dan juga (saya yakin) tidak boleh kosong saat membuat item. Selain itu, mereka membutuhkan throughput tambahan (meskipun biasanya tidak sebanyak pemindaian) dan penyimpanan, jadi ini bukan solusi sempurna, tetapi alternatif yang layak, untuk beberapa.

Saya masih merekomendasikan jawaban Mike Brant sebagai metode yang disukai dalam menggunakan DynamoDB; dan menggunakan metode itu sendiri. Dalam kasus saya, saya hanya memiliki tabel pusat dengan hanya kunci hash sebagai ID saya, kemudian tabel sekunder yang memiliki hash dan rentang yang dapat ditanyakan, lalu item tersebut menunjukkan kode ke "item yang diminati" di tabel pusat, secara langsung .

Data tambahan terkait indeks sekunder dapat ditemukan di dokumentasi DynamoDB Amazon di sini bagi mereka yang tertarik.

Bagaimanapun, semoga ini akan membantu orang lain yang terjadi pada utas ini.

DGolberg
sumber
Saya mencoba membuat tabel DynamoDB di mana ada AWSDynamoDBKeySchemaElement 'createAt' dari jenis hash dan lagi AWSDynamoDBKeySchemaElement 'createAt' dari jenis range dan saya mendapat kesalahan yang mengatakan Domain Kesalahan = com.amazonaws.AWSDynamoDBErrorDomain Code = 0 "(null)" UserInfo = {__ type = com.amazon.coral.validate # ValidationException, message = Hash Key dan elemen Range Key di KeySchema memiliki nama yang sama}. Jadi saya tidak berpikir apa yang Anda katakan itu benar.
pengguna1709076
Saya yakin Anda salah paham (meskipun saya kira deskripsi saya juga tidak terlalu jelas). Anda tidak dapat memiliki 2 atribut berbeda (kolom) dengan nama yang sama, dalam sebuah tabel, tetapi ketika Anda membuat kunci hash dengan kunci rentang, Anda dapat memiliki beberapa item yang semuanya menggunakan hash yang sama selama rentangnya berbeda, dan sebaliknya. Misalnya: Hash Anda adalah "ID" dan rentang Anda adalah "Tanggal", Anda dapat memiliki 2 ID "1234" selama Tanggal mereka berbeda.
DGolberg
Ah DGoldberg! Aku mendapatkanmu sekarang. Itu hebat. Jadi untuk kasus saya karena saya hanya dan selalu hanya ingin menanyakan pesan teks 'after date = x', Sepertinya saya bisa menyetel semua pesan teks agar memiliki 'fake_hash = 1' yang sama. Kemudian lakukan query.keyConditionExpression = @ "fake_hash = 1 dan #Date>: val". Terima kasih banyak. Jika Anda memiliki masukan lain, saya akan senang mendengarnya karena memang tampak aneh memiliki hash yang selalu bernilai sama?
pengguna1709076
Saya harus memeriksa lagi, tetapi saya cukup yakin Anda dapat melakukan kueri pada tabel khusus hash ... meskipun jika Anda menggunakan stempel tanggal / waktu sebagai hash Anda, saya sarankan untuk merekam ke satuan sesingkat mungkin, seperti milidetik atau nano / mikrodetik (berapa pun satuan waktu terkecil yang dapat direkam kode), untuk mengurangi kemungkinan tanggal / waktu yang tumpang tindih. Selain itu, Anda dapat menambahkan penguncian optimis untuk lebih mengurangi kemungkinan tumpang tindih: docs.aws.amazon.com/amazondynamodb/latest/developerguide/… Cukup coba lagi lain kali jika ada konflik.
DGolberg
-11

Jawaban yang Diperbarui Tidak ada cara mudah untuk melakukan ini menggunakan Kueri DB Dynamo dengan hasil yang dapat diprediksi. Satu opsi (sub optimal) adalah menggunakan GSI dengan HashKey & CreatedAt buatan. Kemudian kueri dengan HashKey saja dan sebutkan ScanIndexForward untuk memesan hasilnya. Jika Anda dapat membuat HashKey alami (katakanlah kategori item, dll.) Maka metode ini adalah pemenangnya. Di sisi lain, jika Anda menyimpan HashKey yang sama untuk semua item, maka itu akan memengaruhi throughput sebagian besar ketika kumpulan data Anda tumbuh melebihi 10GB (satu partisi)

Jawaban Asli: Anda dapat melakukannya sekarang di DynamoDB dengan menggunakan GSI. Jadikan kolom "CreatedAt" sebagai GSI dan masalah kueri seperti (GT some_date). Simpan tanggal sebagai angka (mdet sejak masa) untuk jenis kueri ini.

Detail tersedia di sini: Indeks Sekunder Global - Amazon DynamoDB: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html#GSI.

Ini adalah fitur yang sangat kuat. Ketahuilah bahwa kueri terbatas pada (EQ | LE | LT | GE | GT | BEGINS_WITH | BETWEEN) Kondisi - Amazon DynamoDB: http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Condition.html

Sony Kadavan
sumber
31
Saya tidak memilih karena sejauh yang saya tahu, jawaban Anda salah. Sama seperti kunci utama tabel, Anda dapat meminta kunci hash GSI hanya dengan operator EQ. Jika Anda menyiratkan bahwa CreatedAtseharusnya itu adalah kunci rentang GSI, Anda harus memilih kunci hash - dan kemudian Anda kembali ke tempat Anda memulai, karena Anda hanya dapat meminta GT CreatedAtuntuk nilai tertentu dari kunci hash.
PaF
Setuju dengan PaF. Menggunakan GSI dengan kunci hash sebagai waktu pembuatan tidak membantu dengan pertanyaan yang diajukan di OP.
4-8-15-16-23-42