Share
Distributed Scheduling with Spring Boot: the challenges and pitfalls of implementing a background job
(🎥 RECORDING: https://www.youtube.com/watch?v=ghpljMg8Ecc)
Sooner or later a developer will implement his/her first background job using Java and Spring Boot, and what usually is a simple task for the majority of systems might become a nightmare in scenarios that need to deal with high performance, parallelism, distributed systems and a large volume of data. Scenarios like those hide several issues which many developers are not used to, such as large volumes of data, network failures, data inconsistency, out-of-memory errors and even taking the whole system down.
Although it seems controversial, dealing with many of these problems does not require hype technologies or services, but solid distributed systems fundamentals. This talk will present how an experienced developer implements a background job with Java and Spring Boot taking into consideration the main challenges and pitfalls it brings along, and how he/she designs a solution for high-performance, resilience and horizontal scalability at the same time he/she takes advantage of many modules of Spring Boot, Hibernate and the relational database.
If you still believe that a background job is a simple task, so this talk is for you!
Rafael Ponte
Transcript
376 slides
-
DISTRIBUTED SCHEDULING | Spring Boot the challenges and pitfalls of
implementing a background job Rafael Ponte | @rponte -
@Component public class OneJob { @Scheduled(fixedDelay = 60_000) public void
runQuiteOften() { // lógica do job vai aqui } } -
@Component public class OneJob { @Scheduled(fixedDelay = 60_000) public void
runQuiteOften() { // lógica do job vai aqui } } -
@Component public class OneJob { @Scheduled(fixedDelay = 60_000) public void
runQuiteOften() { // job logic goes here } } -
@Component public class OneJob { @Scheduled(fixedDelay = 60_000) public void
runQuiteOften() { // job logic goes here } } -
@Component public class OneJob { public void runQuiteOften() { //
job logic goes here } } -
@Component public class OneJob { @Scheduled public void runQuiteOften() {
// job logic goes here } } -
@Component public class OneJob { @Scheduled(fixedDelay = 60_000) public void
runQuiteOften() { // job logic goes here } } -
@Component public class OneJob { @Scheduled(fixedDelay = 60_000) public void
runQuiteOften() { // job logic goes here } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } Naive Solution -
@rponte Rafael Ponte Brazilian Software Engineer at aka Prince of
the Ocean aka Maharaja of the Legacies 19+ years of experience working with Java and distributed systems Loves working with "boring techs" -
DISTRIBUTED SCHEDULING | Spring Boot the challenges and pitfalls of
implementing a background job Rafael Ponte | @rponte -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
select p.* from proposal p where p.status = 'ELIGIBLE' order
by p.created_at asc -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Scheduled(…) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderBy…; proposals.forEach(proposal
-> { CardDataResponse cardData = cardsClient.findCardByProposalId(…); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } -
@Scheduled(…) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderBy…; proposals.forEach(proposal
-> { CardDataResponse cardData = cardsClient.findCardByProposalId(…); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } -
@Scheduled(…) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderBy…; proposals.forEach(proposal
-> { CardDataResponse cardData = cardsClient.findCardByProposalId(…); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } Inserts a new card Updates a proposal with its card -
-- Inserts a new credit card INSERT INTO card (id,
cardNumber) VALUES (42, ’5260-9991-9040-0001’); -- Updates the proposal with its credit card UPDATE proposal SET card_id = 42, status = 'ELIGIBLE_WITH_ATTACHED_CARD' WHERE id = 7031; -
-- Inserts a new credit card INSERT INTO card (id,
cardNumber) VALUES (42, ’5260-9991-9040-0001’); -- Updates the proposal with its credit card UPDATE proposal SET card_id = 42, status = 'ELIGIBLE_WITH_ATTACHED_CARD' WHERE id = 7031; -
-- Inserts a new credit card INSERT INTO card (id,
cardNumber) VALUES (42, ’5260-9991-9040-0001’); -- Updates the proposal with its credit card UPDATE proposal SET card_id = 42, status = 'ELIGIBLE_WITH_ATTACHED_CARD' WHERE id = 7031; -
@Scheduled(…) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderBy…; proposals.forEach(proposal
-> { CardDataResponse cardData = cardsClient.findCardByProposalId(…); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } -
@Scheduled(…) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderBy…; proposals.forEach(proposal
-> { CardDataResponse cardData = cardsClient.findCardByProposalId(…); Card newCard = cardData.toModel(); cardRepository.save(newCard); // begins and commits a transaction proposal.attachTo(newCard); proposalRepository.save(proposal); // begins and commits a transaction }); } -
@Scheduled(…) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderBy…; proposals.forEach(proposal
-> { CardDataResponse cardData = cardsClient.findCardByProposalId(…); Card newCard = cardData.toModel(); cardRepository.save(newCard); // begins and commits a transaction proposal.attachTo(newCard); proposalRepository.save(proposal); // begins and commits a transaction }); } it’s similar to auto_commit = true😱 -
@Scheduled(…) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderBy…; proposals.forEach(proposal
-> { CardDataResponse cardData = cardsClient.findCardByProposalId(…); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } -
@Scheduled(…) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderBy…; proposals.forEach(proposal
-> { CardDataResponse cardData = cardsClient.findCardByProposalId(…); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } -
@Scheduled(…) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderBy…; proposals.forEach(proposal
-> { CardDataResponse cardData = cardsClient.findCardByProposalId(…); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } -
@Scheduled(…) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderBy…; proposals.forEach(proposal
-> { CardDataResponse cardData = cardsClient.findCardByProposalId(…); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } -
@Scheduled(…) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderBy…; proposals.forEach(proposal
-> { CardDataResponse cardData = cardsClient.findCardByProposalId(…); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } -
ID STATUS CARD_ID 7028 NOT_ELIGIBLE 39 7029 NOT_ELIGIBLE 40 7030
ELIGIBLE_WITH_ATTACHED_CARD 41 7031 ELIGIBLE <null> 7032 ELIGIBLE_WITH_ATTACHED_CARD 96 ... ... ... ID CARD_NUMBER 40 5260-9991-9040-0040 41 5260-9991-9040-0041 42 5260-9991-9040-00042 Proposal Card Database -
ID STATUS CARD_ID 7028 NOT_ELIGIBLE 39 7029 NOT_ELIGIBLE 40 7030
ELIGIBLE_WITH_ATTACHED_CARD 41 7031 ELIGIBLE <null> 7032 ELIGIBLE_WITH_ATTACHED_CARD 96 ... ... ... ID CARD_NUMBER 40 5260-9991-9040-0040 41 5260-9991-9040-0041 42 5260-9991-9040-00042 Proposal Card Database -
ID STATUS CARD_ID 7028 NOT_ELIGIBLE 39 7029 NOT_ELIGIBLE 40 7030
ELIGIBLE_WITH_ATTACHED_CARD 41 7031 ELIGIBLE <null> 7032 ELIGIBLE_WITH_ATTACHED_CARD 96 ... ... ... ID CARD_NUMBER 40 5260-9991-9040-0040 41 5260-9991-9040-0041 42 5260-9991-9040-00042 Proposal Card Database -
ID STATUS CARD_ID 7028 NOT_ELIGIBLE 39 7029 NOT_ELIGIBLE 40 7030
ELIGIBLE_WITH_ATTACHED_CARD 41 7031 ELIGIBLE <null> 7032 ELIGIBLE_WITH_ATTACHED_CARD 96 ... ... ... ID CARD_NUMBER 40 5260-9991-9040-0040 41 5260-9991-9040-0041 42 5260-9991-9040-00042 Proposal Card Database -
Runs every 1 minute ID COL1 COL2 1 2 3
4 ID COL1 COL2 1 2 3 4 Data corruption error -
Runs every 1 minute Runs every 5 minutes ID COL1
COL2 1 2 3 4 ID COL1 COL2 1 2 3 4 ID COL1 COL2 1 2 3 4 ID COL1 COL2 1 2 3 4 Data corruption error Fix corrupted data -
Runs every 1 minute Runs every 5 minute ID COL1
COL2 1 2 3 4 ID COL1 COL2 1 2 3 4 ID COL1 COL2 1 2 3 4 ID COL1 COL2 1 2 3 4 Data corruption error Fix corrupted data No! -
@Scheduled(…) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderBy…; proposals.forEach(proposal
-> { CardDataResponse cardData = cardsClient.findCardByProposalId(…); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } -
@Scheduled(…) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderBy…; proposals.forEach(proposal
-> { CardDataResponse cardData = cardsClient.findCardByProposalId(…); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } -
@Transactional @Scheduled(…) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderBy…;
proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(…); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } -
@Transactional @Scheduled(…) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderBy…;
proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(…); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } -
@Transactional @Scheduled(…) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderBy…;
proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(…); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } -
@Transactional @Scheduled(…) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderBy…;
proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(…); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } You got Recoverability! -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Transactional @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Transactional @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
select p.* from proposal p where p.status = 'ELIGIBLE' order
by p.created_at asc -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Transactional @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Transactional @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findAllByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Repository public interface ProposalRepository extends JpaRepository<Proposal, UUID> { public List<Proposal>
findAllByStatusOrderByCreatedAtAsc(ProposalStatus status); } -
@Repository public interface ProposalRepository extends JpaRepository<Proposal, UUID> { public List<Proposal>
findAllByStatusOrderByCreatedAtAsc(ProposalStatus status); } -
@Repository public interface ProposalRepository extends JpaRepository<Proposal, UUID> { public List<Proposal>
findAllByStatusOrderByCreatedAtAsc(ProposalStatus status); } -
@Repository public interface ProposalRepository extends JpaRepository<Proposal, UUID> { public List<Proposal>
findAllByStatusOrderByCreatedAtAsc(ProposalStatus status); } -
select p.* from proposal p where p.status = 'ELIGIBLE' order
by p.created_at asc -
select p.* from proposal p where p.status = 'ELIGIBLE' order
by p.created_at asc -
select p.* from proposal p where p.status = 'ELIGIBLE' order
by p.created_at asc limit 50 -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Transactional @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Transactional @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Transactional @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } this loop will end very EARLY! -
@Component public class AttachCardsToProposalsJob { private CardsClient cardsClient; private CardRepository
cardRepository; private ProposalRepository proposalRepository; @Transactional @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(ELIGIBLE); proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } It will only run on the NEXT execution! -
@Transactional @Scheduled(fixedDelay = 60_000) public void execute() { List<Proposal> proposals
= proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(…); proposals.forEach(proposal -> { // process each proposal }); } -
@Transactional @Scheduled(fixedDelay = 60_000) public void execute() { while (true)
{ List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(…); if (proposals.isEmpty()) { break; } proposals.forEach(proposal -> { // process each proposal }); } } -
@Transactional @Scheduled(fixedDelay = 60_000) public void execute() { while (true)
{ List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(…); if (proposals.isEmpty()) { break; } proposals.forEach(proposal -> { // process each proposal }); } } -
@Transactional @Scheduled(fixedDelay = 60_000) public void execute() { while (true)
{ List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(…); if (proposals.isEmpty()) { break; } proposals.forEach(proposal -> { // process each proposal }); } } -
@Component public class AttachCardsToProposalsJob { // dependencies and constants @Transactional
@Scheduled(fixedDelay = 60_000) public void execute() { while (true) { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(ELIGIBLE); if (proposals.isEmpty()) { break; } proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } } -
@Component public class AttachCardsToProposalsJob { // dependencies and constants @Transactional
@Scheduled(fixedDelay = 60_000) public void execute() { while (true) { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(ELIGIBLE); if (proposals.isEmpty()) { break; } proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } } - fetching only 50 objects at a time -
@Component public class AttachCardsToProposalsJob { // dependencies and constants @Transactional
@Scheduled(fixedDelay = 60_000) public void execute() { while (true) { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(ELIGIBLE); if (proposals.isEmpty()) { break; } proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } } - creating a new reference - fetching only 50 objects at a time -
@Component public class AttachCardsToProposalsJob { // dependencies and constants @Transactional
@Scheduled(fixedDelay = 60_000) public void execute() { while (true) { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(ELIGIBLE); if (proposals.isEmpty()) { break; } proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } } - exiting the loop at the right time - creating a new reference - fetching only 50 objects at a time -
@Component public class AttachCardsToProposalsJob { // dependências e constantes @Transactional
@Scheduled(fixedDelay = 60_000) public void execute() { while (true) { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(ELIGIBLE); if (proposals.isEmpty()) { break; } proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } } -
@Repository public interface ProposalRepository extends JpaRepository<Proposal, UUID> { public List<Proposal>
findAllByStatusOrderByCreatedAtAsc(ProposalStatus status); } -
@Component public class AttachCardsToProposalsJob { // dependencies and constants @Transactional
@Scheduled(fixedDelay = 60_000) public void execute() { while (true) { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(ELIGIBLE); if (proposals.isEmpty()) { break; } proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } } -
@Component public class AttachCardsToProposalsJob { // dependencies and constants @Scheduled(fixedDelay
= 60_000) public void execute() { while (true) { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(ELIGIBLE); if (proposals.isEmpty()) { break; } proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } } begins and ends a transaction -
@Component public class AttachCardsToProposalsJob { // dependencies and constants @Scheduled(fixedDelay
= 60_000) public void execute() { while (true) { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(ELIGIBLE); if (proposals.isEmpty()) { break; } proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); } } } -
@Component public class AttachCardsToProposalsJob { // dependencies and constants @Scheduled(fixedDelay
= 60_000) public void execute() { while (true) { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(ELIGIBLE); if (proposals.isEmpty()) { break; } proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); // begins and commits a transaction proposal.attachTo(newCard); proposalRepository.save(proposal); // begins and commits a transaction }); } } } We gave up on data consistency! 🤡 -
@Component public class AttachCardsToProposalsJob { // dependencies and constants @Scheduled(fixedDelay
= 60_000) public void execute() { while (true) { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(ELIGIBLE); if (proposals.isEmpty()) { break; } proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); // begins and commits a transaction proposal.attachTo(newCard); proposalRepository.save(proposal); // begins and commits a transaction }); } } } We give up on data consistency! Nooo! -
@Component public class AttachCardsToProposalsJob { @Transactional @Scheduled(fixedDelay = 60_000) public
void execute() { while (true) { // executes the logic within a transactional scope } } } -
@Component public class AttachCardsToProposalsJob { @Transactional @Scheduled(fixedDelay = 60_000) public
void execute() { while (true) { // executes the logic within a transactional scope } } } -
@Component public class AttachCardsToProposalsJob { @Transactional @Scheduled(fixedDelay = 60_000) public
void execute() { while (true) { @Transactional.execute(transaction -> { // executes the logic within a transactional scope });; } } } -
@Component public class AttachCardsToProposalsJob { @Scheduled(fixedDelay = 60_000) public void
execute() { while (true) { @Transactional.execute(transaction -> { // executes the logic within a transactional scope }); } } } -
@Component public class AttachCardsToProposalsJob { @Scheduled(fixedDelay = 60_000) public void
execute() { while (true) { @Transactional.execute(transaction -> { // executes the logic within a transactional scope }); } } } -
@Component public class AttachCardsToProposalsJob { @Scheduled(fixedDelay = 60_000) public void
execute() { while (true) { @Transactional.execute(transaction -> { // executes the logic within a transactional scope }); } } } -
@Component public class AttachCardsToProposalsJob { @Scheduled(fixedDelay = 60_000) public void
execute() { while (true) { @Transactional.execute(transaction -> { // executes the logic within a transactional scope }); } } } -
@Component public class AttachCardsToProposalsJob { @Scheduled(fixedDelay = 60_000) public void
execute() { while (true) { @Transactional.execute(transaction -> { // executes the logic within a transactional scope }); } } } -
@Component public class AttachCardsToProposalsJob { @Scheduled(fixedDelay = 60_000) public void
execute() { while (true) { @Transactional.execute(transaction -> { // executes the logic within a transactional scope }); } } } It does NOT execute! 😵💫 -
@Component public class AttachCardsToProposalsJob { @Scheduled(fixedDelay = 60_000) public void
execute() { while (true) { @Transactional.execute(transaction -> { // executes the logic within a transactional scope }); } } } -
@Component public class AttachCardsToProposalsJob { @Scheduled(fixedDelay = 60_000) public void
execute() { while (true) { transactionManager.execute(transaction -> { // executes the logic within a transactional scope }); } } } -
@Component public class AttachCardsToProposalsJob { private TransactionTemplate transactionManager; @Scheduled(fixedDelay =
60_000) public void execute() { while (true) { transactionManager.execute(transaction -> { // executes the logic within a transactional scope }); } } } -
@Autowired private TransactionTemplate transactionManager; @Scheduled(fixedDelay = 60_000) public void execute()
{ boolean pending = true; while (pending) { pending = transactionManager.execute(transactionStatus -> { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(…); if (proposals.isEmpty()) { return false; } proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); return true; }); } } -
@Component public class AttachCardsToProposalsJob { @Scheduled(...) public synchronized void execute()
{ // executes the logic here } } -
@Component public class AttachCardsToProposalsJob { @Scheduled(...) public synchronized void execute()
{ // executes the logic here } } No! -
@Scheduled(...) public void execute() { if (isLeader()) { // executes
the logic here } } -
@Scheduled(...) public void execute() { if (isLeader()) { // executes
the logic here } } -
@Repository public interface ProposalRepository extends JpaRepository<Proposal, UUID> { public List<Proposal>
findTop50ByStatusOrderByCreatedAtAsc(ProposalStatus status); } -
@Repository public interface ProposalRepository extends JpaRepository<Proposal, UUID> { @Lock(LockModeType.PESSIMISTIC_WRITE) public
List<Proposal> findTop50ByStatusOrderByCreatedAtAsc(ProposalStatus status); } -
select p.* from proposal p where p.status = 'ELIGIBLE' order
by p.created_at asc limit 50 -
select p.* from proposal p where p.status = 'ELIGIBLE' order
by p.created_at asc limit 50 for update -
9630 9631 producer consumer 9629 consumer consumes: 400 items/min consumes:
600 items/min produces: 1000 items/min QUEUE -
consumes: 400 items/min consumes: 600 items/min produces: 1000 items/min 9630
9631 producer consumer 9629 consumer QUEUE -
@Repository public interface ProposalRepository extends JpaRepository<Proposal, UUID> { @QueryHints({ @QueryHint(
name = "javax.persistence.lock.timeout", value = LockOptions.SKIP_LOCKED) // org.hibernate.LockOptions }) @Lock(LockModeType.PESSIMISTIC_WRITE) public List<Proposal> findTop50ByStatusOrderByCreatedAtAsc(ProposalStatus status); } -
@Repository public interface ProposalRepository extends JpaRepository<Proposal, UUID> { @QueryHints({ @QueryHint(
name = "javax.persistence.lock.timeout", value = LockOptions.SKIP_LOCKED) // org.hibernate.LockOptions }) @Lock(LockModeType.PESSIMISTIC_WRITE) public List<Proposal> findTop50ByStatusOrderByCreatedAtAsc(ProposalStatus status); } -
@Repository public interface ProposalRepository extends JpaRepository<Proposal, UUID> { @QueryHints({ @QueryHint(
name = "javax.persistence.lock.timeout", value = LockOptions.SKIP_LOCKED) // org.hibernate.LockOptions }) @Lock(LockModeType.PESSIMISTIC_WRITE) public List<Proposal> findTop50ByStatusOrderByCreatedAtAsc(ProposalStatus status); } -
select p.* from proposal p where p.status = 'ELIGIBLE' order
by p.created_at asc limit 50 for update -
select p.* from proposal p where p.status = 'ELIGIBLE' order
by p.created_at asc limit 50 for update skip locked -
ID STATUS 1001 ELIGIBLE 1002 ELIGIBLE 1003 ELIGIBLE 1004 ELIGIBLE
You got scalability! -
@Autowired private TransactionTemplate transactionManager; @Scheduled(fixedDelay = 60_000) public void execute()
{ boolean pending = true; while (pending) { pending = transactionManager.execute(transactionStatus -> { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(…); if (proposals.isEmpty()) { return false; } proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); return true; }); } } -
@Autowired private TransactionTemplate transactionManager; @Scheduled(fixedDelay = 60_000) public void execute()
{ boolean pending = true; while (pending) { pending = transactionManager.execute(transactionStatus -> { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(…); if (proposals.isEmpty()) { return false; } proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); return true; }); } } -
@Autowired private TransactionTemplate transactionManager; @Scheduled(fixedDelay = 60_000) public void execute()
{ boolean pending = true; while (pending) { pending = transactionManager.execute(transactionStatus -> { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(…); if (proposals.isEmpty()) { return false; } proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); return true; }); } } -
@Autowired private TransactionTemplate transactionManager; @Scheduled(fixedDelay = 60_000) public void execute()
{ boolean pending = true; while (pending) { pending = transactionManager.execute(transactionStatus -> { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(…); if (proposals.isEmpty()) { return false; } proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); return true; }); } } -
@Autowired private TransactionTemplate transactionManager; @Scheduled(fixedDelay = 60_000) public void execute()
{ boolean pending = true; while (pending) { pending = transactionManager.execute(transactionStatus -> { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(…); if (proposals.isEmpty()) { return false; } proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); return true; }); } } -
@Autowired private TransactionTemplate transactionManager; @Scheduled(fixedDelay = 60_000) public void execute()
{ boolean pending = true; while (pending) { pending = transactionManager.execute(transactionStatus -> { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(…); if (proposals.isEmpty()) { return false; } proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); return true; }); } } Inserts a new card Updates a proposal with its card -
2 roundtrips INSERT INTO card(cardNumber) VALUES ('5260-9991-9040-0001'); UPDATE proposal SET
status = 'ELIGIBLE_WITH_ATTACHED_CARD' WHERE id = 2021; -
2 roundtrips INSERT INTO card(cardNumber) VALUES ('5260-9991-9040-0001'); UPDATE proposal SET
status = 'ELIGIBLE_WITH_ATTACHED_CARD' WHERE id = 2021; -
2 roundtrips INSERT INTO card(cardNumber) VALUES ('5260-9991-9040-0001'); UPDATE proposal SET
status = 'ELIGIBLE_WITH_ATTACHED_CARD' WHERE id = 2021; -
INSERT INTO card(cardNumber) VALUES ('5260-9991-9040-0001'); UPDATE proposal SET status =
'ELIGIBLE_WITH_ATTACHED_CARD' WHERE id = 2021; INSERT INTO card(cardNumber) VALUES (' 5513-8141-9261-0002'); UPDATE proposal SET status = 'ELIGIBLE_WITH_ATTACHED_CARD' WHERE id = 2022; 4 roundtrips -
INSERT INTO card(cardNumber) VALUES ('5260-9991-9040-0001'); UPDATE proposal SET status =
'ELIGIBLE_WITH_ATTACHED_CARD' WHERE id = 2021; INSERT INTO card(cardNumber) VALUES (' 5513-8141-9261-0002'); UPDATE proposal SET status = 'ELIGIBLE_WITH_ATTACHED_CARD' WHERE id = 2022; INSERT INTO card(cardNumber) VALUES (' 5599-4154-6354-0003'); UPDATE proposal SET status = 'ELIGIBLE_WITH_ATTACHED_CARD' WHERE id = 2023; 6 roundtrips -
100 roundtrips INSERT INTO card(cardNumber) VALUES ('5260-9991-9040-0001'); UPDATE proposal SET
status = 'ELIGIBLE_WITH_ATTACHED_CARD' WHERE id = 2021; INSERT INTO card(cardNumber) VALUES (' 5513-8141-9261-0002'); UPDATE proposal SET status = 'ELIGIBLE_WITH_ATTACHED_CARD' WHERE id = 2022; INSERT INTO card(cardNumber) VALUES (' 5599-4154-6354-0003'); UPDATE proposal SET status = 'ELIGIBLE_WITH_ATTACHED_CARD' WHERE id = 2023; INSERT INTO card(cardNumber) VALUES (' 5419-8753-1126-0004'); UPDATE proposal SET status = 'ELIGIBLE_WITH_ATTACHED_CARD' WHERE id = 2024; INSERT INTO ... UPDATE proposal SET ... -
2 roundtrips INSERT INTO card(cardNumber) VALUES (?) Params: [ ('5513-8141-9261-0001'),
('5513-8141-9261-0002'), (‘5513-8141-9261-0003'), (‘5513-8141-9261-0004'), ... ] UPDATE proposal SET status = ? WHERE id = ?; Params: [ (‘ELIGIBLE_WITH_ATTACHED_CARD’, 2021), ('ELIGIBLE_WITH_ATTACHED_CARD', 2022), ('ELIGIBLE_WITH_ATTACHED_CARD', 2023), ('ELIGIBLE_WITH_ATTACHED_CARD', 2024), ... ] -
2 roundtrips INSERT INTO card(cardNumber) VALUES (?) Params: [ ('5513-8141-9261-0001'),
('5513-8141-9261-0002'), (‘5513-8141-9261-0003'), (‘5513-8141-9261-0004'), ... ] UPDATE proposal SET status = ? WHERE id = ?; Params: [ (‘ELIGIBLE_WITH_ATTACHED_CARD’, 2021), ('ELIGIBLE_WITH_ATTACHED_CARD', 2022), ('ELIGIBLE_WITH_ATTACHED_CARD', 2023), ('ELIGIBLE_WITH_ATTACHED_CARD', 2024), ... ] -
INSERT INTO card(cardNumber) VALUES (?) Params: [ ('5513-8141-9261-0001'), ('5513-8141-9261-0002'), (‘5513-8141-9261-0003'),
(‘5513-8141-9261-0004'), ... ] 2 roundtrips UPDATE proposal SET status = ? WHERE id = ?; Params: [ (‘ELIGIBLE_WITH_ATTACHED_CARD’, 2021), ('ELIGIBLE_WITH_ATTACHED_CARD', 2022), ('ELIGIBLE_WITH_ATTACHED_CARD', 2023), ('ELIGIBLE_WITH_ATTACHED_CARD', 2024), ... ] -
INSERT INTO card(cardNumber) VALUES (?) Params: [ ('5513-8141-9261-0001'), ('5513-8141-9261-0002'), (‘5513-8141-9261-0003'),
(‘5513-8141-9261-0004'), ... ] 2 roundtrips UPDATE proposal SET status = ? WHERE id = ?; Params: [ (‘ELIGIBLE_WITH_ATTACHED_CARD’, 2021), ('ELIGIBLE_WITH_ATTACHED_CARD', 2022), ('ELIGIBLE_WITH_ATTACHED_CARD', 2023), ('ELIGIBLE_WITH_ATTACHED_CARD', 2024), ... ] You got optimal batch processing! -
@Scheduled(...) public void execute() { boolean pending = true; while
(pending) { pending = transactionManager.execute(txStatus -> { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(…); if (proposals.isEmpty()) { return false; } proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); return true; }); } } -
@Scheduled(...) public void execute() { boolean pending = true; while
(pending) { pending = transactionManager.execute(txStatus -> { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(…); if (proposals.isEmpty()) { return false; } proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); return true; }); } } -
@Scheduled(...) public void execute() { boolean pending = true; while
(pending) { pending = transactionManager.execute(txStatus -> { List<Proposal> proposals = proposalRepository.findTop50ByStatusOrderByCreatedAtAsc(…); if (proposals.isEmpty()) { return false; } proposals.forEach(proposal -> { CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); Card newCard = cardData.toModel(); cardRepository.save(newCard); proposal.attachTo(newCard); proposalRepository.save(proposal); }); return true; }); } } -
CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); can I send the request again?
transient errors? retry+backo f waits in fi nitely? timeout -
CardDataResponse cardData = cardsClient.findCardByProposalId(proposal.getId()); can I send the request again?
idempotency transient errors? retry+backo f waits in fi nitely? timeout -
WHAT WHAT WHAT WHAT WHAT WHAT WHAT WHAT WHAT IF…
WHAT IF… WHAT IF… WHAT WHAT IF… HAT IF… WHAT IF… WHAT WHAT IF… WHAT IF… WHAT IF… WHAT IF… AT IF… WHAT IF… F…WHAT -
…remains available in the face of failures …can handle huge
volumes of data …can recover from crashes -
…designed and built to be scalable …remains available in the
face of failures …can handle huge volumes of data …can recover from crashes -
…designed and built to be scalable …remains available in the
face of failures …can handle huge volumes of data …optimized for high-throughput …can recover from crashes