Bancos de dados¶
O kiln guarda jobs em um banco de dados através de um store. Escolha o que corresponde ao banco de dados que você já usa; trocar depois significa mudar a linha que constrói o store, não o código que enfileira ou processa os jobs.
| Backend | Pacote | Notas |
|---|---|---|
| PostgreSQL (CI na 17) | pgstore |
avisos via LISTEN/NOTIFY, pgx v5, opção de schema |
| SQL Server 2019+ e Azure SQL (CI na 2022) | mssqlstore |
qualquer *sql.DB do go-mssqldb, opção de prefixo de tabela; precisa de READ_COMMITTED_SNAPSHOT; os servidores são avisados através de um Bus, ou fazem poll sem um |
| MySQL 8.0.19+ (CI na 8.4) | mysqlstore |
qualquer *sql.DB, opção de prefixo de tabela; os servidores são avisados através de um Bus, ou fazem poll sem um |
| SQLite 3.38+ | sqlitestore |
qualquer driver database/sql, WAL; servidores no mesmo processo são avisados direto, outros processos através de um Bus |
| em memória | memstore |
testes e ferramentas de um único processo; tudo se perde quando o processo termina |
Todo backend passa pela mesma suíte de conformidade, drivertest.Run, então a semântica (retentativas,
continuações, lotes, unicidade, limites, fencing) não muda quando o banco de dados muda. Escrever um
novo backend significa implementar driver.Store e fazer essa suíte passar; veja
Escrevendo um store.
| PostgreSQL | MySQL | SQL Server | SQLite | em memória | |
|---|---|---|---|---|---|
| Avisa os servidores | LISTEN/NOTIFY |
através de um Bus |
através de um Bus |
no próprio processo; outros através de um Bus |
no próprio processo |
| Enfileirar na sua transação | Tx(pgx.Tx), SQLTx(*sql.Tx) |
Tx(*sql.Tx) |
Tx(*sql.Tx) |
Tx(*sql.Tx) |
Begin() |
| Onde ficam suas tabelas | um schema (Schema) |
um prefixo de tabela (Prefix) |
um prefixo de tabela (Prefix) |
um prefixo de tabela (Prefix) |
memória |
| Console do job, página de Limites | sim | sim | sim | sim | sim |
O PostgreSQL avisa os servidores com LISTEN/NOTIFY. MySQL e SQL Server não têm nada equivalente, e o
SQLite só consegue avisar servidores no próprio processo, então os três aceitam um driver.Bus.
redisbus é um sobre Redis Pub/Sub:
rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379", ContextTimeoutEnabled: true})
store, err := mysqlstore.New(ctx, db, mysqlstore.Bus(redisbus.New(rdb)))
Com um bus, jobs novos, continuações liberadas e cancelamentos chegam a todos os servidores em
milissegundos. Sem um, os servidores encontram trabalho novo no próximo poll (PollInterval) e
verificam a cada 100ms se há jobs vencidos. Eventos são só dicas: um evento perdido atrasa um job até o
próximo poll, mas nunca perde o job.
Com mysqlstore, comece suas próprias transações com sql.LevelReadCommitted antes de passá-las para
store.Tx. Em REPEATABLE READ o InnoDB usa gap locks que podem fazer enfileiramentos concorrentes
esperarem pelo seu commit.
db, _ := sql.Open("mysql", "user:pass@tcp(localhost:3306)/app")
store, err := mysqlstore.New(ctx, db)
mssqlstore recebe um *sql.DB aberto com github.com/microsoft/go-mssqldb, e o banco de dados precisa
de row versioning para READ COMMITTED — o Azure SQL Database já vem com isso ativado:
ALTER DATABASE app SET READ_COMMITTED_SNAPSHOT ON
db, _ := sql.Open("sqlserver", "sqlserver://user:pass@localhost:1433?database=app")
store, err := mssqlstore.New(ctx, db)
Capturas, finalizações e admissões bloqueiam linhas com READPAST, então os servidores pulam o trabalho
um do outro em vez de esperar, e transações escolhidas como vítimas de deadlock recebem uma nova
tentativa. Nomes de fila, tipos e chaves de limite são comparados de forma case-sensitive, seja qual for
o collation do banco. Funciona em nível de compatibilidade 130 ou superior, e é testado no SQL Server
2022.
sqlitestore recebe um *sql.DB de qualquer driver SQLite para database/sql; seus testes e o CI usam
modernc.org/sqlite, que não precisa de cgo. O SQLite permite apenas um writer por vez, então o store
mantém uma conexão reservada do pool só para suas escritas e inicia toda transação de escrita com
BEGIN IMMEDIATE; leituras rodam em paralelo, em modo WAL.
db, _ := sql.Open("sqlite", "file:app.db?_pragma=busy_timeout(5000)&_pragma=synchronous(NORMAL)&_txlock=immediate")
store, err := sqlitestore.New(ctx, db)
busy_timeoutprecisa estar na DSN: ele é configurado por conexão, eNewrejeita um pool sem ele.Newmuda o arquivo para WAL. Bancos de dados em memória não conseguem usar WAL; usememstorepara esses casos.- Use
_txlock=immediatenas transações que você passa parastore.Tx, e mantenha elas curtas: enquanto uma está aberta, todo outro writer do arquivo espera, incluindo os servidores do kiln. - Deixe
SetMaxOpenConnsem 2 ou mais.