我在家里的一些 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
谢谢!
所以事情是这样的:Cassandra非常擅长通过特定键查询数据。它还擅长检索分区内的一系列数据。
但是由于它的分布式特性,它并不擅长扫描整个表来编译结果集。这就是您要求它对上述查询进行的操作。
网络流量很昂贵。因此,Cassandra 的主要目标是确保您的查询由单个节点提供服务。在
ALLOW FILTERING
不指定分区键(名称)的情况下使用时,您的查询需要一个协调节点,并检查集群中的每个节点是否有可能与您的 WHERE 子句匹配的值。本质上,集群中的节点越多,对
ALLOW FILTERING
性能的不利影响就越大(除非您至少指定分区键......只有这样才能保证您的查询可以由单个节点提供服务)。请注意,您较慢的查询实际上做到了这一点,并为您解决了这个问题。这是个好主意!
它解决了两个问题。
Cassandra 每个分区有 20 亿个单元的限制。由于您的分区键是“名称”并且您不断在其中添加唯一的时间戳,因此您将朝着该限制前进,直到达到该限制,或者您的分区变得太大而无法使用(可能是后者)。
以下是我将如何解决这个问题:
现在你可以这样查询:
此外,通过按“日期时间”降序对行进行聚类,您可以确保最新数据位于每个单元格的顶部(使 Cassandra 无需解析)。
我将“名称”移动到最后一个聚类列,只是为了保持唯一性。如果您永远不会按“名称”进行查询,那么将其用作分区键是没有意义的。
希望这可以帮助。
注意:我将您的更改
timestamp int
为,datetime timestamp
因为它增加了示例的清晰度。您可以使用任何适合您的方式,但请注意以数据类型命名列所引起的混淆。编辑 20170826
不,那不一样。那是使用一种称为复合分区键的东西。它会在集群中为您提供更好的数据分布,但会使您的查询更加困难,并且基本上会让您重新进行表扫描。
对于 Cassandra 主键的良好、全面的描述,Carlo Bertuccini在 StackOverflow 上有很好的回答:
https://stackoverflow.com/questions/24949676/difference-between-partition-key-composite-key-and-clustering-key-in-cassandra/24953331#24953331
并不真地。Cassandra 时间戳可能很难使用。它们以毫秒精度存储,但在查询时实际上并没有显示完整的精度。此外,作为 2.1 补丁之一,它会自动以 GMT 显示时间;所以这也会让人们感到困惑。如果您在应用程序端管理时间戳的方式适合您,请坚持下去。