Cassandra
Minhas primeiras impressões depois de aprender sobre Cassandra
Vindo do SQL Server, as surpresas não foram sobre escala. Sem joins, desnormalizar de propósito, desenhar a primary key antes da tabela existir, e uma query language que parece SQL mas recusa coisas que o SQL permite.
Usei Cassandra pela primeira vez num URL shortener que construí para trabalhar em cima de um vídeo de system design. Tudo que eu tinha lido sobre ele antes falava de escala e distribuição. As coisas que de fato me surpreenderam usando ele foram muito mais básicas do que isso.
CQL parece SQL, o que é uma armadilha
A query language é parecida o suficiente com SQL que eu digitei minhas primeiras statements sem pensar:
var stmt = await _session.PrepareAsync(
"SELECT code, id, long_url, short_url, created_on_utc " +
"FROM shortened_urls WHERE code = ?");
Isso é só SQL até onde consigo ver lendo. A familiaridade me fez assumir que eu sabia o que estava fazendo, até eu tentar algo comum e ele recusar.
Não existem joins. Não é "joins são lentos, evite-os" do jeito que as pessoas falam sobre databases relacionais. Eles não existem. O mesmo vale para cláusulas WHERE arbitrárias: você não pode filtrar por qualquer coluna que seja conveniente, porque só é possível fazer query nas formas que a estrutura de key da tabela suporta. Vindo do SQL Server, onde qualquer coluna vale e o query planner resolve, isso pareceu menos uma limitação e mais o database estando quebrado.
Não está quebrado. Ele só não deixa você escrever uma query que não consegue responder eficientemente num cluster distribuído, o que é, sem dúvida, mais honesto do que deixar você escrever e ela ser silenciosamente terrível.
A primary key é uma decisão de design, não uma coluna de ID
Em SQL eu adiciono uma coluna Id principalmente por hábito e penso na query depois. No Cassandra a key determina em qual node o dado vive, e portanto o que você consegue buscar eficientemente.
Minha tabela acabou o mais simples possível:
CREATE TABLE IF NOT EXISTS url_shortener.shortened_urls
(code text PRIMARY KEY, id uuid, long_url text, short_url text, created_on_utc timestamp)
O short code é a primary key, não o uuid. Isso é o oposto do que eu teria feito instintivamente em SQL, onde o GUID seria a key e code receberia um índice. Mas toda leitura nessa aplicação é "me dê a linha para esse code", então o code tem que ser a key. O uuid é só um dado que por acaso está ali.
Acertar essa decisão foi fácil aqui porque o padrão de acesso é trivial. Em algo com mais de um formato de query, eu teria tido que pensar bem mais a fundo com antecedência, e suspeito que é aí que o Cassandra fica de fato difícil.
Você desnormaliza de propósito
O conselho que eu ficava encontrando é que, se você precisa do mesmo dado disponível por duas keys diferentes, você o guarda duas vezes, em duas tabelas, cada uma com a key da forma que aquela query precisa.
Todo instinto que construí diz que duplicar dados é como as coisas dão errado, porque agora existem duas cópias que podem discordar. No Cassandra esse é o design esperado, e manter as cópias sincronizadas é trabalho da aplicação, não do database. Eu entendo por quê, já que não existem joins para remontar as coisas entre nodes, mas ainda pareceu errado de digitar.
O setup envolve keyspaces, e replication é um número que você escolhe
Antes de criar uma tabela, existe um keyspace, que é mais ou menos um database, e ele quer saber quantas cópias do dado manter:
session.Execute(
"CREATE KEYSPACE IF NOT EXISTS url_shortener " +
"WITH replication = {'class':'SimpleStrategy','replication_factor':1}");
Um replication factor de um significa uma única cópia, o que é razoável no meu laptop e seria uma má ideia em produção, onde você ia querer o dado em vários nodes para que perder um não perca dado.
O que me impactou é que essa é uma decisão que eu tive que tomar na primeira linha do setup. No SQL Server, quantas cópias do meu dado existem é uma preocupação de infraestrutura que eu nunca toco no código da aplicação. Aqui é parte do schema.
Foi a escolha certa para esse projeto?
Para aprender, sim. Para o projeto por seus próprios méritos, honestamente não. Um URL shortener com o meu tráfego seria perfeitamente atendido por SQLite. Cassandra foi feito para uma escala que eu não tenho, e todo o motivo de eu usá-lo foi trabalhar com o database que combina com a versão real do problema em vez da versão que eu de fato tinha.
O que eu tirei disso
A mudança útil não foi aprender Cassandra especificamente. Foi perceber que num database relacional eu desenho o dado e descubro as queries depois, e aqui eu desenho as queries e o layout do dado segue. Essa é uma forma genuinamente diferente de começar, e explica a maioria das restrições que pareciam arbitrárias no início.