-
Notifications
You must be signed in to change notification settings - Fork 411
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Migração para criar novas tabelas de saldo #1656
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Conforme discutido em #1491 (comment), separei o
balance_operations
em três tabelas distintas: uma para as operações de TabCoins de conteúdos, outra para as de TabCoins de usuários e outra para as de TabCash.
Show @Rafatcb, mas junto disso você trouxe outras refatorações, que são interessantes, mas que acho melhor fazer separadamente (e avaliando se realmente são necessárias).
Pelo que eu tinha sugerido, fora as adequações nas tabelas e funções do PostgreSQL, apenas os códigos relacionados com as queries precisariam de alterações. E o ponto principal é que todos os testes antigos deveriam passar sem nenhuma adequação.
Se estiver tudo certo, posso passar o primeiro commit (com a criação das funções e tabelas) para outro PR.
O primeiro PR acredito que será isso mesmo, mas será necessário pelo menos mais um PR intermediário que irá ler apenas das tabelas antigas, mas gravar de maneira duplicada para garantir a consistência quando o terceiro PR alterar a leitura para as tabelas novas. Nesse último já deve ser possível parar de duplicar os dados e apagar as tabelas antigas.
- Não sei se podemos considerar breaking change a mudança do
error_location_code
deMODEL:BALANCE:RATE_CONTENT:NOT_ENOUGH
paraMODEL:CONTENT:RATE:NOT_ENOUGH
, já que ninguém deveria depender disso (é uma propriedade instável).
Acho que nesse PR não deveria mexer nisso.
- Eu não adicionei
balance_type
na tabelauser_tabcoin_operations
euser_tabcash_operations
porque nabalance_operations
os valores eramuser:tabcoin
euser:tabcash
, então seria bem redundante. Fiquei em dúvida se faria sentido usar os tiposcredit
edebit
, como ocorre com os conteúdos.
Isso eu manteria como você fez mesmo 👍
- Faz sentido criar uma migration para apagar a tabela
balance_operations
,get_content_balance_credit_debit
eget_current_balance
em um outro PR? Assim essa tabela não existirá desnecessariamente, e podemos garantir que a migration só seja executada depois de termos tudo funcionando corretamente.
Faz sentido sim! 👍
Eu optei por copiar o
sequence
para ficar mais fácil de identificar qual foi o último copiado, mas isso também poderia ser comparado peloid
, já que estou copiando oid
debalance_operations
:
Pela razão de existir do sequence
, acho que não faz sentido copiar o valor dele para a nova tabela, a não ser que crie uma coluna temporária como sequence_old
, mas que não me parece necessária se tomarmos os devidos cuidados.
O que precisamos é garantir que a tabela antiga tenha todas as entradas com os valores possíveis de sequence
do primeiro ao último, sem pular nenhum, que as novas tabelas tenham a mesma sequência em sequence
(não o valor em si, mas a mesma ordem da tabela original e sem pular valores) e que exista a correspondência correta entre as entradas da tabela antiga e as novas.
Então a migração dos dados vai precisar de etapas extras para garantir tudo isso.
Quando colocado em produção, pode ser que ocorra atualizações em
balance_operations
entre a última cópia e o merge do PR. Nessa situação, seria necessário usar a query para copiar novamente a partir de terminadosequence
, mas sem copiar o valor dosequence
para não conflitar com interações que ocorreram após o merge.
Então, é isso que precisa ser melhor pensado para garantir a mesma ordem em sequence
.
models/ban.js
Outdated
for (const eventBalanceOperation of eventBalanceOperations) { | ||
await balance.undo(eventBalanceOperation.id, options); | ||
} | ||
await Promise.all(promises); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Em modo local podemos abrir mais de uma conexão com o PostgreSQL, mas em produção limitamos a uma conexão por lambda, então será preciso executar um por vez.
Mas, na verdade, acho que é melhor não refatorar isso nesse PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eu havia mudado do findAll
com WHERE originator_id
seguido de um for
com balance.undo
para usar direto o undoAllByOriginatorId
porque senão ficaria assim (em pseudo-código):
const contentBalanceOperations = await getContentBalanceOperationsFromEvent();
const userTabcoinsOperations = await getUserTabcoinsOperationsFromEvent();
const userTabcashOperations = await getUserTabcashOperationsFromEvent();
for (const eventContentBalanceOperations of contentBalanceOperations) {
await contentTabcoin.undo();
}
for (const eventTabcoinOperations of userTabcoinOperations) {
await userTabcoin.undo();
}
for (const userTabcashOperations of userTabcashOperations) {
await userTabcash.undo();
}
Ou seja, três SELECT
s, um para cada tabela, seguido de três laços iterando sobre cada retorno dos SELECT
s.
Se preferir que o PR 3 (leitura das tabelas novas) fique com o código mencionado acima, e que abra um quarto PR para a refatoração, tudo bem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Por que o PR 3 não poderia permanecer com o código original aqui?
for (const eventBalanceOperation of eventBalanceOperations) {
await balance.undo(eventBalanceOperation.id, options);
}
Minha sugestão original (criar novas tabelas no banco) não envolvia a criação de novos models. Isso é mesmo necessário?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Não é necessário criar novos models se o models/balance
lidar com o "malabarismo" de usar a tabela adequada dependendo dos argumentos recebidos na função em questão. Seria um model conhecendo a estrutura de três tabelas diferentes, similar à função rate
criada em models/content
, porém para todas funções que forem necessárias (create
, getTotal
, undo
e findOne
, que é usada apenas pelo próprio undo
).
Pensei que fizesse mais sentido separar a responsabilidade para models diferentes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pensei que fizesse mais sentido separar a responsabilidade para models diferentes.
Sim Rafa, deve fazer sentido separar os models, mas não necessariamente por estamos separando as tabelas. Apenas acredito ser melhor separar(remover desse PR) o que não é necessário ao processo de separação das tabelas
Que tipo de migração de dados você teve em mente que permitiria realizar as inserções nas tabelas novas antes do PR 2 entrar em produção? A sugestão que eu dei inicialmente no PR teria o mesmo problema, mas eu estava considerando não deixar ordenado os últimos possíveis casos: saldos Pensei em duas opções para evitar/corrigir isso:
WITH ordered_rows AS (
SELECT id, ROW_NUMBER() OVER (ORDER BY created_at) + 999999 AS new_sequence
FROM content_tabcoin_operations
WHERE sequence >= 1000000
)
UPDATE content_tabcoin_operations
SET sequence = ordered_rows.new_sequence
FROM ordered_rows
WHERE content_tabcoin_operations.id = ordered_rows.id; O exemplo acima considera que os saldos possivelmente desordenados são os saldos a partir do |
Para ativar provisoriamente o salvamento dos dados de forma duplicada na tabela antiga e nas novas, a duplicação só começa após o PR 2, pois as migrations não rodam automaticamente no merge do PR 1, então não dá para começar a duplicar antes de criar e popular as tabelas. Então teria no mínimo 3 PRs, talvez 4.
Deixar o site em "modo leitura" é uma ótima opção para garantir o sucesso de primeira e com menos trabalho. A desvantagem de deixar o sistema parcialmente inoperante pode ser mitigada ao realizarmos o procedimento em um momento de menor trafego, como nas madrugadas. Poderia ser em algo assim:
|
be9132c
to
d97e826
Compare
Estou deixando este como o "PR 1", ainda vou
Estou deixando este PR como draft para não realizarmos o merge sem querer. Para as queries de migração de dados será necessário adaptar as queries que mencionei na descrição deste issue apenas removendo o INSERT INTO content_tabcoin_operations(id, balance_type, content_id, amount, originator_type, originator_id, created_at)
SELECT
id,
balance_type,
recipient_id AS content_id,
amount,
originator_type,
originator_id,
created_at
FROM balance_operations
WHERE balance_type IN ('content:tabcoin:credit', 'content:tabcoin:debit', 'content:tabcoin:initial')
ORDER BY sequence; INSERT INTO user_tabcash_operations(id, user_id, amount, originator_type, originator_id, created_at)
SELECT
id,
recipient_id AS user_id,
amount,
originator_type,
originator_id,
created_at
FROM balance_operations
WHERE balance_type = 'user:tabcash'
ORDER BY sequence; INSERT INTO user_tabcoin_operations(id, user_id, amount, originator_type, originator_id, created_at)
SELECT
id,
recipient_id AS user_id,
amount,
originator_type,
originator_id,
created_at
FROM balance_operations
WHERE balance_type = 'user:tabcoin'
ORDER BY sequence; Parece que não será possível realizar o merge com os testes quebrando. Não sei se é possível tirar temporariamente o |
Acho que não faz sentido gerar logs de Não é necessário criar um endpoint e, principalmente, usar as lambdas para criar as respostas. É melhor responder diretamente pela Edge.
Abraçando a ótima ideia de manter fácil a possibilidade de desabilitar endpoints, acho que implementação e discussão merecem um PR separado, mesmo que não seja algo complexo, vão ficar dois assuntos diferentes misturados aqui, e complicar para quem quiser revisitar qualquer dos dois assuntos no futuro (modo leitura vs desmembramento da Mas já respondendo... Para algo mais flexível, que possa ser usado rapidamente em caso de emergência, e que não quebre os testes, penso que é melhor usar as variáveis de ambiente para escolher os endpoints que ficarão indisponíveis, no estilo do que é feito para o rate-limit. Talvez uma variável de ambiente Daí pode existir algo como |
Acho que, com as tabelas separadas, faz sentido mudar o tipo, exatamente como você tinha feito antes 👍 |
Como sugestão para os próximos casos, quando for quebrando o PR em problemas menores, é melhor colocar essas partes menores nos novos PRs, e manter o grosso no PR original. Não tem problema criar PRs mais novos, mas que precisem ser mesclados antes dos já existentes. 👍 Assim mantemos a documentação mais próxima do código. Por exemplo, o grosso do código que foi discutido aqui acabou indo para outro PR (é verdade que, nesse caso, parte do código foi para lugar nenhum 😅) e a discussão em cima do código vai acabar se perdendo. Resolveria isso, além de ser mais simples, ter criado um PR novo apenas com a migration (e, como sugeri hoje, outro com o "modo leitura"). Uma outra enorme vantagem de manter o grosso no mesmo PR é facilitar a revisão, pois permite usar melhor as ferramentas do GitHub, como o histórico dos arquivos que já foram marcados como revisados (algo privado para cada revisor). Isso é muito útil, pois não é preciso analisar novamente os arquivos que não foram modificados a cada novo commit. Com um PR novo, todos os arquivos precisam ser revisados novamente. Não tem problema manter assim, não estou sugerindo mudar o que já planejou, e esse caso não ficou complexo, pois, no final, acabamos ficando apenas com PRs pequenos. Só achei uma boa oportunidade de expor esse meu pensamento, e também de descobrir se ele faz sentido para as outras pessoas. 🤝 |
Certo 👍
Você consegue clarear para mim como isso funciona? Não tenho certeza se é uma alteração no código (e em qual parte) ou fora dele que fará isso. Edit: Eu dei uma lida e parece que para usar uma função Edge basta colocar um
Gostei da ideia. Realmente isso aumenta a complexidade (principalmente a parte de permitir wildcards Nesse PR poderíamos usar algo mais simples, como Edit: Como mencionado no issue #1181, faz sentido usar Edge Config ao invés de variável de ambiente? Já que pretendemos usar Edge para esse redirecionamento. Provavelmente algo assim: import { NextResponse } from 'next/server';
import { get } from '@vercel/edge-config';
export const runtime = 'edge';
// Não sei se dá para ter um outro middleware
export async function middleware(request) {
const url = request.nextUrl;
const isPostContents = url.pathname.startsWith('/api/v1/contents') && request.method === 'POST';
const isReward = url.pathname === '/api/v1/user' && request.method === 'GET';
if (isPostContents || isReward) {
const balanceMaintenanceConfig = await get('balance_operations_maintenance');
if (balanceMaintenanceConfig === 'true') {
const error = new ServiceError({
message: 'O TabNews está em manutenção.',
action: 'Não é possível realizar esta ação agora, tente novamente mais tarde.',
});
return NextResponse.json(snakeize(error), { status: error.statusCode });
}
}
return NextResponse.next();
}
Ok 👍
Eu havia pensado em fazer como você mencionou, mas mudei os planos por dois motivos, onde o primeiro leva ao segundo:
Mas, ao usar uma variável de ambiente para definir esse comportamento, perde-se essa necessidade da segunda branch se basear na primeira para já remover o código de manutenção (falo especificamente sobre o commit 450ce14 do PR #1661). Vendo a análise que você realizou no PR #1661, me pergunto se você acha mais adequado:
Como há bastante coisa para arrumar, pode me falar o jeito que prefere que eu arrumo os PRs para ficarem de acordo. Posso passar de volta para cá a parte relevante da implementação, por causa da discussão que tivemos. |
O middleware é executado na Edge, assim como também podemos configurar as rotas da API para serem executadas na Edge. Essa configuração que você viu seria para mudar o runtime de uma rota da API, mas a minha sugestão é não criar uma nova rota, e sim responder diretamente pelo middleware (producing-a-response). Já fazemos isso no trecho abaixo: tabnews.com.br/middleware.public.js Lines 52 to 57 in eb5f32b
Nem é tão complexo. Segue um exemplo (não testado): const isUnderMaintenance = methodsAndPathsUnderMaintenance.some((methodAndPath) =>
new RegExp(methodAndPath).test(`${method} ${path}`),
);
Seria outra issue, mas talvez seja mais simples já criar o PR do que detalhar o que é para ser feito em uma issue. 😅 Se quiser, eu abro o PR. 👍
Daí teria que desfazer tudo no PR 2. Acho que dá menos trabalho, e é mais útil, já abrir um novo PR com a versão que irá permanecer no código.
Acho que não, mesmo que em alguns momentos possamos usar as duas funcionalidades juntas, acho que, desativar um endpoint da API, e mostrar uma mensagem nas páginas, são coisas diferentes. E usar a
Não dá. E talvez você esteja confundindo as duas possibilidades de rodar código na Edge (API e Middleware). São coisas diferentes. Seu código de exemplo seria para criar uma rota da API que, diferente do padrão, seria executada na Edge. Não é um código que faz sentido para o middleware.
|
Pode deixar comigo 👍
Não precisa inverter 👍 |
d97e826
to
cc2659d
Compare
cc2659d
to
be33b98
Compare
be33b98
to
8df126f
Compare
8df126f
to
55d112a
Compare
Conforme dito nessa sequência de comentários: #1661 (comment), estou fechando este PR porque a migração será realizada junto das alterações no código, já que uma das alterações modifica uma função SQL existente, que faz os testes quebrarem caso o código não seja atualizado. |
Ainda é necessário avaliar melhor se realmente não será preciso criar as novas tabelas em um PR separado. Talvez dê problemas no build se houverem conteúdos na lista de relevantes. A lista de relevantes estava vazia em homologação, mas já criei uma publicação para tirar essa dúvida no próximo build. Também tem o fato de que outras funcionalidades ficariam com erro até que fosse(m) executada(s) a(s) migration(s). Não apenas os endpoints que travarmos propositalmente, mas outras coisas que não necessariamente precisariam ficar fora. E isso pode disparar alertas indesejados no momento que queremos estar focados em realizar as cópias e testes das novas tabelas. Por tudo isso, já deixei a variável de ambiente configurada para "modo leitura" também nessa branch, caso ela ainda seja utilizada.
Isso não me parece um problema para o processo de PRs separados. Não acha suficiente deixar a atualização da |
Quando comecei a ler seu comentário pensei nisso como alternativa. Vou fazer isso. |
55d112a
to
b0237bc
Compare
A partir de agora, todos os próximos deploys em homologação já vão estar travados com:
Acho que já podemos dar esse PR como pronto, mas é melhor aguardar o #1661 também estar pronto (talvez já esteja), pois assim não deixamos o ambiente de homologação travado por muito tempo. Antes de rodar a migration desse PR, vou tirar do ar todos deploys que ainda existem em homologação, e somente poderemos voltar ao normal após a cópia dos dados para as novas tabelas. Mesmo após a cópia, é bom garantir que ninguém mais consiga fazer um deploy que consiga gravar na tabela |
Apenas após a exclusão do |
|
Create three tables to separately store content TabCoins, user TabCoins and user TabCash operations.
b0237bc
to
60d75e3
Compare
Mudanças realizadas
Conforme discutido em #1491 (comment), separei o
balance_operations
em três tabelas distintas: uma para as operações de TabCoins de conteúdos, outra para as de TabCoins de usuários e outra para as de TabCash.Não adicionei
balance_type
na tabelauser_tabcoin_operations
euser_tabcash_operations
porque nabalance_operations
os valores eramuser:tabcoin
euser:tabcash
, então seria bem redundante.Esse processo será feito em alguns PRs diferentes:
UNDER_MAINTENANCE
#1662), conforme discutido em Migração para criar novas tabelas de saldo #1656 (comment) e nos comentários seguintes.balance_operations
e nas novas tabelas e reabilitar endpoints #1661) fará a leitura e escrita nas tabelas novas.balance_operations
#1673 ) fará uma migração para remover a tabelabalance_operations
e as funções que não são mais utilizadas.Tipo de mudança