$30 off During Our Annual Pro Sale. View Details »

Implementando Mensageria com PostgreSQL

Implementando Mensageria com PostgreSQL

A industria tem feito um marketing agressivo em favor de arquiteturas complexas e muito caras, incluindo microsserviços, brokers de mensageria e bancos NoSQL. Para isso, arquiteturas mais simples e, na maioria das vezes suficientes, tornaram-se os vilões para muitas empresas e profissionais, como monolitos e bancos de dados relacionais. Infelizmente, esse último, há mais de 1 decada tem sido vendido apenas como um simples repositório de dados limitado que não permite escalar para milhões de usuários ou dezenas de milhares de requisições.

Essas falácias sobre bancos relacionais precisam acabar, por esse motivo, nessa talk pretendo apresentar como podemos integrar sistemas e serviços sem abrir mão do mundo relacional, tudo isso na perspectiva de um desenvolvedor(a) de software. Em vez de todo um alto custo e complexidade na adoção de Kafka ou RabbitMQ para implementar mensageria em sistemas, nós abraçaremos recursos nativos do próprio PostgreSQL para implementar troca de eventos via fila de mensagens e comunicação Pub/Sub de forma simples, confiável e escalável.

Você vai se surpreender como esses tipos de soluções de mensageria podem ser resolvidos com algumas linhas de SQL, locking distribuído e algum conhecimento sobre as features oferecidas pelo seu banco de dados relacional. No fim, você vai perceber como PostgreSQL é mais do que um repositório de dados, é uma engine completa e robusta de concorrência.

(GRAVAÇÃO: https://www.youtube.com/watch?v=FF6Am0N6eq4&t=840s&ab_channel=Zup)

Rafael Ponte

August 26, 2022
Tweet

More Decks by Rafael Ponte

Other Decks in Technology

Transcript

  1. Mensageria com PostgreSQL Escalando sua aplicação com seu banco de

    dados relacional
  2. None
  3. None
  4. None
  5. Hype-Driven Development https://blog.daftcode.pl/hype-driven-development-3469fc2e9b22

  6. None
  7. CACHE

  8. PROCESSAMENTO ASSINCRONO CACHE

  9. BALANCEAMENTO DE CARGA CACHE PROCESSAMENTO ASSINCRONO

  10. CACHE BALANCEAMENTO DE CARGA PROCESSAMENTO ASSINCRONO

  11. a idéia é simples…

  12. servidor

  13. db servidor

  14. navegador db servidor

  15. navegador db servidor requisição

  16. navegador db servidor requisição resposta

  17. e funciona muito bem…

  18. db servidor requisição resposta

  19. db servidor requisição resposta

  20. db servidor requisição resposta

  21. db servidor requisição resposta

  22. db servidor requisição resposta

  23. calma!

  24. navegador db servidor requisição resposta

  25. navegador db servidor requisição resposta ???

  26. navegador db servidor requisição resposta

  27. navegador db servidor requisição resposta fi la

  28. navegador db servidor requisição resposta fi la consumidor

  29. navegador db servidor requisição resposta fi la consumidor

  30. navegador db servidor requisição resposta fi la consumidor

  31. mas qual broker?

  32. None
  33. None
  34. None
  35. None
  36. mas qual o custo?

  37. mas qual o custo? Segurança Backup Replicação Monitoramento Upgrade Recovery

    Fail-over Alta-disponibilidade Hardware Contratar 
 especialistas
  38. E agora?

  39. None
  40. Não!

  41. Que tal…

  42. Banco de dados 
 relacional

  43. “Use o banco apenas como repositório de dados.”

  44. Mas sempre tem aqueles…

  45. None
  46. None
  47. None
  48. Diga-me teu throughput e te direis quem és…

  49. Como escalar seu sistema usando seu BANCO DE DADOS

  50. Rafael Ponte @rponte

  51. None
  52. Fortaleza - Terra do Sol

  53. None
  54. Mensageria com PostgreSQL Escalando sua aplicação o seu MELHOR banco

    de dados relacional
  55. nosso problema

  56. Loja Virtual

  57. None
  58. None
  59. None
  60. @RestController class FinalizaPedidoController { @Transactional @PostMapping(“/api/pedidos/finaliza”) public PedidoResponse finaliza(@RequestBody PedidoRequest

    request) { 
 // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal 
 
 return new PedidoResponse(pedido, Status.CONFIRMADO); } }
  61. @RestController class FinalizaPedidoController { @Transactional @PostMapping(“/api/pedidos/finaliza”) public PedidoResponse finaliza(@RequestBody PedidoRequest

    request) { 
 // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal 
 
 return new PedidoResponse(pedido, Status.CONFIRMADO); } }
  62. @RestController class FinalizaPedidoController { @Transactional @PostMapping(“/api/pedidos/finaliza”) public PedidoResponse finaliza(@RequestBody PedidoRequest

    request) { 
 // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal 
 
 return new PedidoResponse(pedido, Status.CONFIRMADO); } }
  63. @RestController class FinalizaPedidoController { @Transactional @PostMapping(“/api/pedidos/finaliza”) public PedidoResponse finaliza(@RequestBody PedidoRequest

    request) { 
 // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal 
 
 return new PedidoResponse(pedido, Status.CONFIRMADO); } }
  64. @RestController class FinalizaPedidoController { @Transactional @PostMapping(“/api/pedidos/finaliza”) public PedidoResponse finaliza(@RequestBody PedidoRequest

    request) { 
 // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal 
 
 return new PedidoResponse(pedido, Status.CONFIRMADO); } }
  65. @RestController class FinalizaPedidoController { @Transactional @PostMapping(“/api/pedidos/finaliza”) public PedidoResponse finaliza(@RequestBody PedidoRequest

    request) { 
 // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal 
 
 return new PedidoResponse(pedido, Status.CONFIRMADO); } }
  66. @RestController class FinalizaPedidoController { @Transactional @PostMapping(“/api/pedidos/finaliza”) public PedidoResponse finaliza(@RequestBody PedidoRequest

    request) { 
 // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal 
 
 return new PedidoResponse(pedido, Status.CONFIRMADO); } }
  67. @RestController class FinalizaPedidoController { @Transactional @PostMapping(“/api/pedidos/finaliza”) public PedidoResponse finaliza(@RequestBody PedidoRequest

    request) { 
 // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal 
 
 return new PedidoResponse(pedido, Status.CONFIRMADO); } }
  68. @RestController class FinalizaPedidoController { @Transactional @PostMapping(“/api/pedidos/finaliza”) public PedidoResponse finaliza(@RequestBody PedidoRequest

    request) { 
 // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal 
 
 return new PedidoResponse(pedido, Status.CONFIRMADO); } }
  69. @RestController class FinalizaPedidoController { @Transactional @PostMapping(“/api/pedidos/finaliza”) public PedidoResponse finaliza(@RequestBody PedidoRequest

    request) { 
 // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal 
 
 return new PedidoResponse(pedido, Status.CONFIRMADO); } }
  70. @RestController class FinalizaPedidoController { @Transactional @PostMapping(“/api/pedidos/finaliza”) public PedidoResponse finaliza(@RequestBody PedidoRequest

    request) { 
 // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal 
 
 return new PedidoResponse(pedido, Status.CONFIRMADO); } }
  71. @RestController class FinalizaPedidoController { @Transactional @PostMapping(“/api/pedidos/finaliza”) public PedidoResponse finaliza(@RequestBody PedidoRequest

    request) { 
 // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal 
 
 return new PedidoResponse(pedido, Status.CONFIRMADO); } }
  72. @RestController class FinalizaPedidoController { @Transactional @PostMapping(“/api/pedidos/finaliza”) public PedidoResponse finaliza(@RequestBody PedidoRequest

    request) { 
 // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal 
 
 return new PedidoResponse(pedido, Status.CONFIRMADO); } }
  73. @RestController class FinalizaPedidoController { @Transactional @PostMapping(“/api/pedidos/finaliza”) public PedidoResponse finaliza(@RequestBody PedidoRequest

    request) { 
 // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal 
 
 return new PedidoResponse(pedido, Status.CONFIRMADO); } }
  74. @RestController class FinalizaPedidoController { @Transactional @PostMapping(“/api/pedidos/finaliza”) public PedidoResponse finaliza(@RequestBody PedidoRequest

    request) { 
 // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal 
 
 return new PedidoResponse(pedido, Status.CONFIRMADO); } }
  75. @RestController class FinalizaPedidoController { @Transactional @PostMapping(“/api/pedidos/finaliza”) public PedidoResponse finaliza(@RequestBody PedidoRequest

    request) { 
 // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal 
 
 return new PedidoResponse(pedido, Status.CONFIRMADO); } }
  76. @RestController

  77. navegador servidor db

  78. navegador servidor db

  79. navegador servidor db

  80. se a execução é custosa então ela é um possível

    gargalo…
  81. None
  82. CACHE

  83. PROCESSAMENTO ASSINCRONO CACHE

  84. BALANCEAMENTO DE CARGA CACHE PROCESSAMENTO ASSINCRONO

  85. CACHE BALANCEAMENTO DE CARGA PROCESSAMENTO ASSINCRONO

  86. “a gente joga numa FILA ! ”

  87. Quando falamos de fi la…

  88. 4 3 2 QUEUE ( fi la)

  89. 4 3 2 5 QUEUE ( fi la)

  90. 4 3 2 5 enqueue QUEUE ( fi la)

  91. 4 3 2 5 1 enqueue QUEUE ( fi la)

  92. 4 3 2 5 1 enqueue dequeue QUEUE ( fi

    la)
  93. 4 3 2 5 1 enqueue dequeue QUEUE ( fi

    la) FIFO: First-in, First-out
  94. No mundo enterprise…

  95. QUEUE (no mundo enterprise)

  96. 1002 1001 QUEUE (no mundo enterprise)

  97. 1002 1001 (message queue) QUEUE (no mundo enterprise)

  98. 1002 1001 3506 QUEUE (no mundo enterprise) (message queue)

  99. 1002 1001 3506 QUEUE (no mundo enterprise) (message) (message queue)

  100. 1002 1001 producer QUEUE (no mundo enterprise) (message queue)

  101. 1002 1001 producer 1000 QUEUE (no mundo enterprise) (message queue)

  102. 1002 1001 producer consumer QUEUE (no mundo enterprise) (message queue)

  103. Ou seja…

  104. navegador servidor db

  105. navegador servidor db X

  106. navegador servidor db fi la

  107. navegador servidor db fi la

  108. navegador servidor db fi la

  109. navegador servidor db fi la

  110. navegador servidor db fi la

  111. Banco de dados 
 relacional

  112. precisamos de uma tabela…

  113. Pedidos

  114. Para isso…

  115. @RestController class FinalizaPedidoController { @Transactional @PostMapping(“/api/pedidos/finaliza”) public PedidoResponse finaliza(@RequestBody PedidoRequest

    request) { 
 // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal 
 
 return new PedidoResponse(pedido, Status.CONFIRMADO); } }
  116. @RestController class FinalizaPedidoController { @Transactional @PostMapping(“/api/pedidos/finaliza”) public PedidoResponse finaliza(@RequestBody PedidoRequest

    request) { 
 // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal 
 
 return new PedidoResponse(pedido, Status.CONFIRMADO); } }
  117. @RestController class FinalizaPedidoController { private PedidoRepository repository; @Transactional @PostMapping(“/api/pedidos/finaliza”) public

    PedidoResponse finaliza(@RequestBody PedidoRequest request) { Pedido pedido = request.toModel(); pedido.setStatus(Status.PENDENTE); 
 repository.save(pedido); 
 return new PedidoResponse(request, Status.PENDENTE); } }
  118. @RestController class FinalizaPedidoController { private PedidoRepository repository; @Transactional @PostMapping(“/api/pedidos/finaliza”) public

    PedidoResponse finaliza(@RequestBody PedidoRequest request) { Pedido pedido = request.toModel(); pedido.setStatus(Status.PENDENTE); 
 repository.save(pedido); 
 return new PedidoResponse(request, Status.PENDENTE); } }
  119. @RestController class FinalizaPedidoController { private PedidoRepository repository; @Transactional @PostMapping(“/api/pedidos/finaliza”) public

    PedidoResponse finaliza(@RequestBody PedidoRequest request) { Pedido pedido = request.toModel(); pedido.setStatus(Status.PENDENTE); 
 repository.save(pedido); 
 return new PedidoResponse(request, Status.PENDENTE); } }
  120. @RestController class FinalizaPedidoController { private PedidoRepository repository; @Transactional @PostMapping(“/api/pedidos/finaliza”) public

    PedidoResponse finaliza(@RequestBody PedidoRequest request) { Pedido pedido = request.toModel(); pedido.setStatus(Status.PENDENTE); 
 repository.save(pedido); 
 return new PedidoResponse(request, Status.PENDENTE); } }
  121. @RestController class FinalizaPedidoController { private PedidoRepository repository; @Transactional @PostMapping(“/api/pedidos/finaliza”) public

    PedidoResponse finaliza(@RequestBody PedidoRequest request) { Pedido pedido = request.toModel(); pedido.setStatus(Status.PENDENTE); 
 repository.save(pedido); 
 return new PedidoResponse(request, Status.PENDENTE); } }
  122. @RestController class FinalizaPedidoController { private PedidoRepository repository; @Transactional @PostMapping(“/api/pedidos/finaliza”) public

    PedidoResponse finaliza(@RequestBody PedidoRequest request) { Pedido pedido = request.toModel(); pedido.setStatus(Status.PENDENTE); 
 repository.save(pedido); 
 return new PedidoResponse(request, Status.PENDENTE); } } “Estamos processando seu pedido"
  123. @RestController class FinalizaPedidoController { private PedidoRepository repository; @Transactional @PostMapping(“/api/pedidos/finaliza”) public

    PedidoResponse finaliza(@RequestBody PedidoRequest request) { Pedido pedido = request.toModel(); pedido.setStatus(Status.PENDENTE); 
 repository.save(pedido); 
 return new PedidoResponse(request, Status.PENDENTE); } }
  124. db servidor navegador

  125. db servidor navegador fi la

  126. db servidor navegador fi la

  127. db servidor navegador requisição fi la

  128. db servidor navegador requisição INSERT fi la

  129. db servidor navegador requisição INSERT fi la resposta

  130. e em algum momento um processo consumiria a fi la…

  131. db servidor navegador requisição INSERT fi

  132. quem executa? db servidor navegador requisição INSERT fi

  133. None
  134. db servidor navegador requisição INSERT fi la

  135. db servidor navegador requisição INSERT fi la

  136. db servidor navegador requisição INSERT fi la

  137. db servidor navegador requisição INSERT fi la

  138. e a vantagem?

  139. db servidor navegador requisição INSERT fi la

  140. db servidor requisição INSERT fi la

  141. db servidor requisição INSERT fi la

  142. db servidor requisição INSERT fi la

  143. db servidor requisição INSERT x

  144. INSERT

  145. INSERT

  146. Mas antes…

  147. Como agendar tarefas com Spring Boot?

  148. @Component public class OneJob { @Scheduled(fixedDelay = 60_000) public void

    runQuiteOften() { // lógica do job vai aqui } }
  149. @Component public class OneJob { @Scheduled(fixedDelay = 60_000) public void

    runQuiteOften() { // lógica do job vai aqui } }
  150. @Component public class OneJob { @Scheduled(fixedDelay = 60_000) public void

    runQuiteOften() { // lógica do job vai aqui } }
  151. @Component public class OneJob { @Scheduled(fixedDelay = 60_000) public void

    runQuiteOften() { // lógica do job vai aqui } }
  152. @Component public class OneJob { public void runQuiteOften() { //

    lógica do job vai aqui } }
  153. @Component public class OneJob { @Scheduled public void runQuiteOften() {

    // lógica do job vai aqui } }
  154. @Component public class OneJob { @Scheduled(fixedDelay = 60_000) public void

    runQuiteOften() { // lógica do job vai aqui } }
  155. @Component public class OneJob { @Scheduled(fixedDelay = 60_000) public void

    runQuiteOften() { // lógica do job vai aqui } }
  156. E nossa tarefa?

  157. db servidor requisição INSERT

  158. db servidor requisição INSERT x

  159. @Component public class FinalizaPedidoJob { private PedidoRepository repository; @Scheduled(fixedDelay =

    60_000) public void execute() { List<Pedido> pedidos = repository.findAllByStatus(Status.PENDENTE); pedidos.forEach(pedido -> { // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal pedido.setStatus(Status.CONFIRMADO); repository.save(pedido); }); } }
  160. @Component public class FinalizaPedidoJob { private PedidoRepository repository; @Scheduled(fixedDelay =

    60_000) public void execute() { List<Pedido> pedidos = repository.findAllByStatus(Status.PENDENTE); pedidos.forEach(pedido -> { // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal pedido.setStatus(Status.CONFIRMADO); repository.save(pedido); }); } }
  161. @Component public class FinalizaPedidoJob { private PedidoRepository repository; @Scheduled(fixedDelay =

    60_000) public void execute() { List<Pedido> pedidos = repository.findAllByStatus(Status.PENDENTE); pedidos.forEach(pedido -> { // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal pedido.setStatus(Status.CONFIRMADO); repository.save(pedido); }); } }
  162. @Component public class FinalizaPedidoJob { private PedidoRepository repository; @Scheduled(fixedDelay =

    60_000) public void execute() { List<Pedido> pedidos = repository.findAllByStatus(Status.PENDENTE); pedidos.forEach(pedido -> { // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal pedido.setStatus(Status.CONFIRMADO); repository.save(pedido); }); } }
  163. @Component public class FinalizaPedidoJob { private PedidoRepository repository; @Scheduled(fixedDelay =

    60_000) public void execute() { List<Pedido> pedidos = repository.findAllByStatus(Status.PENDENTE); pedidos.forEach(pedido -> { // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal pedido.setStatus(Status.CONFIRMADO); repository.save(pedido); }); } }
  164. @Component public class FinalizaPedidoJob { private PedidoRepository repository; @Scheduled(fixedDelay =

    60_000) public void execute() { List<Pedido> pedidos = repository.findAllByStatus(Status.PENDENTE); pedidos.forEach(pedido -> { // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal pedido.setStatus(Status.CONFIRMADO); repository.save(pedido); }); } }
  165. @Component public class FinalizaPedidoJob { private PedidoRepository repository; @Scheduled(fixedDelay =

    60_000) public void execute() { List<Pedido> pedidos = repository.findAllByStatus(Status.PENDENTE); pedidos.forEach(pedido -> { // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal pedido.setStatus(Status.CONFIRMADO); repository.save(pedido); }); } }
  166. @Repository public interface PedidoRepository extends JpaRepository<Pedido, Long> { public List<Pedido>

    findAllByStatus(Status status); }
  167. @Repository public interface PedidoRepository extends JpaRepository<Pedido, Long> { public List<Pedido>

    findAllByStatus(Status status); }
  168. select p.* from pedido p where p.status = 'PENDENTE' order

    by p.criado_em asc
  169. ID STATUS 1001 PENDENTE 1002 PENDENTE 1003 PENDENTE 1004 PENDENTE

  170. INSERT

  171. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 CONFIRMADO 1004 CONFIRMADO

  172. Uhuull!

  173. E SE…

  174. …rodarmos a aplicação em cluster?

  175. None
  176. None
  177. None
  178. None
  179. ID STATUS 1001 PENDENTE 1002 PENDENTE 1003 PENDENTE 1004 PENDENTE

  180. ID STATUS 1001 PENDENTE 1002 PENDENTE 1003 PENDENTE 1004 PENDENTE

  181. ID STATUS 1001 PENDENTE 1002 PENDENTE 1003 PENDENTE 1004 PENDENTE

  182. ID STATUS 1001 CONFIRMADO 1002 PENDENTE 1003 PENDENTE 1004 PENDENTE

  183. ID STATUS 1001 CONFIRMADO 1002 PENDENTE 1003 PENDENTE 1004 PENDENTE

  184. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 PENDENTE 1004 PENDENTE

  185. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 PENDENTE 1004 PENDENTE

  186. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 CONFIRMADO 1004 PENDENTE

  187. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 CONFIRMADO 1004 PENDENTE

  188. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 CONFIRMADO 1004 CONFIRMADO

  189. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 CONFIRMADO 1004 CONFIRMADO

  190. None
  191. ID STATUS 1001 PENDENTE 1002 PENDENTE 1003 PENDENTE 1004 PENDENTE

  192. ID STATUS 1001 PENDENTE 1002 PENDENTE 1003 PENDENTE 1004 PENDENTE

  193. ID STATUS 1001 CONFIRMADO 1002 PENDENTE 1003 PENDENTE 1004 PENDENTE

  194. ID STATUS 1001 CONFIRMADO 1002 PENDENTE 1003 PENDENTE 1004 PENDENTE

    2x
  195. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 PENDENTE 1004 PENDENTE

    2x
  196. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 PENDENTE 1004 PENDENTE

    2x 2x
  197. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 CONFIRMADO 1004 PENDENTE

    2x 2x
  198. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 CONFIRMADO 1004 PENDENTE

    2x 2x 2x
  199. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 CONFIRMADO 1004 CONFIRMADO

    2x 2x 2x
  200. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 CONFIRMADO 1004 CONFIRMADO

    2x 2x 2x 2x
  201. Oops!

  202. o desenho da solução deve ser pensado para multi-threaded *

    desenho e implementação *
  203. “Fácil! Vamos 
 LOCKAR o método do Job”

  204. Synchronized?

  205. @Component public class FinalizaPedidoJob { @Scheduled(...) public void execute() {

    // executa lógica aqui } }
  206. @Component public class FinalizaPedidoJob { @Scheduled(...) public synchronized void execute()

    { // executa lógica aqui } }
  207. None
  208. None
  209. None
  210. None
  211. @Component

  212. Distributed Lock?

  213. @Scheduled(...) public void execute() { if (isLeader()) { // executa

    lógica aqui } }
  214. @Scheduled(...) public void execute() { if (isLeader()) { // executa

    lógica aqui } }
  215. None
  216. None
  217. None
  218. None
  219. None
  220. None
  221. None
  222. None
  223. None
  224. None
  225. None
  226. Não!

  227. Que tal…

  228. Banco de dados 
 relacional

  229. None
  230. @Repository public interface PedidoRepository extends JpaRepository<Pedido, Long> { public List<Pedido>

    findAllByStatus(Status status); }
  231. @Repository public interface PedidoRepository extends JpaRepository<Pedido, Long> { @Lock(LockModeType.PESSIMISTIC_WRITE) public

    List<Pedido> findAllByStatus(Status status); }
  232. select p.* from pedido p where p.status = 'PENDENTE' order

    by p.criado_em asc 

  233. select p.* from pedido p where p.status = 'PENDENTE' order

    by p.criado_em asc 
 for update
  234. ID STATUS 1001 PENDENTE 1002 PENDENTE 1003 PENDENTE ... ...

  235. ID STATUS 1001 PENDENTE 1002 PENDENTE 1003 PENDENTE ... ...

  236. ID STATUS 1001 PENDENTE 1002 PENDENTE 1003 PENDENTE ... ...

  237. ID STATUS 1001 PENDENTE 1002 PENDENTE 1003 PENDENTE ... ...

  238. ID STATUS 1001 CONFIRMADO 1002 PENDENTE 1003 PENDENTE ... ...

  239. ID STATUS 1001 CONFIRMADO 1002 PENDENTE 1003 PENDENTE ... ...

  240. ID STATUS 1001 CONFIRMADO 1002 PENDENTE 1003 PENDENTE ... ...

  241. ID STATUS 1001 CONFIRMADO 1002 PENDENTE 1003 PENDENTE ... ...

  242. ID STATUS 1001 CONFIRMADO 1002 PENDENTE 1003 PENDENTE ... ...

  243. ID STATUS 1001 CONFIRMADO 1002 PENDENTE 1003 PENDENTE ... ...

  244. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 PENDENTE ... ...

  245. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 PENDENTE ... ...

  246. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 PENDENTE ... ...

  247. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 PENDENTE ... ...

  248. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 CONFIRMADO ... ...

  249. SIM!

  250. MAS…

  251. O problema do FOR UPDATE é que não escala!

  252. None
  253. None
  254. None
  255. Quando queremos…

  256. None
  257. Para isso…

  258. 9630 9631 QUEUE ( fi la) 9629

  259. 9630 9631 QUEUE ( fi la) producer 9629 produz: 


    1000 itens/min
  260. 9630 9631 QUEUE ( fi la) producer consumer 9629 produz:

    
 1000 itens/min consume: 
 600 itens/min
  261. 9630 9631 QUEUE ( fi la) producer consumer 9629 produz:

    
 1000 itens/min consumer consume: 
 400 itens/min consume: 
 600 itens/min
  262. 9630 9631 QUEUE ( fi la) producer consumer 9629 produz:

    
 1000 itens/min consumer consume: 
 400 itens/min consume: 
 600 itens/min
  263. None
  264. Não!

  265. Que tal…

  266. Banco de dados 
 relacional

  267. @Repository public interface PedidoRepository extends JpaRepository<Pedido, Long> { @QueryHints({ @QueryHint(

    name = "javax.persistence.lock.timeout", value = LockOptions.SKIP_LOCKED) // org.hibernate.LockOptions }) @Lock(LockModeType.PESSIMISTIC_WRITE) public List<Pedido> findAllByStatus(Status status); }
  268. @Repository public interface PedidoRepository extends JpaRepository<Pedido, Long> { @QueryHints({ @QueryHint(

    name = "javax.persistence.lock.timeout", value = LockOptions.SKIP_LOCKED) // org.hibernate.LockOptions }) @Lock(LockModeType.PESSIMISTIC_WRITE) public List<Pedido> findAllByStatus(Status status); }
  269. select p.* from pedido p where p.status = 'PENDENTE' order

    by p.criado_em asc 
 for update
  270. select p.* from pedido p where p.status = 'PENDENTE' order

    by p.criado_em asc 
 for update skip locked
  271. ID STATUS 1001 PENDENTE 1002 PENDENTE 1003 PENDENTE ... ...

  272. ID STATUS 1001 PENDENTE 1002 PENDENTE 1003 PENDENTE ... ...

  273. ID STATUS 1001 PENDENTE 1002 PENDENTE 1003 PENDENTE ... ...

  274. ID STATUS 1001 PENDENTE 1002 PENDENTE 1003 PENDENTE ... ...

  275. ID STATUS 1001 PENDENTE 1002 PENDENTE 1003 PENDENTE ... ...

  276. ID STATUS 1001 PENDENTE 1002 PENDENTE 1003 PENDENTE ... ...

  277. ID STATUS 1001 PENDENTE 1002 PENDENTE 1003 PENDENTE ... ...

  278. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 PENDENTE ... ...

  279. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 PENDENTE ... ...

  280. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 PENDENTE ... ...

  281. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 PENDENTE ... ...

  282. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 PENDENTE ... ...

  283. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 PENDENTE ... ...

  284. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 PENDENTE ... ...

  285. ID STATUS 1001 CONFIRMADO 1002 CONFIRMADO 1003 PENDENTE ... ...

  286. Ou seja…

  287. ID STATUS 1001 PENDENTE 1002 PENDENTE 1003 PENDENTE 1004 PENDENTE

  288. ID STATUS 1001 PENDENTE 1002 PENDENTE 1003 PENDENTE 1004 PENDENTE

  289. SIM!

  290. E isso escala?

  291. single-threaded funciona bem…

  292. single-threaded funciona bem… https://www.pgcon.org/2016/schedule/track/Applications/929.en.html

  293. multi-threaded com FOR UPDATE…

  294. multi-threaded com FOR UPDATE… https://www.pgcon.org/2016/schedule/track/Applications/929.en.html

  295. multi-threaded com SKIP LOCKED…

  296. multi-threaded com SKIP LOCKED… https://www.pgcon.org/2016/schedule/track/Applications/929.en.html

  297. UFA!

  298. Mas calma ae que tem mais…

  299. Listen / Notify

  300. None
  301. None
  302. None
  303. None
  304. None
  305. None
  306. None
  307. None
  308. None
  309. None
  310. None
  311. None
  312. None
  313. None
  314. None
  315. None
  316. Publisher

  317. Publisher Subscriber

  318. Publisher Subscriber Topic

  319. Broker Publisher Subscriber Topic

  320. Pub/Sub

  321. None
  322. None
  323. None
  324. None
  325. None
  326. None
  327. None
  328. None
  329. None
  330. None
  331. None
  332. Não!

  333. Que tal…

  334. Banco de dados 
 relacional

  335. Listen Notify

  336. None
  337. None
  338. None
  339. None
  340. Para enviar uma mensagem…

  341. pg_notify() NOTIFY is a utility command for sending noti fi

    cations to other sessions connected to the same database listening on a channel speci fi ed with the LISTEN command.
  342. pg_notify(<topic>, <message>)

  343. pg_notify(<topic>, <message>)

  344. pg_notify(<topic>, <message>)

  345. pg_notify(<topic>, <message>)

  346. pg_notify( 
 ‘pedidos-finalizados’, ‘{“id”: 42, “nome”: “iPhone 13”}’ 
 )

  347. pg_notify( 
 ‘pedidos-finalizados’, ‘{“id”: 42, “nome”: “iPhone 13”}’ 
 )

  348. pg_notify( 
 ‘pedidos-finalizados’, ‘{“id”: 42, “nome”: “iPhone 13”}’ 
 )

  349. Enquanto para receber as mensagens…

  350. LISTEN LISTEN is a utility command which registers the current

    session as a listener on the named channel.
  351. LISTEN <topic>

  352. LISTEN <topic>

  353. LISTEN <topic>

  354. LISTEN ‘pedidos-finalizados’

  355. LISTEN ‘pedidos-finalizados’

  356. E onde ele pode ser útil?

  357. @Component public class FinalizaPedidoJob { private PedidoRepository repository; @Scheduled(fixedDelay =

    60_000) public void execute() { List<Pedido> pedidos = repository.findAllByStatus(Status.PENDENTE); pedidos.forEach(pedido -> { // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal pedido.setStatus(Status.CONFIRMADO); repository.save(pedido); }); } }
  358. @Component public class FinalizaPedidoJob { private PedidoRepository repository; @Scheduled(fixedDelay =

    60_000) public void execute() { List<Pedido> pedidos = repository.findAllByStatus(Status.PENDENTE); pedidos.forEach(pedido -> { // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal pedido.setStatus(Status.CONFIRMADO); repository.save(pedido); }); } }
  359. @Component public class FinalizaPedidoJob { private PedidoRepository repository; @Scheduled(fixedDelay =

    60_000) public void execute() { List<Pedido> pedidos = repository.findAllByStatus(Status.PENDENTE); pedidos.forEach(pedido -> { // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal pedido.setStatus(Status.CONFIRMADO); repository.save(pedido); }); } }
  360. @Component public class FinalizaPedidoJob { private PedidoRepository repository; @Scheduled(fixedDelay =

    60_000) public void execute() { List<Pedido> pedidos = repository.findAllByStatus(Status.PENDENTE); pedidos.forEach(pedido -> { // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal pedido.setStatus(Status.CONFIRMADO); repository.save(pedido); }); } }
  361. None
  362. INSERT PENDENTE PENDENTE PENDENTE

  363. INSERT PENDENTE PENDENTE PENDENTE

  364. INSERT PENDENTE PENDENTE PENDENTE

  365. INSERT PENDENTE PENDENTE PENDENTE

  366. INSERT CONFIRMADO CONFIRMADO PENDENTE

  367. INSERT CONFIRMADO CONFIRMADO PENDENTE

  368. @Component public class FinalizaPedidoJob { private PedidoRepository repository; @Scheduled(fixedDelay =

    60_000) public void execute() { List<Pedido> pedidos = repository.findAllByStatus(Status.PENDENTE); pedidos.forEach(pedido -> { // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal pedido.setStatus(Status.CONFIRMADO); repository.save(pedido); }); } }
  369. @Component public class FinalizaPedidoJob { private PedidoRepository repository; @Scheduled(fixedDelay =

    60_000) public void execute() { List<Pedido> pedidos = repository.findAllByStatus(Status.PENDENTE); pedidos.forEach(pedido -> { // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido // envia email de confirmação // gera nota fiscal pedido.setStatus(Status.CONFIRMADO); repository.save(pedido); }); } }
  370. @Component public class FinalizaPedidoJob { private PedidoRepository repository; @Scheduled(fixedDelay =

    60_000) public void execute() { List<Pedido> pedidos = repository.findAllByStatus(Status.PENDENTE); pedidos.forEach(pedido -> { // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido repository 
 .pgNotify("pedidos-finalizados", pedido.toJson()); pedido.setStatus(Status.CONFIRMADO); repository.save(pedido); }); } }
  371. @Component public class FinalizaPedidoJob { private PedidoRepository repository; @Scheduled(fixedDelay =

    60_000) public void execute() { List<Pedido> pedidos = repository.findAllByStatus(Status.PENDENTE); pedidos.forEach(pedido -> { // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido repository 
 .pgNotify("pedidos-finalizados", pedido.toJson()); pedido.setStatus(Status.CONFIRMADO); repository.save(pedido); }); } }
  372. @Component public class FinalizaPedidoJob { private PedidoRepository repository; @Scheduled(fixedDelay =

    60_000) public void execute() { List<Pedido> pedidos = repository.findAllByStatus(Status.PENDENTE); pedidos.forEach(pedido -> { // efetua pagamento no gateway // dá baixa no estoque // atualiza pedido repository 
 .pgNotify("pedidos-finalizados", pedido.toJson()); pedido.setStatus(Status.CONFIRMADO); repository.save(pedido); }); } }
  373. Para isso dar certo…

  374. INSERT PENDENTE PENDENTE PENDENTE

  375. INSERT PENDENTE PENDENTE PENDENTE

  376. INSERT PENDENTE PENDENTE PENDENTE

  377. INSERT PENDENTE PENDENTE PENDENTE

  378. INSERT PENDENTE PENDENTE PENDENTE

  379. INSERT PENDENTE LISTEN ‘pedidos-finalizados’ LISTEN ‘pedidos-finalizados’ PENDENTE PENDENTE

  380. INSERT PENDENTE LISTEN ‘pedidos-finalizados’ LISTEN ‘pedidos-finalizados’ pg_notify( 
 ‘pedidos-finalizados’, ‘{“id”:

    42, “nome”: ... }’ 
 ) PENDENTE PENDENTE
  381. INSERT CONFIRMADO CONFIRMADO PENDENTE LISTEN ‘pedidos-finalizados’ LISTEN ‘pedidos-finalizados’ pg_notify( 


    ‘pedidos-finalizados’, ‘{“id”: 42, “nome”: ... }’ 
 )
  382. E quem precisar escutar?

  383. INSERT CONFIRMADO CONFIRMADO PENDENTE LISTEN ‘pedidos-finalizados’ LISTEN ‘pedidos-finalizados’ pg_notify( 


  384. try (Connection cn = new org.postgresql.Driver().connect(url, properties)) { cn.createStatement().execute(“LISTEN ‘pedidos-finalizados’");

    for (;;) { PGNotification[] ns = ((PGConnection) cn).getNotifications(0); if (ns != null) { for (PGNotification n : ns) { System.out.println("pid=%s, event=%s, payload=%s".formatted( 
 n.getPID(), n.getName(), n.getParameter() )); // invoca logica de negocio } } break; } }
  385. try (Connection cn = new org.postgresql.Driver().connect(url, properties)) { cn.createStatement().execute(“LISTEN ‘pedidos-finalizados’");

    for (;;) { PGNotification[] ns = ((PGConnection) cn).getNotifications(0); if (ns != null) { for (PGNotification n : ns) { System.out.println("pid=%s, event=%s, payload=%s".formatted( 
 n.getPID(), n.getName(), n.getParameter() )); // invoca logica de negocio } } break; } }
  386. try (Connection cn = new org.postgresql.Driver().connect(url, properties)) { cn.createStatement().execute(“LISTEN ‘pedidos-finalizados’");

    for (;;) { PGNotification[] ns = ((PGConnection) cn).getNotifications(0); if (ns != null) { for (PGNotification n : ns) { System.out.println("pid=%s, event=%s, payload=%s".formatted( 
 n.getPID(), n.getName(), n.getParameter() )); // invoca logica de negocio } } break; } }
  387. try (Connection cn = new org.postgresql.Driver().connect(url, properties)) { cn.createStatement().execute(“LISTEN ‘pedidos-finalizados’");

    for (;;) { PGNotification[] ns = ((PGConnection) cn).getNotifications(0); if (ns != null) { for (PGNotification n : ns) { System.out.println("pid=%s, event=%s, payload=%s".formatted( 
 n.getPID(), n.getName(), n.getParameter() )); // invoca logica de negocio } } break; } } Ouvindo um topico!
  388. try (Connection cn = new org.postgresql.Driver().connect(url, properties)) { cn.createStatement().execute(“LISTEN ‘pedidos-finalizados’");

    for (;;) { PGNotification[] ns = ((PGConnection) cn).getNotifications(0); if (ns != null) { for (PGNotification n : ns) { System.out.println("pid=%s, event=%s, payload=%s".formatted( 
 n.getPID(), n.getName(), n.getParameter() )); // invoca logica de negocio } } break; } }
  389. try (Connection cn = new org.postgresql.Driver().connect(url, properties)) { cn.createStatement().execute(“LISTEN ‘pedidos-finalizados’");

    for (;;) { PGNotification[] ns = ((PGConnection) cn).getNotifications(0); if (ns != null) { for (PGNotification n : ns) { System.out.println("pid=%s, event=%s, payload=%s".formatted( 
 n.getPID(), n.getName(), n.getParameter() )); // invoca logica de negocio } } break; } }
  390. try (Connection cn = new org.postgresql.Driver().connect(url, properties)) { cn.createStatement().execute(“LISTEN ‘pedidos-finalizados’");

    for (;;) { PGNotification[] ns = ((PGConnection) cn).getNotifications(0); if (ns != null) { for (PGNotification n : ns) { System.out.println("pid=%s, event=%s, payload=%s".formatted( 
 n.getPID(), n.getName(), n.getParameter() )); // invoca logica de negocio } } break; } } Bloqueia 
 FOREVER até receber novos eventos
  391. try (Connection cn = new org.postgresql.Driver().connect(url, properties)) { cn.createStatement().execute(“LISTEN ‘pedidos-finalizados’");

    for (;;) { PGNotification[] ns = ((PGConnection) cn).getNotifications(0); if (ns != null) { for (PGNotification n : ns) { System.out.println("pid=%s, event=%s, payload=%s".formatted( 
 n.getPID(), n.getName(), n.getParameter() )); // invoca logica de negocio } } break; } }
  392. try (Connection cn = new org.postgresql.Driver().connect(url, properties)) { cn.createStatement().execute(“LISTEN ‘pedidos-finalizados’");

    for (;;) { PGNotification[] ns = ((PGConnection) cn).getNotifications(0); if (ns != null) { for (PGNotification n : ns) { System.out.println("pid=%s, event=%s, payload=%s".formatted( 
 n.getPID(), n.getName(), n.getParameter() )); // invoca logica de negocio } } break; } }
  393. try (Connection cn = new org.postgresql.Driver().connect(url, properties)) { cn.createStatement().execute(“LISTEN ‘pedidos-finalizados’");

    for (;;) { PGNotification[] ns = ((PGConnection) cn).getNotifications(0); if (ns != null) { for (PGNotification n : ns) { System.out.println("pid=%s, event=%s, payload=%s".formatted( 
 n.getPID(), n.getName(), n.getParameter() )); // invoca logica de negocio } } break; } }
  394. try (Connection cn = new org.postgresql.Driver().connect(url, properties)) { cn.createStatement().execute(“LISTEN ‘pedidos-finalizados’");

    for (;;) { PGNotification[] ns = ((PGConnection) cn).getNotifications(0); if (ns != null) { for (PGNotification n : ns) { System.out.println("pid=%s, event=%s, payload=%s".formatted( 
 n.getPID(), n.getName(), n.getParameter() )); // invoca logica de negocio } } break; } }
  395. try (Connection cn = new org.postgresql.Driver().connect(url, properties)) { cn.createStatement().execute(“LISTEN ‘pedidos-finalizados’");

    for (;;) { PGNotification[] ns = ((PGConnection) cn).getNotifications(0); if (ns != null) for (PGNotification n : ns) { System.out.println("pid=%s, event=%s, payload=%s".formatted( 
 n.getPID(), n.getName(), n.getParameter() )); // invoca logica de negocio } break; } }
  396. A vantagem desse modelo…

  397. INSERT CONFIRMADO CONFIRMADO PENDENTE LISTEN ‘pedidos-finalizados’ LISTEN ‘pedidos-finalizados’ pg_notify( 


  398. INSERT CONFIRMADO CONFIRMADO PENDENTE LISTEN ‘pedidos-finalizados’ LISTEN ‘pedidos-finalizados’ pg_notify( 


  399. INSERT CONFIRMADO CONFIRMADO PENDENTE LISTEN ‘pedidos-finalizados’ LISTEN ‘pedidos-finalizados’ pg_notify( 


  400. INSERT CONFIRMADO CONFIRMADO PENDENTE LISTEN ‘pedidos-finalizados’ LISTEN ‘pedidos-finalizados’ pg_notify( 


  401. Recapitulando…

  402. None
  403. CACHE BALANCEAMENTO DE CARGA PROCESSAMENTO ASSINCRONO

  404. INSERT CONFIRMADO CONFIRMADO PENDENTE LISTEN ‘pedidos-finalizados’ LISTEN ‘pedidos-finalizados’ pg_notify( 


    ‘pedidos-finalizados’, ‘{“id”: 42, “nome”: ... }’ 
 )
  405. INSERT CONFIRMADO CONFIRMADO PENDENTE LISTEN ‘pedidos-finalizados’ LISTEN ‘pedidos-finalizados’ pg_notify( 


    ‘pedidos-finalizados’, ‘{“id”: 42, “nome”: ... }’ 
 )
  406. INSERT CONFIRMADO CONFIRMADO PENDENTE LISTEN ‘pedidos-finalizados’ LISTEN ‘pedidos-finalizados’ pg_notify( 


    ‘pedidos-finalizados’, ‘{“id”: 42, “nome”: ... }’ 
 ) Queue
  407. INSERT CONFIRMADO CONFIRMADO PENDENTE LISTEN ‘pedidos-finalizados’ LISTEN ‘pedidos-finalizados’ pg_notify( 


    ‘pedidos-finalizados’, ‘{“id”: 42, “nome”: ... }’ 
 ) Pub/Sub
  408. Banco de dados 
 relacional

  409. CONCLUINDO

  410. None
  411. None
  412. Não!

  413. None
  414. None
  415. None
  416. “Use o banco apenas como repositório de dados.”

  417. “Use o banco apenas como repositório de dados.” Não!

  418. @rponte Rafael Ponte