안녕하세요 Multi-threaded-step과 Partitioning 차이가 제가 알고 있는 게 맞는지 궁금하여 질문드립니다. Multi-threaded-step 은 하나의 Step 안에서 Chuk 크기만큼 읽기-처리-쓰기 이 한 사이클을 여러 스레드들이 독립적으로 수행되어 단일 스레드보다 속도 향상을 할 수 있고 PartitionStep 은 하나의 Master Step 안에 파티셔닝 할 Slave Step을 정의하고 이 Step은 공유되고 gridSize 만큼 스레드가 생성되어 해당 스레드에는 독립적으로 스택에 StepExecution 참조를 할당받아 마치 여러 Step들이 스레드 개수만큼 수행되는 건가요? 즉 Multi-threaded-step은 Step in Muti-Thread 이고 Partitioning은 Step in Multi-Step 인거죠?
안녕하세요 jdbc 를 통해 데이터를 읽어오는 점에 대해 질문이 있습니다. @Bean public ItemReader<Customer> jdbcCursorItemReader() { return new JdbcCursorItemReaderBuilder<Customer>() .name("jdbcCursorItemReader") .fetchSize(10) .dataSource(dataSource) .sql( "select id, firstName, lastName, birthDate from customer where firstName like ? order by lastName, firstName") .beanRowMapper(Customer.class) .queryArguments("A%") .build(); } 여기서 fetchSIze 는 db 에서 한번에 가져올 레코드 수이고 @Bean public ItemReader<Customer> jdbcPagingReader() throws Exception { Map<String ,Object> paramMap = new HashMap<>(); paramMap.put("firstName", "H%"); return new JdbcPagingItemReaderBuilder<Customer>() .name("jdbcPagingReader") .dataSource(dataSource) .fetchSize(3) .pageSize(3) .queryProvider(pagingQueryProvider()) .parameterValues(paramMap) .beanRowMapper(Customer.class) .build(); } 여기 페이징 방식에는 fetchSize, pageSize 두개를 둘 다 사용할 수 있던데 차이가 정확인 뭔가요? pageSize는 설정 시 쿼리에 Limit 설정 수가 추가되어 나가는 것은 확인했습니다.
안녕하세요 step 에서의 멀티 스레드 동작 방식에 대해 질문이 있습니다. 4개의 스레드 풀을 관리하는 taskExecutor 를 사용한다고 하였을 때 제일 맨 처음 스레드가 db 에서 fetch size 만큼 가져오고 시작하게 되는데 각 스레드들은 독립적으로 읽기-처리-쓰기 한 싸이클을 독립적으로 처리하게 되는게 맞나요.? 아니면 또 스레드1이 읽은 데이터를 스레드 2가 처리하는 방식인가요.? 요약은 스레드1이 읽은 데이터를 쓰기 작업까지 한 싸이클을 끝까지 독립적으로 수행하는가 아닌가 입니다.
안녕하세요 강의 중 궁금한 사항이 있어 질문드립니다. chunkSize 가 만약 5이고 데이터 사이즈가 10이라면 step을 실행할 때 최초 reader, writer 의 open 메서드를 실행시키고 최초 update 실행 후 chunk process (read, process, write) 5번 주기로 update를 실행, 마지막으로 reader, writer close 해주는걸로 알고 있는데요 여기서 update는 총 3번 진행되는게 맞는건가요? -> 최초 1회, 5번일 때 1회, 10번일때 1회
안녕하세요 강사님 스코프 객체 지연 초기화에 대해 질문이 있습니다. 해당 어노테이션을 사용하면 스프링 컨텍스트 초기화 시점에 실제 객체 지연 초기화를 위해 컨테이너에 프록시 객체를 미리 등록해 놓고 해당 객체의 메서드 사용 시점에 실제 객체가 생성되는 것으로 알고 있는데요 이 때 실제 객체를 생성하면은 스프링 컨테이너에 빈으로 또 다시 등록하나요? 아니면 실제 객체 생성 후에 참조만 프록시에게 넘겨주어 동작하게 되는건가요?
FlowJob에 대한 강의를 듣다가 궁금한 점이 있어서 질문드립니다. from()에 관한 질문인데요, from()의 역할이 현재 트랜지션에서 벗어나 새로운 트랜지션을 다시 재정의하는 것으로 이해했습니다. 그래서 38:39 부분에서, step1()은 처음에 start()에서 정의하고, 30번 째 라인에서, 다시 step1()의 기준을 재정의하 FAILED가 아닌 모든 경우에 대한 flow를 재구성하는 것으 이해했습니다. 질문은 step2()에 관한 것입니다. step2()는 첫번째 트랜지션인] on("FAILED") 하위에 to()를 통해서 최초에 정의되었습니다. 하지만, 그리고 step2()는 "FAILED" 상태인 경우에, FlowJob을 Stop하고 그 결과로 Job을 상태를 Stopped로 만듭니다. step2의 중점 Flow는 step1() -> FAILED -> step2() -> FAILED -> STOPED 인 것을 알 수 있습니다. 그런데, 34번 라인에서 다 step2를 가져와서 이전에 정의 FAILED를 제외한 나머지 경우를 진입시키는 Transtion을 작성하였습니다. 코드만 보면, 계층이 step1과 나란한 구조라서, 헷갈릴 것 같습니다. 그래서, 저는 34~36번 라인의 코드를, 29번 라인 이후에, 작성해도 같은 결과를 보장하는지 궁금합니다. from절이 어디에 사용돼도, 항상 from절 내부에 있는 StepBean을 기준으 다시 flow가 재정의 된다고 봐도 되는걸까요?
return new StepBuilder("apiStep", jobRepository) .<HelloVO, Hello>chunk(CHUNK_SIZE, transactionManager) .reader(helloReader(null)) .processor(helloVO -> { LocalDate date = helloVO.getDate(); if(date.getDayOfMonth() == 10){ throw new IllegalStateException("!! " + helloVO); } return helloVO.toEntity(); }) .writer(v -> log.info("write = {}", v)) .listener(partitionLimitSkipListener(null)) .build(); 안녕하세요. 만약 위 상황에서 파티션이 1000개라고 가정할 경우 천 건의 청크가 동작하게 될텐데요. 만약 첫 번재 값이 timeout으로 예외가 발생할 경우, 남은 999건 동일한 timeout이 발생할 수 있다고 예상되고 이런 에러는 장애로 확장될 수 있다고 예상됩니다. 실제로 위의 throw ex 상황에서 failed로 처리된 1000건의 step이 발생함을 확인하였습니다. 이런 문제를 최소화 해야 하는 것에 목적이 있습니다. 이를 해결하기 faultTolerant로 사용하고 chunkListener의 afterError메서드로 step context까지 도달하여 setTerminal 을 하였으나... 이 방법으로는 해결이 되지 않더라고요. 결국 결국 아래와 같이 operator로 처리하였습니다. @Slf4j public class ChunkExceptionCounterListener implements ChunkListener { private int failureCount = 0; private static final int MAX_FAILURES = 3; private final JobOperator jobOperator; public ChunkExceptionCounterListener(JobOperator jobOperator) { this.jobOperator = jobOperator; } @Override public void afterChunkError(ChunkContext context) { failureCount++; log.error("chunk {} exceeds error {}/{}", context.getStepContext().getStepName(), failureCount, MAX_FAILURES); if (failureCount >= MAX_FAILURES) { log.error("stop job! with afterChunkError!"); context.getStepContext().getStepExecution().setTerminateOnly(); // 안됨..ㅠ context.getStepContext().getStepExecution().getJobExecution().setExitStatus(ExitStatus.FAILED); Long jobExecutionId = context.getStepContext().getStepExecution().getJobExecutionId(); try { jobOperator.stop(jobExecutionId); // 이건 된다! } catch (NoSuchJobExecutionException e) { throw new RuntimeException(e); } catch (JobExecutionNotRunningException e) { throw new RuntimeException(e); } } } } 위 방식이 좋은 방식인지는 솔직히 모르겠습니다. 일단 기대하는 것처럼 interrupt로 에러로 남은 모든 step이 stopped되어 좋긴 하였는데, 제가 혼자서 구현한 방식인지라... 최적의 방식인지는 잘 모르겠네요. 더불어 retry template에서 recover callback 메서드를 구현할 경우 청크 파티션 일부의 에러를 마스터 슬레이브 전체에 대한 정지 처리로 할 수 있던 것으로 기억하는데, 이 정도의 튜닝은 제공하는지도 궁금합니다. 감사합니다^^
안녕하세요. 좋은 강의 잘 듣고 있습니다. 조금 헷갈리는 부분이 있는데 JdbcCursorItemReader 를 사용하면서 chunk size를 10으로 설정해서 100개의 데이터를 처리한다면 메모리에는 데이터가 최대 10개가 올라가는건가요? 아니면 100개까지 올라가는건가요? 몇백만건의 데이터를 처리할 때 사용가능한건지 궁금합니다. 그리고 커넥션을 한번 맺으면 끝까지 물고 있다고 이해했는데 몇백만건 처리를 할때 3시간이 걸린다면 실무에서 타임아웃도 3시간 이상으로 설정을 하나요?
안녕하세요 ! 강의 듣고 있는데 실습해보고 싶어서 계속 따라하는데 빈 등록이 자꾸 안된다고 뜹니다 ㅠ 혹시 호환성에 대해 문제가 있을까바 검색해봤는데도 호환성에 대해서는 잘 작동한다고도 하는데 왜 자꾸 빈으로 등록이 안되는걸까요 ? spring boot 3.3.2 사용하고 jdk는 17 사용합니다. 제가 지금까지 해본것은 1. @RequiredArgsConstructor 을 이용한 의존성 주입 2. 생성자를 생성하여 @Autowired를 사용 3. batch 호환성에 대한 문제 아니면 혹시 Maven으로만 작성해서 그런걸까요 ? 답변 부탁드립니다 ㅠ
안녕하세요. 비전공자로 배치 프로그램에 관심이 많아서 해당 강의를 듣다가 커서 방식과 페이징 방식을 스프링 배치에서 사용할 때 고려사항과 관련하여 질문이 있습니다. 커서가 페이징 방법에 비해 데이터 일관성이 보장이 쉽고 속도도 더 빠른 것으로 알고 있습니다. 그리고 청크 기반 배치 처리 로직으로 구현한다면, 커서 기법을 사용하는 경우에도 페이징 방법처럼 트랜잭션 단위를 청크 크기만큼 나눌 수 있을 텐데요. (페이징에서 chunk와 paging 단위를 같이 하는 것처럼 커서에서도 chunk와 fetchsize를 같게 해주면 될 것으로 고려합니다.) 그리고 멀티 스레드를 고려한다면, SynchronizedItemStreamReader로 reader 영역을 감싸서 처리할 수도 있는 걸로 알고 있습니다. 그렇다면, 이러한 상황 속에서 커서와 페이징을 사용함에 고려해야할 트레이프 오프가 커넥션 타임 뿐인 걸까요?? 이런 상황에서도 커서가 메모리 할당 방식으로 인해서 메모리 사용량이 커진다는 단점이 있는 건가요?? ( 더 정확하게 말하자면 결과를 메모리에 할당한다는 점이 명확히 와닿지 않습니다. 제 나름 추론하자면, 커서가 fetch size나 chunk로 전체 데이터를 분할하여 메모리에 올리고 읽고나서도 모든 결과값이 메모리에 쌓여 한번에 write(ex- db에 저장) 되기 때문에 메모리 사용량이 많아 진다는 것인가여?? 반면에, 페이징은 기반 처리는 페이징 단위로 데이터를 읽고 읽은 데이터 크기를 처리 후 메모리에 올리고 write한 후에 다시 정해진 크기 만큼 읽는 방식인건가요?? 즉, 이것이 커넥션을 맺고 끊는 것 방식과 관련이 깊은 것인지 궁금합니다.) 이상 cs적인 지식이 부족한 비전공자의 긴 질문을 읽어주셔서 감사합니다.
5버전으로 강의 따라가면서 정리하고 있습니다(현재진행중). 공유하고자 올려봅니다. 혹여나 틀린점 있으면 지적해주세용. Execution context serialization Updates https://github.com/spring-projects/spring-batch/wiki/Spring-Batch-5.0-Migration-Guide ExecutionContext에 Base64로 인코딩된 데이터가 저장됩니다. @Configuration public class BatchConfig { @Bean public ExecutionContextSerializer jacksonSerializer() { return new Jackson2ExecutionContextStringSerializer(); } } 강의처럼 데이터를 json으로 저장하고 싶으면, jackson-core의존성을 추가한 후, 위 설정클래스를 정의합니다. 섹션2~3 @EnableBatchProcessing 을 쓰게되면 BatchAutoConfiguration이 적용되지 않는다. https://umbum.dev/1320/ 자동 구성과 수동 구성 요소가 동시에 존재할 때 발생할 수 있는 모호성과 충돌을 피하기 위해 BuilderFactory대신 JobBuilder, StepBuilder 직접 사용 팩토리 클래스들은 종종 불필요한 복잡성을 추가하고, Spring Batch 구성의 일관성을 떨어뜨림. 직접 사용해서 더 많은 제어 권한 획득 Tasklet 사용시 , PlatformTransactionManager 함께사용 (단일 사용 deprecated) 배치 설정 클래스 BasicBatchConfigurer → DefaultBatchConfiguration 으로 변경 섹션 3 - JobLauncher 주입받은 JobLauncher가 프록시객체가 아닌 실제 객체라서 DefaultBatchConfiguration 에서 JobLauncher를 얻어올 필요없이 바로 타입캐스팅 가능 섹션 4 - 배치 초기화 설정 spring:batch:job: names(x) -> name 실행 할 job을 찾지 못하면 예외가 발생합니다. 인텔리제이에 환경설정으로 프로그램 인수를 한개만 할당할 수 있게 변경되었습니다. 파라미터 관련해서는 코드로 할당해줘야 합니다. multiple jobs 실행 불가능. JobLauncherApplicationRunner 의 executeLocalJobs 메서드에서 split으로 job 이름을 구분하는 코드가 삭제되었습니다. 섹션 7 - 스프링 배치 청크 프로세스 이해 chunk사용시 , PlatformTransactionManager 함께사용 (단일 사용 deprecated) ItemWriter의 write(List<? extends String> items ) -> write(Chunk<? extends String> chunk) Items에 접근하려면 getItems로 꺼내야합니다.
안녕하세요. 아래 코드에서 둘 다 ExecutionContext가 반환되는데, 둘은 같은 ExecutionContext가 아니라 다른 ExecutionContext인가요? 메서드 참조로 계속 타고 들어가서 하나는 getJobExecution()에서, 나머지 하나는 getStepExecution()에서 꺼내오는데 둘다 동일한 타입의 ExecutionContext라 헷갈립니다. 그리고 저렇게 각각 꺼내올 때 다른 ExecutionContext라 JobExecutionContext에 저장된 값을 Step에서 공유가 가능하고, stepExecutionContext에 저장된 값은 Step에서 공유가 불가능한 것이 맞는지 궁금합니다. ExecutionContext jobExecutionContext = stepContribution.getStepExecution().getJobExecution().getExecutionContext(); ExecutionContext stepExecutionContext = stepContribution.getStepExecution().getExecutionContext();
안녕하세요. Retry 관련해서 궁금증이 있어 문의 드립니다. FaultTolerant 을 사용하여 Retry 관리를 하려고 하는데 Processor에 인입되는 Item을 아래와 같이 조작하여 Writer에 넘겨줄 경우 Writer에서 문제 발생 시 RetryState의 키가 바뀌어 ' 무한루프' 가 도는 현상이 발생합니다. Listener 나 별도 Custom RetryPolicy 등을 활용하여 해결해보려 했지만 실패하여 고민끝에 글 남깁니다. 혹시 관련하여 고려해야 할 점이나, 방안에 대하여 알 수 있을까요. public class PersonItemProcessor implements ItemProcessor<Person, Person> { @Override public Person process(final Person person) { person.setFirstName(person.getFirstName()+"A"); return person; } }
chunk 지향 처리 방식에 대한 질문 스프링 배치를 공부 중에 궁금한 점이 있어서 질문을 남기게 됐습니다. chunk란 여러 아이템을 묶은 덩어리 블록을 의미. chunkSize를 100으로 했을 경우 제가 OPEN API로부터 데이터를 한번에 10개씩 읽어오도록 ItemReader를 설정했다면 (한 페이지에 10개의 데이터를 가져오도록 함) 그렇다면 동작 과정이 ItemReader를 10개씩 10번 돌고, 이후에 ItemWriter로 이동하는 것인가요 아니면 ItemReader, ItemProcessor, ItemWriter를 거치는 이 과정을 10번 하는 것인가요 ? chunk 단위로 트랜잭션을 처리한다고 이해했는데 위 과정에서 ItemWriter는 그럼 chunk단위로 10개씩 10번 모두 읽어온 이후에 처리해야 한다고 생각하는데 어떻게 동작하는지 궁금합니다. 또한 궁금한 것이 pageSize와 chunkSize를 동일시 하는 것이 성능에 좋다고 봤습니다. 그렇다면 ItemReader에서 제가 만약 10개를 읽어온다면 chunkSize역시 10으로 하는게 좋다는 건가요 ? - 위와 동일하게 item을 10번 read하고 ItemWriter로 이동하는지 (ItemReader - ItemWriter)를 10번하고 하나의 트랜잭션에서 ItemWriter에서 동작하는 내용을 처리하는지 궁금합니다.. 부족한 질문이라 죄송합니다 ㅠㅠ