2018年5月7日 星期一

AWS DynamoDB SDK v2 基礎使用(四):讀取資料

在 DynamoDB 中,讀取被分成 Query 和 Scan 兩種行為
兩種行為的差異,主要來自於存取的範圍是否侷限於同一個 Partition。
這其實在之前(好久以前了呀….XD)的文章中有提到過,概念上可以當成一個 Partition 就是一台主機
在寫入資料時,作為 Partition Key 的值會決定資料會被存放在哪一台主機。
而讀取資料時,使用 Query 是在特定一台主機上讀取資料,而使用 Scan 則會在所有主機上讀取資料。

Query 和 Scan

至於使用 Query 和 Scan 會產生什麼差異呢?這裡稍微再提一下~
Amazon 是 on-demand 的收費模式,也就是使用多少才收多少錢,因此使用的量越大,Amazon 收的費用也就越高。
Scan 的操作一般來說,通常是用在事先不清楚 Partition Key 為何的狀況,此時 DynamoDB 會掃遍所有的 Partition
也就是說會實際地讀取了所有資料表中的 record,並篩選出符合條件的 record 以後才回覆。
而因為 DynamoDB 的確做了所有 record 的讀取,因此收的讀取費用(以 read capacity 形式表達)自然是所有 record 的讀取費。
也就是說,假設表格中有 N 筆資料,DynamoDB Scan 操作回覆了 M 筆資料(N >= M)
但實際要支付給 Amazon 的是 N 筆資料的讀取費。

若是使用 Query,而且篩選條件滿足表格中的索引格式,則只會實際讀取到滿足篩選條件的 record
這時,DynamoDB Query 回覆的 M 筆資料,將會導致的就是 M 筆資料的讀取費了。

不過要注意的是,Query 跟 Scan 其實在收費模式上並沒有什麼不同,差別只是在於 Scan 會跨 Partition 讀取、且不參考 Index。
在使用 Query 時,並不是只要用了 Query 讀取 M 筆資料,就一定只會收 M 筆資料的讀取費。
因為問題還是在於「索引有沒有正確被使用」。

DynamoDB 跟 RDBMS 在索引方面沒什麼太大的不同,在一群位於相同 Partition 的資料集中
如果篩選的條件並不屬於索引的一部分,則 DynamoDB 依然會掃描這個 Partition 內所有的資料
也就是說,如果這個 Partition 內有 P 筆資料,最後 DynamoDB 回覆 M 筆資料(N >= P >= M)
則 Amazon 實際會收取 P 筆資料的讀取費。

讀取資料

讀取資料時,因為 DynamoDB 很囉唆(?),所以需要準備不少東西。

首先,一定會需要的是參數表(Attribute Value),這個參數表是表示讀取操作裡所有需要的欄位的值
例如我要查詢欄位 my_pk = 1、my_sk 介於 0 ~ 100 之間的所有 record,這裡的 1 和 0 ~ 100 都是參數。

接著,我需要給 key condition expression,用來告訴 DynamoDB 我要怎麼使用 Primary Key 去查詢我的資料。

除了這兩個以外,其他的就是視需求決定了。

使用索引讀取資料

Map<String, AttributeValue> attributeValues = new TreeMap<String, AttributeValue>();
    attributeValues.put(
        ":userid", AttributeValue.builder().s("user_name").build());
    attributeValues.put(
        ":t_start", 
        AttributeValue.builder().n(0).build());
    attributeValues.put(
        ":t_end", 
        AttributeValue.builder().n(99999999).build());
    attributeValues.put(
        ":output", AttributeValue.builder().bool(true).build());

QueryRequest request = QueryRequest.builder()
    .tableName("table_name")
    .indexName("my_index")
    .keyConditionExpression("user_id = :userid AND (event_timestamp BETWEEN :t_start AND :t_end)")
	.filterExpression("rec_output = :output")
    .projectionExpression("user_id,event_timestamp")
    .scanIndexForward(false)
    .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL)
	.build();

在上述的程式碼中,第一段是在建立上頭提到的參數表(Attribute Value),第二段則是建立整個 Query。
其中,這裡頭有兩個 expression,一個是 key condition expression,另一個則是 filter expression
兩者的差別在於篩選的對象是否是 Primary Key 的一部分。
對於 Partition Key 和 Sort Key 的篩選,必須要使用 key condition expression;其他欄位的篩選則是透過 filter expression。

事實上,這裡也很明確切分出「使用索引」和「不使用索引」的行為:
在 key condition expression 中的篩選行為,是會依靠索引完成的;在 filter expression 中的篩選行為,則會掃描剩下的所有 record 來篩選。

此外,這裡也特別指定了索引的名稱,這點是很重要的。
在 DynamoDB 中,每個查詢只能利用一個索引,而且 DynamoDB 不會幫你自動選擇最適合的索引,而是需要你自己指定
如果沒有指定索引,那 DynamoDB 會使用表格的 Sort Key 來作為查詢的索引。

整體來說,這裡的 Query 就相當於以下的 SQL statement:

SELECT user_id, event_timestamp
FROM table_name 
WHERE user_id = ‘user_name’ AND event_timestamp BETWEEN 0 AND 99999999 AND rec_output = true 
ORDER BY event_timestamp DESC

而在 DynamoDB 上執行時,DynamoDB 會先去 user_id = ‘user_name’ 的這個 Partition 上
透過 my_index 這個索引尋找 event_timestamp 在 0 ~ 99999999 之間的所有 record
接著把這些 record 全掃過一次,收集其中 rec_output = true 的 record,以 event_timestamp 反序的順序回覆給使用者。

沒有留言: