Eu tenho um cluster de brinquedo Cassandra rodando em alguns RaspberryPis em casa. Atualmente, estou registrando dados da CryptoCoin na esperança de aprender mais sobre Cassandra e outras coisas ao longo do caminho.
Minha dúvida aqui hoje é saber se estou estruturando meu esquema corretamente nesta tabela.
A tabela não possui muitos campos, as chaves primárias são o campo de nome e o campo de carimbo de data/hora. Quero consultar as últimas N horas de dados (os dados são registrados a cada minuto) de todas as moedas. Se eu usar uma cláusula WHERE simples, recebo o aviso 'ALLOW FILTERING'. Eu entendo por que isso acontece, mas estou lutando para entender o caminho correto a seguir para garantir uma solução escalável. No momento, a tabela tem apenas cerca de 320k registros e posso usar ALLOW FILTERING sem problemas, mas percebo que isso nem sempre pode ser o caso.
Eu configurei um teste para ver quanto tempo levou para executar dois métodos de consulta diferentes. O método ALLOW FILTERING atualmente é o mais rápido, mas é provável que continue assim? É aqui que eu sou deficiente em conhecimento.
Eu tive uma ideia de adicionar outro campo que seria o dia da semana, e talvez um campo do mês também. O pensamento era que isso poderia permitir mais filtragem em uma consulta para que eu não precisasse percorrer todas as moedas como estou fazendo abaixo, mas não sei se isso é uma boa ideia ou não. Se eu fizer isso, faço deles uma chave primária ou não? Acho que é aqui que estou mais confuso com Cassandra, mas não inteiramente; talvez apenas o suficiente para ser inseguro.
Descrição da Tabela 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';
Código em 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))
Tempo decorrido para este método: 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))
Tempo decorrido para este método: 1,4576539993286133
Obrigada!
Então é o seguinte: Cassandra é muito boa em consultar dados por uma chave específica. Também é bom para recuperar um intervalo de dados dentro de uma partição.
Mas devido à sua natureza distribuída, não é bom escanear uma tabela inteira para compilar um conjunto de resultados. E é isso que você está pedindo para fazer com a consulta acima.
O tráfego de rede é caro. Portanto, o principal objetivo com o Cassandra é garantir que sua consulta seja atendida por um único nó. Ao usar
ALLOW FILTERING
sem especificar sua chave de partição (nome) faz com que sua consulta exija um nó coordenador e verifique cada nó em seu cluster para valores que possam corresponder à sua cláusula WHERE.Essencialmente, quanto mais nós houver em seu cluster, mais prejudicial
ALLOW FILTERING
se tornará para o desempenho (a menos que você especifique pelo menos sua chave de partição... somente então você estará garantindo que sua consulta possa ser atendida por um único nó). Observe que sua consulta mais lenta realmente faz isso corretamente e resolve esse problema para você.E esta é uma boa ideia!
Resolve dois problemas.
Cassandra tem um limite de 2 bilhões de células por partição. Como sua chave de partição é "nome" e você continua adicionando carimbos de data e hora exclusivos dentro dela, você progredirá em direção a esse limite até alcançá-lo ou sua partição se tornar grande demais para usar (provavelmente o último).
Aqui está como eu resolveria isso:
Agora você pode consultar assim:
Além disso, ao agrupar suas linhas em "datetime" descendente, você garante que os dados mais recentes estejam no topo de cada célula (dando a Cassandra menos necessidade de analisar).
Mudei "name" para ser a última coluna de agrupamento, apenas para manter a exclusividade. Se você nunca vai consultar por "nome", não faz sentido usá-lo como sua chave de partição.
Espero que isto ajude.
Observação: alterei seu
timestamp int
paradatetime timestamp
porque acrescentou clareza ao exemplo. Você pode usar o que funcionar para você, mas tome cuidado com a confusão que surge ao nomear uma coluna após um tipo de dados.Editar 20170826
Não, isso não é o mesmo. Isso está usando algo chamado chave de partição composta. Isso lhe dará uma melhor distribuição de dados em seu cluster, mas tornará a consulta mais difícil para você e basicamente fará com que você volte a fazer varreduras de tabela.
Para uma boa e abrangente descrição das chaves primárias do Cassandra, Carlo Bertuccini tem uma ótima resposta no StackOverflow:
https://stackoverflow.com/questions/24949676/difference-between-partition-key-composite-key-and-clustering-key-in-cassandra/24953331#24953331
Na verdade, não. Os carimbos de data e hora do Cassandra podem ser complicados de trabalhar. Eles armazenam com precisão de milissegundos, mas na verdade não mostram essa precisão total quando consultados. Além disso, a partir de um dos patches 2.1, ele exibe automaticamente a hora em GMT; então isso pode ser confuso para as pessoas também. Se sua maneira de gerenciar carimbos de data/hora no lado do aplicativo estiver funcionando para você, continue com isso.