Video perfeito. Problema bem explicado, teorias de resolução bem explicados e código bem aplicado. Manda mais desse Pedrão, seria bem legal uma série de “problemas do cotidiano resolvidos” algo que trouxesse mais insights como esse.
Muito bom! Meses atrás eu também esbarrei nesse problema e descobri o mundo das race conditions kkkkk. Vou colocar aqui a resposta que fiz pra um comentário. Se não me engano, isso é apenas onde existe um conflito, uma condição de corrida. Por exemplo neste caso do estoque, se duas pessoas lerem o estoque como 1 e uma delas comprar primeiro, a segunda pessoa que for comprar e leu o estoque como 1 já está desatualizado e portanto iria comprar um produto achando que tem estoque quando na verdade não tem. Percebe o conflito? Isso é uma race condition. No caso, colocar um campo versao, o sistema pode verificar se a versão ainda é a mesma colocando no where do update, garantindo que a atualização do estoque só ocorra se a linha da tabela não foi alterada por outra pessoa. Eu fiz um resumo rápido aqui e provavelmente insuficiente pra uma visão clara do problema. Sugiro que você pesquise no google sobre race conditions em banco de dados e faça testes na prática para internalizar o conhecimento.
excelente como sempre. Um detalhe sobre optimistic lock, é que se tiver muito choque de atualização gerará muito rollback e em certos casos pode ficar mais caro.
Provavelmente ele quis dizer em uma transação longa que envolve a atualização ou criação de objetos novos. O rollback tem que desfazer isso tudo caso o update no optimistic lock falhe.
Opa pedro, uma correção, o for update ou o lock do serializable, só vai bloquear a leitura caso as outras transactions estiverem no mesmo nível de isolamento, caso uma estiver em outro como no read commited por exemplo, ele ainda consegue ler o registro selecionado com for update e etc, e no seu sistema vc tem alternativas para contornar isso caso possa atrapalhar a sua query como usar um skip locked aonde ele vai ignorar os registros bloqueados e realizar o select no restante. Outra sobre o serializable, ele nao necessariamente vai dar lock na tabela inteira, se vc dar um for update em apenas um registro da tabela, outras transactions com o nível de isolamento serializable, vão conseguir ler os outros registros em paralelo, então o serializable não é uma execução sequencial no literal, vc pode ter 2 transactions com o nivel serializable executando em paralelo desde que elas não alterem registros bloqueados pelas outras.
Lembrando que ao utilizar certos níveis de isolamento vc pode correr o risco de ter 2 transações em deadlock, algo que seria legal ser explicado no vídeo.
Nem acredito que esse video foi lançado, to nesse exato momento resolvendo um problema de race conditions na minha aplicação, usuários estão sendo duplicados no meu banco de dados
Você é demais, Pedro. Explicação clara, áudio e vídeo perfeitos e exemplo simples de entendimento. Desde quando comecei a te seguir, não perco 1 vídeo sequer. Aprendo muito contigo.
Eu achei muito interessante essa solução. Só tenho algumas dúvidas, por exemplo; Será que a performance de fazer um retry caso a version seja diferente, não poderia ser a mesma de dar lock no registro? Se 100 registros ao mesmo tempo encontrar a version diferente no select de um mesmo registro, o retry de todas elas ao mesmo tempo não poderia ser menos performático que usar lock? São apenas questionamentos, mas talvez o custo do lock seja tão grande, que a tecnica do version seja bem melhor mesmo com retries
lembrei de quando desenvolvi o chat via socket, as vezes se a pessoa clica-se no mesmo tempo para aceitar o atendimento era uma briga de um puxando para o outro, 1 linha de conferencia resolveu 100% os problema mais ou menos como você fez com o version :3
E para um cenário onde a um insert no banco depois de um retorno de uma api, mas podem existir muitas chamadas para um mesmo registro (que terá esse insert da chamada na api) para essa api e acaba por duplicar essa chamada, já que era para acontecer só uma chamada por dia, visto que a resposta da api não é instantânea.
Cara fantástico esse tema! Achei muito legal as soluções apresentadas, em especial a última, que achei extremamente simples porém fiquei pensando: isso não causaria uma grande quantidade de erros sempre que uma requisição tentasse atualizar e outra já tiver passado na frente, pensando em um elevado número de requisições simultâneas o estresse com erros (para o usuário) não seria o mesmo da perda de performasse?
Eu tava pensando nisso tbm, por exemplo a versão começa como 0 aí João e Marcelo clicaram pra comprar ao mesmo tempo e só tem mais duas unidades do produto, a requisição do João terminou primeiro portanto agora a versão está 1, quando Marcelo for atualizar para ele a versão ainda será 0 resultando na falha da compra quando na verdade ainda possui uma unidade
Cara essa parte da versão, serveria bem para algo como: Eu tenho uma tabela que tem uma lista de números, e sempre que alguém reserva aquele numero, a lista e atualizada. E tem uma cron que também atualiza aquela lista, podendo colocar alguns números de novo dentro dela, como números que acabei de tirar no metodo acima. E elas podem rodar ao mesmo tempo sendo que a cron roda a cada 1 minuto e o metodo pode ser chamado por um usúario toda hora.
Esse problema não exige um design de sistema específico para resolvê-lo. Você tem apenas duas opções: lock otimista ou lock pessimista. Você pode implementar com MVC, CQRS, etc.
Boa tarde! E se ao invés de tratar isso no Backend fazer uma função no BD que encapsule essa lógica e retorna a informação para o backened com o sucesso ou não. Nesse caso ainda precisaria de controle de transição? Também acredito que se colocar no update na cláusula where id = p_id and qtde_estoque >= p_qtde_pedido e tratar com um row affected pode solucionar.
vesh n entendi, enquanto ter várias pessoas acessando o msm endpoint, eu tenho q dar sorte de n ter ninguém atualizando a versão pra conseguir executar a query? e no caso de criar registros em várias tabelas, todas tem q ter uma verificação de versão?
Se não me engano, isso é apenas onde existe um conflito, uma condição de corrida. Por exemplo, neste caso do estoque, se duas pessoas lerem o estoque como 1 e uma delas comprar primeiro, a segunda pessoa que for comprar e leu o estoque como 1 já está desatualizado e portanto iria comprar um produto achando que tem estoque quando na verdade não tem. Percebe o conflito? Isso é uma race condition. No caso, colocar um campo versao, o sistema pode verificar se a versão ainda é a mesma colocando no where do update, garantindo que a atualização do estoque só ocorra se a linha da tabela não foi alterada por outra pessoa. Eu fiz um resumo rápido aqui e provavelmente insuficiente pra uma visão clara do problema. Sugiro que você pesquise no google sobre race conditions em banco de dados e faça testes na prática para internalizar o conhecimento.
Pedrão, achei muito interessante o assunto do vídeo, tentei replicar o código com NodeJS e MySQL, porém, no primeiro cenário não tive o problema dos -90, sabe me dizer se o Node ou o próprio MySQL já faz a tratativa do problema apresentado no vídeo? Obg, abraços..
Filas são usadas pra controlar a carga máxima que o sistema pode receber. Não significa que todos os pedidos são processados sequencialmente. Eu posso definir um limite máximo de X pedidos em processamento e encaminhar o excesso para a fila.
Não entendi muito bem a solução com versa pra esse cenário. Entendo que o problema seria alguém comprar um item sem ter mais em estoque pq durante o processo de compra o saldo foi alterado e chegou a zero. Só o fato de outro usuário ter feito a compra e ter atualizado o registro não me impede de fazer o mesmo. Nesse caso o update não deveria verificar "quantidade > 0"?
Sim! Mas na hora de fazer o update, a versão no banco vai estar diferente, o que significa que aquele registro foi alterado, e aí o update vai ser “cancelado”. Na verdade, o que acontece é que a WHERE clause retorna “No Rows”, então nenhum registro é atualizado. Mas em termos práticos entende-se que o update foi “cancelado”. Faz sentido?
@@phenpessoa então eu entendi o funcionamento, mas nesse cenário me parece q mesmo que outra request tenha alterado o registro, como o saldo ainda é maior que zero a segunda request poderia prosseguir com a operação sem problemas. Imagino que essa versão seria útil num cenário em que apenas uma requisição possa ter acesso a um recurso específico como a reserva de um assento em um vôo, pois se outra requisição concorrente já o reservou a segunda teria que falhar mesmo. Só que aí não sei como faria com update. (Eu sempre utilizei o "for update" mas nunca trabalhei com um sistema com uma carga alta que exigisse uma solução mais elaborada)
De qualquer maneira,o sistema com versão tem um problema.Se dois acessos recebem a mesma versão no momento da leitura eles terão concorrencia no momento da escrita.
Isso não acontece. O PostgreSQL garante que updates sempre vão precisar de um ROW EXCLUSIVE lock na hora de rodar, o que garante que não existe condição de corrida (a nível de banco) durante os writes. Pode acontecer a nível de aplicação (como eu mostrei no vídeo), mas não a nível de banco. Tanto que o no primeiro exemplo o resultado final sempre vai ser -90. Se existisse condição de corrida a nível de banco, esse número poderia variar toda vez que a gente escrevesse o código.
@@phenpessoa mas aí dá no mesmo que fazer o FOR UPDATE, a transação fica pendente até que a outra termine. De qualquer forma, não utilize update no saldo para fazer um controle de estoque. Faça um fluxo, se necessário uma fila.
Changeset ? Pq vc gostam de aprender as técnicas modernas de programação e nao entender o pq Phoenix ja vem com quase tudo pra solucionar esse velho problemas?
Video perfeito. Problema bem explicado, teorias de resolução bem explicados e código bem aplicado. Manda mais desse Pedrão, seria bem legal uma série de “problemas do cotidiano resolvidos” algo que trouxesse mais insights como esse.
Muito bom! Meses atrás eu também esbarrei nesse problema e descobri o mundo das race conditions kkkkk.
Vou colocar aqui a resposta que fiz pra um comentário.
Se não me engano, isso é apenas onde existe um conflito, uma condição de corrida. Por exemplo neste caso do estoque, se duas pessoas lerem o estoque como 1 e uma delas comprar primeiro, a segunda pessoa que for comprar e leu o estoque como 1 já está desatualizado e portanto iria comprar um produto achando que tem estoque quando na verdade não tem. Percebe o conflito? Isso é uma race condition. No caso, colocar um campo versao, o sistema pode verificar se a versão ainda é a mesma colocando no where do update, garantindo que a atualização do estoque só ocorra se a linha da tabela não foi alterada por outra pessoa.
Eu fiz um resumo rápido aqui e provavelmente insuficiente pra uma visão clara do problema. Sugiro que você pesquise no google sobre race conditions em banco de dados e faça testes na prática para internalizar o conhecimento.
excelente como sempre. Um detalhe sobre optimistic lock, é que se tiver muito choque de atualização gerará muito rollback e em certos casos pode ficar mais caro.
e o que você faria nesse caso? é pro meu tcc 😅
Deixando uma ward aqui pra saber a resposta. Fiquei curioso
Provavelmente ele quis dizer em uma transação longa que envolve a atualização ou criação de objetos novos. O rollback tem que desfazer isso tudo caso o update no optimistic lock falhe.
@@gstella acho que aí vc teria que escolher entre:
- o custo maior do optimistic
- a performance pior do pessimistic
Opa pedro, uma correção, o for update ou o lock do serializable, só vai bloquear a leitura caso as outras transactions estiverem no mesmo nível de isolamento, caso uma estiver em outro como no read commited por exemplo, ele ainda consegue ler o registro selecionado com for update e etc, e no seu sistema vc tem alternativas para contornar isso caso possa atrapalhar a sua query como usar um skip locked aonde ele vai ignorar os registros bloqueados e realizar o select no restante. Outra sobre o serializable, ele nao necessariamente vai dar lock na tabela inteira, se vc dar um for update em apenas um registro da tabela, outras transactions com o nível de isolamento serializable, vão conseguir ler os outros registros em paralelo, então o serializable não é uma execução sequencial no literal, vc pode ter 2 transactions com o nivel serializable executando em paralelo desde que elas não alterem registros bloqueados pelas outras.
Lembrando que ao utilizar certos níveis de isolamento vc pode correr o risco de ter 2 transações em deadlock, algo que seria legal ser explicado no vídeo.
Nem acredito que esse video foi lançado, to nesse exato momento resolvendo um problema de race conditions na minha aplicação, usuários estão sendo duplicados no meu banco de dados
Perfeito macho, uma ideia bem simples que nunca tinha pensado nisso. Engraçado que as vezes o simples/óbvio precisa ser dito.
calma femea kkkk
@@carloshenrique-ov5nk ai dento 'kkkkk
caralho voce explico de maneira mais direta e eficaz que eu ja vi aqui no youtube parabens!
Você é demais, Pedro. Explicação clara, áudio e vídeo perfeitos e exemplo simples de entendimento. Desde quando comecei a te seguir, não perco 1 vídeo sequer. Aprendo muito contigo.
Excelente vídeo irmão, parabéns, vlw mesmo!!!!
Caraca, olha a qualidade desse áudio kkkkk tava assistindo um vídeo e cliquei nesse, DIFERENÇA ABSURDA!! Bom vídeo!
Tive que usar algumas estratégias para lidar com concorrência e condições de corrida na rinha de backend. O conteudo desse canal é fantastico.
Muito obrigado! :)
Conteúdo massa, pfv não pare.
Cara, seu conteúdo é excelente. Poderia assistir todos os dias um vídeo novo kkkkk.
Eu achei muito interessante essa solução. Só tenho algumas dúvidas, por exemplo; Será que a performance de fazer um retry caso a version seja diferente, não poderia ser a mesma de dar lock no registro?
Se 100 registros ao mesmo tempo encontrar a version diferente no select de um mesmo registro, o retry de todas elas ao mesmo tempo não poderia ser menos performático que usar lock?
São apenas questionamentos, mas talvez o custo do lock seja tão grande, que a tecnica do version seja bem melhor mesmo com retries
lembrei de quando desenvolvi o chat via socket, as vezes se a pessoa clica-se no mesmo tempo para aceitar o atendimento era uma briga de um puxando para o outro, 1 linha de conferencia resolveu 100% os problema mais ou menos como você fez com o version :3
E para um cenário onde a um insert no banco depois de um retorno de uma api, mas podem existir muitas chamadas para um mesmo registro (que terá esse insert da chamada na api) para essa api e acaba por duplicar essa chamada, já que era para acontecer só uma chamada por dia, visto que a resposta da api não é instantânea.
Skill de malandro essa de usar o version haha gostei muito!! video ótimo
Explicado de uma forma muito clara. Obrigado.
Simples e funcional! Parabéns!! Muito bem explicado.
Que didática, que oratória.
Parabéns Pedro, continue fazendo vídeos frequentes, por favor.
Cara fantástico esse tema! Achei muito legal as soluções apresentadas, em especial a última, que achei extremamente simples porém fiquei pensando: isso não causaria uma grande quantidade de erros sempre que uma requisição tentasse atualizar e outra já tiver passado na frente, pensando em um elevado número de requisições simultâneas o estresse com erros (para o usuário) não seria o mesmo da perda de performasse?
Eu tava pensando nisso tbm, por exemplo a versão começa como 0 aí João e Marcelo clicaram pra comprar ao mesmo tempo e só tem mais duas unidades do produto, a requisição do João terminou primeiro portanto agora a versão está 1, quando Marcelo for atualizar para ele a versão ainda será 0 resultando na falha da compra quando na verdade ainda possui uma unidade
top!
Parabens pelo conteudo!
Amei, já tive essa dúvida mas nunca fui atrás para sanar, obrigado por mostrar ❤.
Esse vídeo é incrível
Muito bom Pedro! ganhou um inscrito
Muito obrigado e seja bem vindo! :)
Muito interessante, acho que já sei onde posso usar isso.
Cara essa parte da versão, serveria bem para algo como:
Eu tenho uma tabela que tem uma lista de números, e sempre que alguém reserva aquele numero, a lista e atualizada.
E tem uma cron que também atualiza aquela lista, podendo colocar alguns números de novo dentro dela, como números que acabei de tirar no metodo acima.
E elas podem rodar ao mesmo tempo sendo que a cron roda a cada 1 minuto e o metodo pode ser chamado por um usúario toda hora.
Acha que da pra resolver isso no padrão CQRS ? Se não, qual padrão de arquitetura poderia ser uma opção pra resolver ?
Obs: Que tema sensacional.
Esse problema não exige um design de sistema específico para resolvê-lo. Você tem apenas duas opções: lock otimista ou lock pessimista. Você pode implementar com MVC, CQRS, etc.
É possível ver o título do conteúdo de membros para dar um gostinho e vontade de se tornar membro
Boa tarde!
E se ao invés de tratar isso no Backend fazer uma função no BD que encapsule essa lógica e retorna a informação para o backened com o sucesso ou não.
Nesse caso ainda precisaria de controle de transição?
Também acredito que se colocar no update na cláusula where id = p_id and qtde_estoque >= p_qtde_pedido e tratar com um row affected pode solucionar.
Pedrao muito massa essa explicação 😮
O que você acha de orms? Usa eles em projetos em Go? Ou vai com o sql puro mesmo?
Muito didático 👏🏻👏🏻👏🏻
Mto bom!
Por favor que tema/color scheme é esse que você está utilizando no vim?
É o catppuccin
vesh n entendi, enquanto ter várias pessoas acessando o msm endpoint, eu tenho q dar sorte de n ter ninguém atualizando a versão pra conseguir executar a query? e no caso de criar registros em várias tabelas, todas tem q ter uma verificação de versão?
Se não me engano, isso é apenas onde existe um conflito, uma condição de corrida. Por exemplo, neste caso do estoque, se duas pessoas lerem o estoque como 1 e uma delas comprar primeiro, a segunda pessoa que for comprar e leu o estoque como 1 já está desatualizado e portanto iria comprar um produto achando que tem estoque quando na verdade não tem. Percebe o conflito? Isso é uma race condition. No caso, colocar um campo versao, o sistema pode verificar se a versão ainda é a mesma colocando no where do update, garantindo que a atualização do estoque só ocorra se a linha da tabela não foi alterada por outra pessoa.
Eu fiz um resumo rápido aqui e provavelmente insuficiente pra uma visão clara do problema. Sugiro que você pesquise no google sobre race conditions em banco de dados e faça testes na prática para internalizar o conhecimento.
Excelente, parabéns!
Pedrão, achei muito interessante o assunto do vídeo, tentei replicar o código com NodeJS e MySQL, porém, no primeiro cenário não tive o problema dos -90, sabe me dizer se o Node ou o próprio MySQL já faz a tratativa do problema apresentado no vídeo? Obg, abraços..
Você está fazendo os requests concurrentemente ou sequencialmente? Se os requests forem sequenciais não terá problema.
Muito bom mesmo.
muito interessante, ótimo vid ^-^
Excelente
Topp! Pedro se possível nos mostre como configurou o LSP do Glean no neovim! Ficaria agradecido se pudesse ajudar kkkkk 😃
Boa ideia!
colocar as compras em uma queue tambem seria uma boa pratica? tipo a amazon. voce faz a compra, porem ele demora um tempo ate chegar a confirmação
Filas são usadas pra controlar a carga máxima que o sistema pode receber. Não significa que todos os pedidos são processados sequencialmente.
Eu posso definir um limite máximo de X pedidos em processamento e encaminhar o excesso para a fila.
O mysql tem essa condição?
Ótimo conteúdo
Muito bom..!! Parabéns ...
Bem interessante
ótimo vídeo
video excelente!
Qual o tema?
Não entendi muito bem a solução com versa pra esse cenário. Entendo que o problema seria alguém comprar um item sem ter mais em estoque pq durante o processo de compra o saldo foi alterado e chegou a zero.
Só o fato de outro usuário ter feito a compra e ter atualizado o registro não me impede de fazer o mesmo.
Nesse caso o update não deveria verificar "quantidade > 0"?
Sim! Mas na hora de fazer o update, a versão no banco vai estar diferente, o que significa que aquele registro foi alterado, e aí o update vai ser “cancelado”. Na verdade, o que acontece é que a WHERE clause retorna “No Rows”, então nenhum registro é atualizado. Mas em termos práticos entende-se que o update foi “cancelado”. Faz sentido?
@@phenpessoa então eu entendi o funcionamento, mas nesse cenário me parece q mesmo que outra request tenha alterado o registro, como o saldo ainda é maior que zero a segunda request poderia prosseguir com a operação sem problemas.
Imagino que essa versão seria útil num cenário em que apenas uma requisição possa ter acesso a um recurso específico como a reserva de um assento em um vôo, pois se outra requisição concorrente já o reservou a segunda teria que falhar mesmo. Só que aí não sei como faria com update.
(Eu sempre utilizei o "for update" mas nunca trabalhei com um sistema com uma carga alta que exigisse uma solução mais elaborada)
De qualquer maneira,o sistema com versão tem um problema.Se dois acessos recebem a mesma versão no momento da leitura eles terão concorrencia no momento da escrita.
Isso não acontece. O PostgreSQL garante que updates sempre vão precisar de um ROW EXCLUSIVE lock na hora de rodar, o que garante que não existe condição de corrida (a nível de banco) durante os writes. Pode acontecer a nível de aplicação (como eu mostrei no vídeo), mas não a nível de banco.
Tanto que o no primeiro exemplo o resultado final sempre vai ser -90. Se existisse condição de corrida a nível de banco, esse número poderia variar toda vez que a gente escrevesse o código.
@@phenpessoa mas aí dá no mesmo que fazer o FOR UPDATE, a transação fica pendente até que a outra termine. De qualquer forma, não utilize update no saldo para fazer um controle de estoque. Faça um fluxo, se necessário uma fila.
Changeset ? Pq vc gostam de aprender as técnicas modernas de programação e nao entender o pq Phoenix ja vem com quase tudo pra solucionar esse velho problemas?
FOR UPDATE SKIP LOCKED resolve melhor ao meu ver
controle de concorrência
Top demais.