Schema

允許過濾與不過濾;Cassandra 數據模型問題

  • August 26, 2017

我在家裡的一些 RaspberryPi 上執行了一個玩具 Cassandra 集群。我目前正在將 CryptoCoin 數據記錄到其中,希望能更多地了解 Cassandra 以及沿途的其他一些事情。

我今天的問題是確定我是否在這張表上正確地建構了我的模式。

該表沒有很多欄位,主鍵是名稱欄位和時間戳欄位。我想從所有硬幣中查詢最後 N 小時的數據(每分鐘記錄一次數據)。如果我使用簡單的 WHERE 子句,我會收到“ALLOW FILTERING”警告。我理解它為什麼會發生,但我正在努力理解正確的前進道路以確保可擴展的解決方案。現在該表只有大約 320k 條記錄,我可以毫無問題地使用 ALLOW FILTERING,但我意識到這可能並非總是如此。

我設置了一個測試來查看執行兩種不同的查詢方法需要多長時間。ALLOW FILTERING 方法目前是最快的,但它可能會保持這種狀態嗎?這就是我缺乏知識的地方。

我有一個想法添加另一個欄位,即星期幾,也可能是一個月欄位。想法是這可能允許在查詢中進行更多過濾,因此我不必像下面那樣遍歷所有硬幣,但我不知道這是否是個好主意。如果我這樣做,我是否將它們設為 PrimaryKey?認為這是我與 Cassandra 最混淆的地方,但並非完全如此;也許只是不夠自信。

CQL表說明:

CREATE TABLE cryptocoindb.worldcoinindex (
   name text,
   timestamp int,
   label text,
   price_btc double,
   price_cny double,
   price_eur double,
   price_gbp double,
   price_rur double,
   price_usd double,
   volume_24h double,
   PRIMARY KEY (name, timestamp)
) WITH CLUSTERING ORDER BY (timestamp ASC)
   AND bloom_filter_fp_chance = 0.01
   AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'}
   AND comment = ''
   AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'}
   AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'}
   AND crc_check_chance = 1.0
   AND dclocal_read_repair_chance = 0.1
   AND default_time_to_live = 0
   AND gc_grace_seconds = 864000
   AND max_index_interval = 2048
   AND memtable_flush_period_in_ms = 0
   AND min_index_interval = 128
   AND read_repair_chance = 0.0
   AND speculative_retry = '99PERCENTILE';

Python中的程式碼:

# First method using ALLOW FILTERING:
startTime = time.time()
oneDaySec = 60*60*24
prior24hr = int(time.time()-oneDaySec)

query = "SELECT * FROM {}.{} WHERE timestamp > {} ALLOW FILTERING;".format(CASSANDRA_DB, CASSANDRA_TABLE, prior24hr)

rslt = session.execute(query, timeout=None)
worldcoinindex = rslt._current_rows
elapseTime = time.time()-startTime

print("Elapsed Time for this method: {}".format(elapseTime))

> > 此方法經過的時間:0.6223547458648682 > > >

# Second method using multiple queries...

startTime = time.time()

# I get the unique coin names here.
qryGetCoinList = "SELECT DISTINCT name FROM {}.{};".format(CASSANDRA_DB, CASSANDRA_TABLE)
rslt = session.execute(qryGetCoinList, timeout=None)
rsltGetCoinList = rslt._current_rows
rsltGetCoinList = rsltGetCoinList.name.tolist()

oneDaySec = 60*60*24
prior24hr = int(time.time()-oneDaySec)

# This iterates over the unique coin names and queries 
# the last 24 hrs worth of data per coin.
# NOTE: There are 518 unique coins.  

rsltTodayPrices = pd.DataFrame()
for coin in rsltGetCoinList:

   qryTodayPrices = """
                   SELECT * FROM {}.{} 
                   WHERE name = '{}' AND timestamp > {};
                   """.format(CASSANDRA_DB, 
                              CASSANDRA_TABLE, 
                              coin, 
                              prior24hr)
   rslt = session.execute(qryTodayPrices, timeout=None)
   TodayPrices = rslt._current_rows
   rsltTodayPrices.append(TodayPrices)

elapseTime = time.time()-startTime
print("Elapsed Time for this method: {}".format(elapseTime))

> > 此方法經過的時間:1.4576539993286133 > > >

謝謝!

現在該表只有大約 320k 條記錄,我可以毫無問題地使用 ALLOW FILTERING,但我意識到這可能並非總是如此。

所以事情是這樣的:Cassandra非常擅長通過特定鍵查詢數據。它還擅長檢索分區內的一系列數據。

"SELECT * FROM {}.{} WHERE timestamp > {} ALLOW FILTERING;"

但是由於它的分佈式特性,它並不擅長掃描整個表來編譯結果集。這就是您要求它對上述查詢進行的操作。

網路流量很昂貴。因此,Cassandra 的主要目標是確保您的查詢由單個節點提供服務。在ALLOW FILTERING不指定分區鍵(名稱)的情況下使用時,您的查詢需要一個協調節點,並檢查集群中的每個節點是否有可能與您的 WHERE 子句匹配的值。

本質上,集群中的節點越多,對ALLOW FILTERING性能的不利影響就越大(除非您至少指定分區鍵……只有這樣才能保證您的查詢可以由單個節點提供服務)。請注意,您較慢的查詢實際上做到了這一點,並為您解決了這個問題。

我有一個想法添加另一個欄位,即星期幾,也可能是一個月欄位。

這是個好主意!

它解決了兩個問題。

  1. 它確保您的查詢將由單個節點提供服務。
  2. 它可以保護您的分區不會變得太大。

Cassandra 每個分區有 20 億個單元的限制。由於您的分區鍵是“名稱”並且您不斷在其中添加唯一的時間戳,因此您將朝著該限制前進,直到達到該限制,或者您的分區變得太大而無法使用(可能是後者)。

以下是我將如何解決這個問題:

CREATE TABLE cryptocoindb.worldcoinindex_byday (
   daybucket text,
   name text,
   datetime timestamp,
   label text,
   price_btc double,
   price_cny double,
   price_eur double,
   price_gbp double,
   price_rur double,
   price_usd double,
   volume_24h double,
   PRIMARY KEY (daybucket, datetime, name)
) WITH CLUSTERING ORDER BY (datetime DESC, name ASC);

現在你可以這樣查詢:

SELECT * FROM cryptocoindb.worldcoinindex
WHERE daybucket='20170825' AND datetime > '2017-08-25 17:20';

此外,通過按“日期時間”降序對行進行分群,您可以確保最新數據位於每個單元格的頂部(使 Cassandra 無需解析)。

我將“名稱”移動到最後一個分群列,只是為了保持唯一性。如果您永遠不會按“名稱”進行查詢,那麼將其用作分區鍵是沒有意義的。

希望這可以幫助。

注意:我將您的更改timestamp int為,datetime timestamp因為它增加了範例的清晰度。您可以使用任何適合您的方式,但請注意以數據類型命名列所引起的混淆。

編輯 20170826

以下程式碼與您的程式碼相同還是不同?PRIMARY KEY ((daybucket, datetime), name)

不,那不一樣。那是使用一種稱為複合分區鍵的東西。它會在集群中為您提供更好的數據分佈,但會使您的查詢更加困難,並且基本上會讓您重新進行表掃描。

對於 Cassandra 主鍵的良好、全面的描述,Carlo Bertuccini在 StackOverflow 上有很好的回答:

https://stackoverflow.com/questions/24949676/difference-between-partition-key-composite-key-and-clustering-key-in-cassandra/24953331#24953331

有沒有辦法改變 Cassandra 讀取時間戳的方式,或者有一種簡單的方法來更改整個數據欄位以改變時間戳,以便正確讀取它?

並不真地。Cassandra 時間戳可能很難使用。它們以毫秒精度儲存,但在查詢時實際上並沒有顯示完整的精度。此外,作為 2.1 更新檔之一,它會自動以 GMT 顯示時間;所以這也會讓人們感到困惑。如果您在應用程序端管理時間戳的方式適合您,請堅持下去。

引用自:https://dba.stackexchange.com/questions/184127