2016年5月2日 星期一

AWS DynamoDB SDK v2 基礎使用(一):基本認知

DynamoDB 是 Amazon 提供的 NoSQL 資料庫
不過就我最近使用的感覺,其實我覺得它比較像是「有 RDBMS 靈魂的 NoSQL」
換言之,我覺得它其實是個 RDBMS,只是稍微支援了一點 NoSQL 的功能而已。

關於 DynamoDB 的簡介就不細談了,網路上大概可以查到不少
主要想紀錄的是如何使用 DynamoDB 的 Java SDK。
陸續找了不少資料後,我覺得 DynamoDB 的文件 [1] 真是相當差勁。
官方文件給的範例非常地簡略,對於稍微複雜一點的指令就沒有說明該如何實作
Google 往往會找到許多過去的部落格文章、或者甚至是官方的部落格
然後會發現同一件事情有好幾種不同的實作方法,估計大概是新舊的 API 的差別
每種實作方法似乎都有一些優點跟缺點,對使用者來說雖然可以依照自己的需求做選擇
但對於多數剛開始使用 API 的開發者來說,可能只會跟我一樣頻頻想說「WTF」。

以上是前言,在陸續開始紀錄 CRUD 的實作方法前,需要先了解 DynamoDB
千萬不要以「它就是個 NoSQL 資料庫」的想法就直接跳進去
你絕對會發現它跟你想的有很大的不同。

    1. Primary Key

    首先,DynamoDB 有所謂 Primary Key 的概念 [2]。
    Primary Key 就如同過往的 RDBMS 一般,是每筆資料都一定要有的唯一欄位
    也就是說,每筆資料在 DynamoDB 中都必須有 Primary Key,而且值必須是唯一的。
    這點在 RDBMS 中是很普遍的習慣,所以對於慣於使用 RDBMS 的人來說可能沒什麼感覺
    但對於習慣使用 NoSQL 的人,可能就會是第一個「WTF」的來源。
    在多數案例下,光是 Primary Key 這點,就導致資料的 Schema 必須針對 DynamoDB 的特性去重新設計。

    Primary Key 有兩種組成方法,一種是用單一欄位(Hash Key)組成
    另一種則是用複合欄位(Hash Key + Range Key)組成。
    不論組成方法為何,Primary Key 都必須是唯一的,但組成 Primary Key 的個別欄位則不一定。
    也就是說,當以單一欄位(Hash Key)組成 Primary Key 時
    因為 Primary Key 必須是唯一的,且只有一個欄位組成 Primary Key
    因此組成 Primary Key 的這個欄位(Hash Key)也必須是唯一的。
    而若是以複合欄位(Hash Key + Range Key)組成 Primary Key
    則 Hash Key 可以重複、Range Key 也可以重複,但是 Hash Key + Range Key 必須唯一。

    上段提到的 Hash Key(或稱 Partition Key),是 DynamoDB 用來決定資料要放在哪裡的屬性 [3]
    因此可以想像每筆資料一定都要有 Hash Key,就技術上來說是相當合理的要求
    不過 DynamoDB 同時也要求除了 Scan 操作以外,無論是 CRUD,所有對資料的 query 一定都要給 Hash Key,並且必須用 EQUAL 運算子
    「無論是 CRUD」,這意味著連 Read 操作也不例外。
    因此,在 DynamoDB 上,所有 query 都必須明確知道 Hash Key 為何
    亦即,絕不能用以往在 RDBMS 或者像 MongoDB 那樣
    用一個不知道會是多少的序號或隨機產生的資料作為 Hash Key
    因為那樣會導致日後要做 Read、Update 或 Delete 時會遭遇困難。
    所以在開始存資料到 DynamoDB 以前,一定要仔細地設計資料的 Schema
    以免資料放進去以後才悔不當初。

    上面講的好像有點複雜,簡單舉個例子。假設我有一張 Table,內容如下 :

    Primary Key
    FieldA (Hash Key) FieldB (Range Key) FieldC ...
    A 1 123 ...
    A 2 456 ...
    B 2 789 ...
    B 3 789 ...

    此時,對於以下這種 query 就可以正常使用:

    SELECT * FROM table WHERE FieldA=A AND FieldB < 2
    SELECT FieldA, FieldC FROM table WHERE FieldA = A

    但是下面這個 query 則不能使用:

    SELECT * FROM table WHERE FieldB < 2
    SELECT * FROM table WHERE FieldC = 123

    不能使用的原因是因為,FieldA 是 Hash Key,因此 query 裡一定要包含 FieldA=XXXX 的條件。
    PS. 當然 DynamoDB 並不是用 SQL 來操作的,只是在這裡用大家都看得懂的 SQL 來表示。

    回到主題,也許有人會注意到上面寫到「除了 Scan 操作以外」
    是的,Scan 操作可以不用知道 Hash Key 就能做 Read、Update 及 Delete
    但它帶來了兩個昂貴的代價:費用和效能。
    詳細的描述可以繼續往下看。

    2. Throughput

    Throughput 是 Amazon 在 DynamoDB 上實作的一個獨特且大受好評的功能 [4]。
    當使用者在建立 Table 時,必須針對 Table 指定 Read 和 Write 的 Throughput Unit
    用以決定這個 Table 整體的 Throughput(當然也決定了使用者要為了這個 Table 付多少錢)。
    從營運的角度來看,使用者可以藉由指定 Throughput 來規範自己的成本花費
    不會因為 Application 出乎意料之外的大量行為,導致自己的帳單暴漲
    但反過來說,Application 就會處處受制於 Throughput。
    具體而言,如果 Application 當前的 Throughput 超過設置的上限,導致的反應是其他操作都會失敗
    API 會回饋說沒有足夠的 Throughput,並回覆對應的錯誤代碼。

    簡單來說,DynamoDB 將所有使用者在 DynamoDB 上的行為,以 Throughput Unit 來表示
    使用的 Throughput Unit 越多,就表示 Amazon 提供給使用者使用的資源越多
    當然也就表示 Amazon 收的費用越貴了。

    以下節錄官方文件 [4] 中對於 Read Throughput 和 Write Throughput 的描述:

    • One read capacity unit represents one strongly consistent read per second, or two eventually consistent reads per second, for items up to 4 KB in size. If you need to read an item that is larger than 4 KB, DynamoDB will need to consume additional read capacity units. The total number of read capacity units required depends on the item size, and whether you want an eventually consistent or strongly consistent read.
    • One write capacity unit represents one write per second for items up to 1 KB in size. If you need to write an item that is larger than 1 KB, DynamoDB will need to consume additional write capacity units. The total number of write capacity units required depends on the item size.

    細節這裡就不談了,只談談為什麼上段提到的 Scan 操作會有昂貴的代價。
    首先,DynamoDB 雖然提供了 Scan 操作,但 Scan 操作本質上就是掃描整張 Table,最後收集出滿足條件的資料回傳
    因此可以合理想像 Scan 操作的效率想必不會很好。
    此外,在 Scan 的過程中,因為使用者對於 DynamoDB 的每個操作都要消耗 Throughput Unut
    因此 Scan 這種會尋訪所有資料的行為,會消耗鉅額的 Throughput Unit
    因而導致其他操作的停頓或者要付給 Amazon 的錢變多。
    因此,如果一開始沒有好好設計 Schema,也忽略了 Primary Key 的重要性
    日後依靠大量的 Scan 去做 Read、Update 和 Delete 操作的話
    導致的結果就是要嘛 Application 常常被 Throughput 卡住而停頓、不然就是要支付昂貴的 Throughput 費用。

    (待續...)
    目前先想到了這兩點,如果之後再想到其他比較重要需要注意的行為再上來補...

    參考資料
    1. Java and DynamoDB
    2. DynamoDB Core Components - Primary Key
    3. Partitions and Data Distribution
    4. Provisioned Throughput

    沒有留言: