O driver é parte do contrato de performance. Session única, queries preparadas, paging explícito e modelagem conforme scylladb.
Stack e escopo
| Peça | Papel |
|---|---|
| github.com/scylladb/gocql | Driver (fork Scylla, token/shard-aware) |
| gocqlx | Mapping structs ↔ CQL, builders (se o projeto usar) |
| Session | Pool de conexões; compartilhada no processo |
| Repository | Encapsula CQL; handlers não montam query solta |
Preserve o driver já adotado. Em apps novos: scylladb/gocql (+ gocqlx quando mapping ajudar). Modelagem de tables: skill scylladb.
Session e config
cluster := gocql.NewCluster(hosts...)
cluster.Keyspace = "app"
cluster.Consistency = gocql.LocalQuorum
cluster.PoolConfig.HostSelectionPolicy = gocql.TokenAwareHostPolicy(
gocql.RoundRobinHostPolicy(),
)
cluster.Timeout = 2 * time.Second
cluster.ConnectTimeout = 5 * time.Second
session, err := cluster.CreateSession()
if err != nil {
return err
}
defer session.Close()
- Uma
Sessionpor processo/app; injete via deps — não abra session por request. - Token-aware (+ shard-aware quando suportado/configurado) para ir ao nó certo.
- Timeouts alinhados ao SLA; consistency por operação quando diferir do default.
- Auth/TLS via config do cluster em produção.
Prepared statements e queries
stmt := `SELECT message_id, author_id, body, sent_at
FROM messages_by_room
WHERE room_id = ? AND sent_at < ?
LIMIT ?`
iter := session.Query(stmt, roomID, before, limit).
WithContext(ctx).
PageSize(100).
Iter()
var row Message
for iter.Scan(&row.MessageID, &row.AuthorID, &row.Body, &row.SentAt) {
// ...
}
if err := iter.Close(); err != nil {
return err
}
- Sempre
WithContext/ cancelamento do request. - Prepared statements (reutilização automática do driver) para hot paths.
- Bind por posição ou gocqlx
BindStruct/GetRelease— evite concatenar CQL. PageSize+ paging state para listas; não carregue partições inteiras na memória.
Writes e idempotência
err := session.Query(
`INSERT INTO messages_by_room (room_id, sent_at, message_id, author_id, body)
VALUES (?, ?, ?, ?, ?)`,
roomID, sentAt, messageID, authorID, body,
).WithContext(ctx).Exec()
- Retries só com política clara; writes devem ser idempotentes ou usar IDs estáveis.
- LWT (
ScanCAS/MapScanCAS) só quando o domínio exigir compare-and-set. - Batch: prefira single-partition; batches multi-partition não são atômicos como “transação SQL”.
- TTL:
USING TTL ?quando o modelo pedir expiração.
Organização
internal/
scylla/
session.go # cluster/session wiring
messages.go # repository por tabela/access pattern
domain/
- Um arquivo/repo por access pattern ou agregado — nomes alinhados à table.
- Não exponha
*gocql.Sessionaos handlers HTTP; exponha métodos de domínio. - Erros do driver → erros de domínio (
not found, timeout, unavailable).
Observabilidade e erros
- Distinga timeout, unavailable, overloaded; mapeie para
503/504na API quando couber. - Métricas: latência p99 por query/table, timeouts, retries.
- Tracing do driver só em debug; não logue payloads sensíveis.
- Testes: Testcontainers/Scylla local ou cluster de CI; fakes do repositório nos unit tests (
golang-unit-testing).
Anti-padrões
CreateSessionpor request ou por handler- CQL montado com
fmt.Sprintfe input de usuário ALLOW FILTERINGescondido no repositório- Ignorar
context/ paging e fazer full partition scan - Consistency
Allcomo default “seguro” - Tratar batch multi-partition como transação ACID
- Modelar no Go sem table query-first (ver
scylladb)
Fluxo ao implementar uma feature
- Defina access pattern e table CQL (
scylladb). - Adicione método no repository com statement preparado + context.
- Escolha consistency/TTL/paging conscientes.
- Trate erros e timeouts na borda HTTP/service.
- Teste com Scylla real ou fake do port; meça p99 se for hot path.
Critérios de conclusão
- Session singleton token-aware configurada
- Queries com partition key e context
- Paging/limites em listas
- Retries/idempotência pensados nos writes
- Repositório encapsula o driver
- Erros de cluster mapeados para o domínio/API