簡體   English   中英

與MongoDB相比,使用Java Driver的Cassandra Bulk-Write性能非常糟糕

[英]Cassandra Bulk-Write performance with Java Driver is atrocious compared to MongoDB

我為MongoDB和Cassandra構建了一個導入器。 基本上導入器的所有操作都是相同的,除了最后一部分形成數據以匹配所需的cassandra表模式和想要的mongodb文檔結構。 與MongoDB相比,Cassandra的寫入性能非常差,我認為我做錯了。

基本上,我的抽象導入器類加載數據,讀出所有數據並將其傳遞給擴展的MongoDBImporter或CassandraImporter類以將數據發送到數據庫。 一次針對一個數據庫 - 同時沒有“雙重”插入C *和MongoDB。 導入器在相同數量的節點上運行在同一台機器上(6)。


問題:

MongoDB導入在57分鍾后完成。 我攝取了10.000.000個文檔,我希望Cassandra的行數相同。 我的Cassandra導入器現在運行2.5小時,並且只插入了5.000.000行。 我將等待進口商完成並在此處編輯實際完成時間。


我如何用Cassandra導入:

我准備一旦攝取數據前兩個語句。 這兩個語句都是UPDATE查詢,因為有時我必須將數據附加到現有列表。 在開始導入之前,我的表格已完全清除。 准備好的陳述一次又一次地被使用。

PreparedStatement statementA = session.prepare(queryA);
PreparedStatement statementB = session.prepare(queryB);

對於每一行,我創建一個BoundStatement並將該語句傳遞給我的“自定義”批處理方法:

    BoundStatement bs = new BoundStatement(preparedStatement); //either statementA or B
    bs = bs.bind();

    //add data... with several bs.setXXX(..) calls

    cassandraConnection.executeBatch(bs);

使用MongoDB,我可以一次插入1000個文檔(這是最大的)沒有問題。 對於Cassandra,導入程序與com.datastax.driver.core.exceptions.InvalidQueryException: Batch too large崩潰com.datastax.driver.core.exceptions.InvalidQueryException: Batch too large ,在某些時候只有10個我的語句。 我正在使用此代碼來構建批次。 順便說一句,我以1000,500,300,200,100,50,20批量開始,但顯然它們也不起作用。 然后我將其設置為10並再次拋出異常。 現在我已經沒有想法為什么它會破裂。

private static final int MAX_BATCH_SIZE = 10;

private Session session;
private BatchStatement currentBatch;

...

@Override
public ResultSet executeBatch(Statement statement) {
    if (session == null) {
        throw new IllegalStateException(CONNECTION_STATE_EXCEPTION);
    }

    if (currentBatch == null) {
        currentBatch = new BatchStatement(Type.UNLOGGED);
    }

    currentBatch.add(statement);
    if (currentBatch.size() == MAX_BATCH_SIZE) {
        ResultSet result = session.execute(currentBatch);
        currentBatch = new BatchStatement(Type.UNLOGGED);
        return result;
    }

    return null;
}

我的C *架構看起來像這樣

CREATE TYPE stream.event (
    data_dbl frozen<map<text, double>>,
    data_str frozen<map<text, text>>,
    data_bool frozen<map<text, boolean>>,
);

CREATE TABLE stream.data (
    log_creator text,
    date text, //date of the timestamp
    ts timestamp,
    log_id text, //some id
    hour int, //just the hour of the timestmap
    x double,
    y double,
    events list<frozen<event>>,
    PRIMARY KEY ((log_creator, date, hour), ts, log_id)
) WITH CLUSTERING ORDER BY (ts ASC, log_id ASC)

我有時需要在現有行中添加更多新事件。 這就是我需要一個UDT列表的原因。 我的UDT包含三個映射,因為事件創建者生成不同的數據(string / double / boolean類型的鍵/值對)。 我知道UDT已被凍結,我無法觸及已經攝取事件的地圖。 這對我來說很好,我只需要添加有時候具有相同時間戳的新事件。 我在日志的創建者(一些傳感器名稱)以及記錄的日期(即“22-09-2016”)和時間戳的小時上進行分區(以便在保持相關數據靠近的同時更多地分發數據)一個分區)。


我在我的pom中使用Cassandra 3.0.8和Datastax Java Driver 3.1.0版。 根據Cassandra的批量限制是多少? ,我不應該通過調整我的cassandra.yaml batch_size_fail_threshold_in_kb來增加批量大小。 那么......我的導入做了什么或出了什么問題?


更新所以我調整了我的代碼來運行異步查詢並將當前運行的插入存儲在列表中。 每當異步插入完成時,它將從列表中刪除。 當列表大小超過閾值並且之前插入中發生錯誤時,該方法將等待500毫秒,直到插入低於閾值。 我的代碼現在在沒有插入失敗時自動增加閾值。

但是在流式傳輸3.300.000行之后,正在處理280.000個插入但沒有發生錯誤。 這似乎當前處理的插入數量看起來太高。 6個cassandra節點在商用硬件上運行,該硬件已有2年歷史。

這是並發插入的高數字(6個節點280.000)有問題嗎? 我應該添加一個像MAX_CONCURRENT_INSERT_LIMIT這樣的變量嗎?

private List<ResultSetFuture> runningInsertList;
private static int concurrentInsertLimit = 1000;
private static int concurrentInsertSleepTime = 500;
...

@Override
public void executeBatch(Statement statement) throws InterruptedException {
    if (this.runningInsertList == null) {
        this.runningInsertList = new ArrayList<>();
    }

    //Sleep while the currently processing number of inserts is too high
    while (concurrentInsertErrorOccured && runningInsertList.size() > concurrentInsertLimit) {
        Thread.sleep(concurrentInsertSleepTime);
    }

    ResultSetFuture future = this.executeAsync(statement);
    this.runningInsertList.add(future);

    Futures.addCallback(future, new FutureCallback<ResultSet>() {
        @Override
        public void onSuccess(ResultSet result) {
            runningInsertList.remove(future);
        }

        @Override
        public void onFailure(Throwable t) {
            concurrentInsertErrorOccured = true;
        }
    }, MoreExecutors.sameThreadExecutor());

    if (!concurrentInsertErrorOccured && runningInsertList.size() > concurrentInsertLimit) {
        concurrentInsertLimit += 2000;
        LOGGER.info(String.format("New concurrent insert limit is %d", concurrentInsertLimit));
    }

    return;
}

在使用C *之后,我確信你應該真正使用批處理來保持多個表同步。 如果您不需要該功能 ,則根本不要使用批次,因為這導致性能損失。

將數據加載到C *的正確方法是使用異步寫入,如果您的群集無法跟上攝取率,則可選擇背壓。 您應該使用以下內容替換“自定義”批處理方法:

  • 執行異步寫入
  • 掌控你有多少機上寫作
  • 寫入超時時執行一些重試。

要執行異步寫入,請使用.executeAsync方法,該方法將返回ResultSetFuture對象。

要控制多少飛行查詢只收集從列表中的.executeAsync方法檢索到的ResultSetFuture對象,如果列表獲得(此處為球場值),則說出1k元素,然后在發出更多寫入之前等待所有這些元素完成。 或者你可以在發出一次寫入之前等待第一次完成,只是為了保持列表滿。

最后,您可以在等待操作完成時檢查寫入失敗。 在這種情況下,您可以:

  1. 再次使用相同的超時值寫入
  2. 使用增加的超時值再次寫入
  3. 等待一段時間,然后再次使用相同的超時值寫入
  4. 等待一段時間,然后再次使用增加的超時值寫入

從1到4,您的背壓強度會增加。 選擇最適合您的情況。


問題更新后編輯

您的插入邏輯對我來說似乎有些不妥:

  1. 我沒有看到任何重試邏輯
  2. 如果失敗,則不會刪除列表中的項目
  3. 你的while (concurrentInsertErrorOccured && runningInsertList.size() > concurrentInsertLimit)是錯誤的,因為只有當發出的查詢數是> concurrentInsertLimit時才會休眠,並且因為2.你的線程只會停在那里。
  4. 您永遠不會設置為false concurrentInsertErrorOccured

我通常會保留一個(失敗的)查詢列表,以便以后重試它們。 這讓我對查詢有了強大的控制權,當失敗的查詢開始累積時,我會睡一會兒,然后繼續重試(最多X次,然后很難失敗......)。

此列表應該非常動態,例如,您在查詢失敗時添加項目,並在執行重試時刪除項目。 現在您可以了解群集的限制,並根據例如最后一秒中失敗查詢的平均數量來調整concurrentInsertLimit ,或者堅持使用更簡單的方法“ 如果我們在重試列表中有項目則暫停 ”等等...


評論后編輯2

由於您不需要任何重試邏輯,我會以這種方式更改您的代碼:

private List<ResultSetFuture> runningInsertList;
private static int concurrentInsertLimit = 1000;
private static int concurrentInsertSleepTime = 500;
...

@Override
public void executeBatch(Statement statement) throws InterruptedException {
    if (this.runningInsertList == null) {
        this.runningInsertList = new ArrayList<>();
    }

    ResultSetFuture future = this.executeAsync(statement);
    this.runningInsertList.add(future);

    Futures.addCallback(future, new FutureCallback<ResultSet>() {
        @Override
        public void onSuccess(ResultSet result) {
            runningInsertList.remove(future);
        }

        @Override
        public void onFailure(Throwable t) {
            runningInsertList.remove(future);
            concurrentInsertErrorOccured = true;
        }
    }, MoreExecutors.sameThreadExecutor());

    //Sleep while the currently processing number of inserts is too high
    while (runningInsertList.size() >= concurrentInsertLimit) {
        Thread.sleep(concurrentInsertSleepTime);
    }

    if (!concurrentInsertErrorOccured) {
        // Increase your ingestion rate if no query failed so far
        concurrentInsertLimit += 10;
    } else {
        // Decrease your ingestion rate because at least one query failed
        concurrentInsertErrorOccured = false;
        concurrentInsertLimit = Max(1, concurrentInsertLimit - 50);
        while (runningInsertList.size() >= concurrentInsertLimit) {
            Thread.sleep(concurrentInsertSleepTime);
        }
    }

    return;
}

您還可以通過用計數器替換List<ResultSetFuture>來優化一些過程。

希望有所幫助。

在Cassandra中運行批處理時,它會選擇一個節點作為協調器。 然后,該節點負責查看批量寫入找到其適當的節點。 因此(例如)通過將10000個寫入一起批處理,您現在已經為一個節點執行了協調10000次寫入的任務,其中大多數將用於不同的節點。 通過執行此操作,可以非常輕松地提示節點,或者消除整個群集的延遲。 因此,批量大小限制的原因。

問題是Cassandra CQL BATCH是用詞不當,它不會做你或別人認為它做的事情。 它不能用於提高性能。 並行異步寫入總是比在一起運行相同數量的語句更快。

我知道我可以輕松地將10.000行一起批量處理,因為它們會轉到同一個分區。 ...你還會使用單行插入(異步)而不是批次嗎?

這取決於寫性能是否是您的真正目標。 如果是這樣,那么我仍然堅持使用並行,異步寫入。

有關這方面的更多信息,請查看DataStax的Ryan Svihla撰寫的這兩篇博文:

Cassandra:沒有Batch關鍵字的批量加載

Cassandra:無批量批量加載 - Nuanced Edition

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM