搜索DynamoDB:Elasticsearch的索引器邊車

TL; DR
DynamoDB很棒,但分區和搜索很難
咱們創建了交流發電機和遷移服務,使生活更輕鬆
咱們開放了一個邊車來索引Elasticsearch中你應該使用的DynamoDB表。 這是代碼。
當咱們三年多前開始使用Bitbucket Pipelines時,咱們幾乎沒有使用NoSQL數據庫的經驗。但做爲一個但願快速提升質量的小團隊,咱們決定將DynamoDB做爲具備出色可用性和可擴展性特徵的託管服務。三年過去了,咱們已經學到了不少關於如何使用和如何不使用DynamoDB的知識,而且咱們已經構建了一些可能對其餘團隊有用的東西或者能夠被不斷增加的平臺吸取的東西。算法

NoSQL仍是NoSQL?就是那個問題
首先,關係數據庫不是狼人,NoSQL不是銀彈。關係數據庫多年來一直服務於大規模應用程序,而且它們的擴展範圍遠遠超出許多人的預期。例如,Atlassian的許多團隊繼續選擇Postgres而不是DynamoDB,而且有不少徹底正確的理由這樣作。但願本博客將重點介紹選擇一種技術而不是另外一種技術的一些緣由。從較高層面來看,它們包括諸如操做開銷,表的預期大小,數據訪問模式,數據一致性和查詢要求等考慮因素。數據庫

分區,分區,分區
很好地理解 分區如何工做 多是使用DynamoDB成功的最重要的事情,而且必須避免可怕的 熱分區問題。出現這種錯誤可能意味着重組數據,從新設計API,全表遷移,或者在系統達到臨界閾值時的某個時間更糟糕。固然,對錶的分區沒有可見性; 你能夠根據表的吞吐量和大小來計算它們,但它是不許確的,繁瑣的,並且咱們發現,若是你設計了分佈良好的密鑰做爲最佳實踐開發人員指南,那麼這在很大程度上是沒必要要的 提示。幸運的是,咱們花時間瞭解get get的分區,並設法避免任何這些問題。做爲額外的好處,咱們如今可以利用自動擴展而無需考慮分區邊界,由於即便分區發生變化而且吞吐量在它們之間從新分配,請求仍保持均勻分佈。架構

吞吐量,爆破和節流
DynamoDB表的讀取和寫入受到 爲表配置的吞吐量容量的限制 。吞吐量還決定了表的分區方式,它會影響成本,所以值得確保您不會過分配置。DynamoDB容許在開始限制請求以前在短期內突破吞吐量限制,而且雖然受限制的請求可能致使應用程序中的操做失敗,但咱們發現因爲默認重試配置,它不多會這樣作適用於DynamoDB的 AWS開發工具包。這尤爲讓人放心,由於 DynamoDB中的自動擴展會因設計而延遲,而且容許吞吐量超過容量,足以發生限制。ide

僅當實際工做負載持續升高(或抑制)持續幾分鐘時,DynamoDB自動縮放纔會修改預配置的吞吐量設置。Application Auto Scaling目標跟蹤算法旨在將目標利用率保持在您選擇的值或接近您選擇的值。工具

桌子的內置爆破容量能夠緩解忽然的短暫的活動高峯。性能

您仍然但願在超出吞吐量時設置警報,以便您能夠在必要時進行相應的監視和操做(例如,自動擴展有一個上限),但咱們發現突發容量,默認SDK重試,自動擴展和 自適應吞吐量 很是有效地結合,所以不多須要干預。開發工具

圖片標題測試

圖像來源ui

交替器:DynamoDB的對象項映射庫
在Pipelines得到綠燈並開始重構可怕的alpha代碼以後,咱們構建的第一件事就是交流發電機:DynamoDB的內部對象項映射庫(相似於ORM)。Alternator從應用程序中抽象出AWS SDK,並提供基於註釋,被動(RxJava - 儘管目前仍在使用阻止AWS API,直到SDK的v2變得穩定)接口,用於與DynamoDB交互。它還經過Hystrix增長了斷路功能,從而消除了早期版本系統中存在的大部分樣板代碼。搜索引擎

@Table( name = "pipeline", primaryKey = @PrimaryKey(hash = @HashKey(name = "uuid")) ) @ItemConverter(PipelineItemConverter.class) public interface PipelineDao { @PutOperation(conditionExpression = "attribute_not_exists(#uuid)") Single<Pipeline> create(@Item Pipeline pipeline); }

移民局
固然,DynamoDB表是無架構的。可是,這並不意味着您不須要執行遷移。除了在表中添加或更改屬性的典型數據遷移以外,還有許多功能只能在首次建立表時配置,例如本地二級索引,這對於查詢和排序除了之外的其餘屬性很是有用。首要的關鍵。

管道中的前幾回遷移涉及編寫定製代碼以將大量數據移動到新表並將其與應用程序中常常複雜的更改同步,以支持新舊錶以免停機。咱們很早就瞭解到,遷移策略能夠消除大量的摩擦,所以遷移服務就誕生了。

遷移服務是咱們爲在DynamoDB表中遷移數據而開發的內部服務。它支持兩種類型的遷移:

用於在現有表中添加或修改數據的相同表遷移。
用於將數據移動到新表的表到表的遷移。
遷移的工做方式是掃描源表中的全部數據,將其傳遞給變換器(特定於發生遷移)並將其寫入目標表。它使用 並行掃描 在表的分區中均勻分佈負載,並在最短的時間內最大化地完成遷移,從而實現此目的。

圖片標題

而後,表到表的遷移將附加到源表的 流, 以使目標表保持同步,直到您決定切換到使用它。這容許應用程序直接切換到使用新表,而無需在遷移期間同時支持新舊錶。

查詢DynamoDB:一個心痛和痛苦的故事
DynamoDB經過本地和全局二級索引提供有限的查詢和排序功能 。表限制爲五個本地二級索引,每一個索引與表的主鍵共享相同的分區鍵,而且一次只能對一個索引執行查詢操做。這意味着在經過電子郵件地址分區的「用戶」表上,查詢操做只能在電子郵件地址和另外一個值的上下文中執行。

全局二級索引以支付第二批吞吐量爲代價來移除分區密鑰要求,而且僅支持最終一致的讀取。兩種類型的索引對於許多用例都是有用且充分的,而且管道繼續普遍使用它們,但它們不知足某些應用程序的更復雜的查詢要求。在Pipelines中,這種需求主要來自咱們的REST API,它一般容許客戶端同時對多個屬性進行過濾和排序。

咱們的方案
咱們第一次嘗試解決這個問題,MultiQuery,已經內置到交流發電機中。經過這種方法,咱們查詢了多個本地二級索引(LSI)並將結果聚合在內存中,容許咱們在單個API請求中對最多五個值(最大LSI數)進行過濾和排序。雖然這在當時起做用,但隨着咱們的表的大小和指數增加,它開始遭受至關可怕的性能降低,所以您過濾的值越多。在替換多個查詢以前,咱們的端到端測試存儲庫(具備大量管道的存儲庫)上的管道列表頁面須要花費幾十秒的時間來響應並在分支上進行過濾時始終超時。

搜索DynamoDB內容的常見模式是在搜索引擎中對其進行索引。方便的是,AWS提供了一個 logstash插件, 用於在Elasticsearch中索引Dynamo表,所以咱們開始使用此插件建立索引服務,結果使人鼓舞。查詢性能大大提升了預期,但logstash插件須要花費大約11個小時來索引700,000個文檔。

對logstash插件的一些分析以及咱們已經在遷移服務中構建了本質上是高性能索引器的實現使咱們用自定義索引器實現替換了logstash插件。咱們的索引器主要基於遷移服務的相同掃描/流語義,並利用Elasticsearch的批量索引API,在27分鐘內成功掃描了近700萬個文檔。

用於dynamodb的邊車索引器圖像來源
Indexer Sidecar
自定義索引器後來被從新打包爲sidecar,容許任何服務應用程序無縫索引Elasticsearch中的DynamoDB表。初始掃描和正在進行的流式傳輸階段都是高度可用的,而且能夠經過租用/檢查點機制(爲掃描定製,爲流的標準Kinesis客戶端)進行恢復。

圖片標題

咱們目前利用 Bitbucket代碼搜索團隊構建的優秀 Elasticsearch客戶端來查詢索引,並開始研究內部庫,它與交流發電機同樣添加RxJava和Hystrix。

這是包含代碼和自述文件的repo 。

最後的想法若是您之前沒有使用過NoSQL,那確定須要轉變思惟,但總的來講,咱們使用DynamoDB的經驗是積極的。事實證實,該平臺在過去三年中很是可靠(我記不起由此形成的一次重大事故),咱們的查詢要求帶來了最大的挑戰。有些人可能會說,首先選擇關係數據庫是合理的,我不會強烈反對它們。但咱們已經設法經過一個解決方案克服了這個問題,這個解決方案大部分都是從平常運營中抽象出來的。從好的方面來講,咱們沒必要在全部時間運行解釋查詢或處理格式錯誤的SQL,複雜的錶鏈接或缺乏索引,咱們並不急於回去。

相關文章
相關標籤/搜索