묻고 답해요
161만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
메타데이터 관리
킬구형메타데이터쪽 update를 읽다가 이해가 안가는게 있는데 형이update() 메서드는 매 트랜잭션의 커밋 직전에 호출된다. 단, 처리 도중 예외가 발생하여 트랜잭션이 롤백되는 경우에는 호출되지 않는다. 이는 실패한 처리 내용이 실행 정보에 반영되는 것을 방지한다.라고 했는데 그러면 문제가 생겨서 update를 호출하지 않고 롤백이 됬다고 하면 open입장에서는 실패했는지 안했는지도 모르는거아니야? 실패를 해도 마지막으로 저장된 곳부터 다시 시작하니까 실패를 아예 저장을 안한다는거야?재시작할때 메타테이블에서 execution값을 받아와서 괜찮은건가?
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
2장. 작전2: 분산 서버 로그 처형 작전 Resource[]의 대체방안(읽어야할 내용이 매우 커지면?)
형님 안녕하십니까!2장 작전2 내용 중에, collected_logs에 모아진 모든 로그파일 내용을 Resource[]에 담고, 이를 flatFileItemReader로 읽기를 위임하는 부분이 있습니다. 아래는 그 중 Resource[] 배열에 해당 로그 내용을 담는 부분입니다. private Resource[] getResources(String date) { try { //String userHome = System.getProperty("user.home"); String userHome = "C:/Users/gyrbs/OneDrive/Desktop"; String location = "file:" + userHome + "/collected_logs/" + date + "/*.log"; //경로 직접 주입이 아닌 Resolver를 통한 url구성은 백슬래쉬가 아닌 /로 해야 인식 PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); return resolver.getResources(location); } catch (IOException e) { throw new RuntimeException("Failed to resolve log files", e); } } 이에 대해서 gpt한테 몇가지를 물어보았는데, 먼저 배열에 읽어야할 내용을 담고 이를 filterReader로 읽는 과정이 Resource[] 배열에 읽어야할 모든 내용을 모두 저장하고, 이 저장한 내용을 reader가 받아서 청크지향처리라면 청크사이즈만큼 읽고 넘기고 아니면 지금과 같이 일괄적인 읽기를 진행하는 것으로 보였습니다. 그래서 만약에 읽어야할 내용이 매우 커지면 Resource[]라는 배열에 이 모든 내용을 넣기가 어려울 것이고, 강의에서 항상 강조해주시는 JVM무덤, 배치무덤이 되지않을까 하는 생각이 들었습니다. 그래서 이에 대한 방안을 gpt한테 한번 물어보았는데, public class StreamingMultiFileReader<T> extends AbstractItemStreamItemReader<T> { private Iterator<Path> fileIterator; private FlatFileItemReader<T> currentReader; private final Function<Path, FlatFileItemReader<T>> readerFactory; public StreamingMultiFileReader(Path dir, Function<Path, FlatFileItemReader<T>> readerFactory) throws IOException { this.fileIterator = Files.list(dir).iterator(); this.readerFactory = readerFactory; } @Override public T read() throws Exception { if (currentReader == null) { if (!fileIterator.hasNext()) { return null; // 모든 파일 처리 완료 } Path nextFile = fileIterator.next(); currentReader = readerFactory.apply(nextFile); currentReader.open(new ExecutionContext()); } T item = currentReader.read(); if (item == null) { // 파일 끝 currentReader.close(); currentReader = null; return read(); // 다음 파일로 넘어감 } return item; } } 이런 방법이 있다고 하는데(StreamingMultiFileReader로 deletegate 처리없이, 파일 하나하나씩 바로바로 처리해나가는 과정), 파일 하나하나씩 읽어들이면서 더이상의 파일이 없을때까지 순회하여 메모리부담은 없다고는 하였습니다. 혹시, 이 방법 말고도 청크지향처리를 이용해서 청크사이즈 만큼 파일, 혹은 파일 내부의 record를 읽고 넘기는, 그리고 이 과정을 모든 파일에 대해 순회하는 방식은 없을지 질문드려보고자 합니다! 실무에서 사용하는 방법이 궁금해서 질문드리게 되었습니다! 감사합니다.
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
2장. 작전2: 분산 서버 로그 처형 작전 (시스템에 의존적인) SystemCommandTasklet 관련 질문
안녕하십니까 형님!2강 작전2 : 분산 서버 로그 처형 작전 관련해서, 실무적으로 어떠한 방향으로 접근하는 것이 좋을지 의문이 생겨서 질문 드려봅니다!제가 윈도우 환경에서 실행하다보니, 자료에 기술된 cli를 윈도우 환경으로 바꾸는데 많은 비용을 소모하였습다.특히 이번 내용의 경우,단순 개행문자 및 OS간 인식문제를 넘어서, 명령어를 아예 윈도우 환경에 맞게 변경을 해야한다는 점에서 다소 힘이 많이 들었던 것 같습니다.뿐만 아니라, 윈도우 cmd 환경에서의 실행을 고려해서 cmd -c의 명령어를 추가해준다든지, 리눅스의 mkdir -p가 먹히지 않으므로 mkdir를 &&로 이어서 실행하게끔 한다든지..(이러다보니 collected_logs 폴더도 최초부터 존재하지 않아야 정상 실행이 가능한 환경적 제약사항까지 고려해야 하였습니다) 전체적으로 환경의 차이를 많이 느끼고 그만큼의 비용도 일전보다는 훨씬 많이 소모된 느낌이 있었던 내용이었습니다. //-p 옵션은 윈도우에서 안먹힘..따라서 collected_logs라는 디렉토리 생성(mkdir)은 처음부터 존재하지 않아야 한다. //SystemCommandTasklet -> 반드시 cli 명령어 작성 필요 @Bean @StepScope public SystemCommadTasklet mkdirTasklet( @Value("#{jobParameters['date']}") String date) throws IOException { SystemCommandTasklet tasklet = new SystemCommandTasklet(); // //tasklet.setWorkingDirectory(System.getProperty("user.home")); //실행환경은 batch 명령어 작성환경에 상관없이 무조건 cmd //경로직접주임은 반드시 백슬래쉬 tasklet.setWorkingDirectory("C:\\Users\\gyrbs\\OneDrive\\Desktop"); String collectedLogsPath = "collected_logs\\" + date; String processedLogsPath = "processed_logs\\" + date; //String collectedLogsPath = String.format("collected_logs\\%s", date); //String processedLogsPath = String.format("processed_logs\\%s", date); String command = String.format("mkdir %s && mkdir %s", collectedLogsPath, processedLogsPath); // //tasklet.setCommand("mkdir", "-p", collectedLogsPath, processedLogsPath); //tasklet.setCommand("cmd", "/c", "mkdir", collectedLogsPath); tasklet.setCommand("cmd", "/c", command); tasklet.setTimeout(3000); // 3초 타임아웃 return tasklet; }@Bean @StepScope public SystemCommandTasklet scpTasklet( @Value("#{jobParameters['date']}") String date) { SystemCommandTasklet tasklet = new SystemCommandTasklet(); //tasklet.setWorkingDirectory(System.getProperty("user.home")); String rootDirectory = "C:\\Users\\gyrbs\\OneDrive\\Desktop"; tasklet.setWorkingDirectory(rootDirectory); String collectedLogsPath = String.format("collected_logs\\%s", date); StringJoiner commandBuilder = new StringJoiner(" && "); for (String host : List.of("localhost", "loan", "pay")) { //String command = String.format("scp %s:~\\logs\\%s.log .%s\\%s.log", // host, date, processedLogsPath, host); String src = String.format("logs\\%s.log", date); String dest = String.format("%s\\%s.log", collectedLogsPath, host); commandBuilder.add(String.format("copy %s %s", src, dest)); } //String src = String.format("logs\\%s.log", date); //String dest = String.format("%s\\%s.log", collectedLogsPath, "localhost"); tasklet.setCommand("cmd", "/c", commandBuilder.toString()); tasklet.setTimeout(10000); //10초 타임아웃 return tasklet; } 이런 느낀점이 많다보니, 이번 내용을 진행하면서, SystemCommandTasklet을 실무적으로 어떻게 활용하면 좋을지 의문점이 생겼습니다. 첫번째는, 단순히 배치를 실행하는 환경이 리눅스라면, 리눅스 환경은 어차피 한동안 안고쳐질거니까, 윈도우라면 그것대로 윈도우 배치 환경은 거의 변경이 없겠지?라는 생각으로 단순히 SystemCommandTasklet으로 구성해야겠네!라고 생각하는게 맞는지 일단 의문이 들었습니다. 배치라는게 이러한 환경적 독립성을 유지하지 않고, 배치실행환경에 맞게 일단 구성하는게 맞을까요? 물론 이 내용이 SystemCommandTasklet을 배우는데 중점이 있을 수 있겠지만, 이번에 꽤 많은 소모를 느껴서 질문드리게 되었습니다! 두번째로, SystemCommandTasklet이 아닌 Tasklet을 사용하여 아래와 같이 시스템에 의존하지 않고 모든 시스템에 적용이 가능한 Java API버전으로 구성하였는데, 빌드 성공하긴 했습니다. //-p 옵션은 윈도우에서 안먹힘..따라서 collected_logs라는 디렉토리 생성(mkdir)은 처음부터 존재하지 않아야 한다. //tasklet -> return (con, ch) @Bean @StepScope public Tasklet mkdirTasklet( @Value("#{jobParameters['date']}") String date) throws IOException { return ((contribution, chunkContext) -> { Path desktop = Paths.get("C:\\Users\\gyrbs\\OneDrive\\Desktop"); Path collected = desktop.resolve("collected_logs").resolve(date); Path processed = desktop.resolve("processed_logs").resolve(date); Files.createDirectories(collected); Files.createDirectories(processed); return RepeatStatus.FINISHED; }); } 이렇게 처음부터 시스템에 상관없는(상관없이 구동이 가능한) 로직을 구현하는게 유지보수적으로도 맞지 않을까..하는 생각이 들었습니다. 형님께서는 실무적으로 이를 적용할때 어떠한 방향으로 구성하시는지, 제가 지금 의문이 드는게 맞는것인지.. 궁금해서 질문드려봅니다!(아니면 만약 SystemCommandTasklet과 같이 시스템에 의존적인 구현이 필요하다면, 이 경우는 환경적으로 바뀔 가능성이 현저히 적다던가..이러한 특수적인 상황적 조건이 있을때 사용하실까요?) 제약사항이 생각보다 커져서 배꼽이 더 커지는 상황이었기에, 형님께 여쭤보고자 하였습니다! 감사합니다!
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
CommandLineJobRunner를 통한 실행
형 이거 CommandLineJobRunner를 통한 실행할 때 프로필도 줄 수 있어?
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
상용 시스템에서 Spring Batch H2 DB
킬구형우리 매니저는 무슨 이유인지 mysql, postgres처럼 RDB를 무지무지 싫어해, 어떤 말을 해도 RDB는 절대 안된다고 하걸랑그런데 하필이면 Spring Batch가 RDB를 필요로 한단 말이지! 매일 밤마다 상용 서버의 로그 데이터를 분석하고 다른 데이터 소스로 보낼 정도로만 쓰려고 하는데, Spring Batch RDB로 H2 file mode나 sqllite로 Spring Batch를 돌려도 문제가 없을까 헝헝... 나 슬퍼
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
[typo] 3장. 작전1 명령어 문의
킬구형 잘 지내고 있는가?날이 차가워졌다가 말다가 뒤죽박죽 3장 작전1의 JpaCursorItemReader 를 활용한 postBlockBatchJob 에 문제가 있어 보인다. 킬구형이 해킹을 잘한다지만, 우리는 못한다!!!명령어에 jobParameters 가 제외 되었는데 이런 좋은 해킹 공유 해주면 좋겟다! postBlockBatchJob 은 startDateTime / endDateTime 를 필요로 하는 명령어로 보인다.아래와 같이 구동해야 정상 작동이 되는걸로 보인다 ./gradlew bootRun --args='--spring.batch.job.name=postBlockBatchJob startDateTime=2025-11-16T00:00:00,java.time.LocalDateTime endDateTime=2025-11-20T00:00:00,java.time.LocalDateTime'
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
2강 작전1. csv/fixedLength 윈도우 실행 관련 유의사항 제보
현재 윈도우 환경에서 학습 중이어서 형님께서 싫어하실 수 있겠지만, 꽤나 흥미로운 것을 발견해서 형님께(강의내용에 도움이 되실 수도 있을까 하여) 제보드립니다!참고로 저의 경우 IntelliJ의 powershell에서 echo를 하고 csv/txt내용수정은 IntelliJ에서 직접 하였습니다. 1) csv 실행시 오류최초 실행시 파싱불가 오류가 발생하였는데, csv파일에서 문제점은 없을 것으로 판단하여 프로젝트의 인코딩을 utf-8에서 EUC-KR로 변경해주었습니다. 이러면 정상 작동하였습니다.2) fixedLength 실행시 오류역시 최초 실행시 파싱불가 오류가 발생하였습니다.로그를 살펴보았을때 인코딩이 잘못되었을 것으로 판단하여 프로젝트의 인코딩 설정과 해당 txt파일의 인코딩 설정을 모두 utf-8로 바꿔주었습니다(utf-16/utf-16LE/utf-16BE 모두 오류 발생, 특히 저 인코딩 문제는 utf-8에서만 유일하게 발생하지 않아서 utf-8 인코딩만 가능한 것으로 보임).이래도 파싱문제가 발생하였는데,.columns(new Range[]{ new Range(1, 8), // errorId: ERR001 + 2 new Range(9, 29), // errorDateTime: 19 + 2 new Range(30, 38), // severity: CRITICAL/FATAL + padding new Range(39, 44), // processId: 1234 + 2 new Range(45, 66) // errorMessage: msg + \n }) severity 부분을 30 ~ 39 가 아닌 30 ~ 38로 바꿔주었고마지막 errorMessage 부분을 46 ~ 66이 아닌 45 ~ 66으로 바꿔주었습니다(21자리 -> 22자리)echo로 공백을 입력해주었지만 실제 파일을 보았을때는 공백이 먹히지 않았던 것 같습니다. 그래서 아래와 같이 공백대신 문자열 1개를 그냥 입력해주었습니다. echo를 통한 개행이 먹히지 않아 위와 같이 intellij로 txt파일을 변경해주었습니다.마지막 문자는 DETECT + 공백 + 개행 -> DETECTS + 개행으로 공백을 제거해주었습니다. 이렇게 했을때 최종적으로 빌드성공하였습니다.저 Range범위, 특히 severity 범위를 왜 저렇게 구성해야 하는지, 이게 윈도우 실행 환경만 이런건지, 제 환경에서만 이런 문제가 발생하는건지..아직도 모르겠지만.. 형님께 제보를 해드리면 좋을 것 같아서 남겨봅니다!
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
형 JobLauncherController 구성 질문
킬구형 여기부분에서 사실상 JobLauncherController 에서 applicationContext는 실제로 사용되지않는데 이전 강의에서 job이 bean으로 등록된다는걸 우리가 실제로 확인해보라고 넣은 큰 그림인거야 후훗? 😏
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
레디스 설명시작 부분 인코딩깨짐 문제
오타라기 보다는 인코딩이 깨져 보여서 신경이 쓰여서3장. 작전2: NoSQL 읽고 쓰기 (무법지대 NoSQL, 새로운 처형 방식의 시작 ☠) 에서 중간 레디스 설명 시작 부분이야
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
ExitCode 질문
킬구형 5장. 작전2: Run Squad 장악 작전 공부중인데, Process finished with exit code 0이라는 문구 자체가 나오질 않아. 혹시나 싶어서 새 프로젝트 만들어서 시도해봐도 마찬가지고,, 윈도우 11, java 17에 org.springframework.boot:spring-boot-starter-batch:3.5.7 환경에서 개발중인데 혹시 버전이나 OS 문제일까?? 아니면 뭔가 설정을 빼먹은건지 모르겠네.. 혹시 몰라서 application.yaml 설정도 첨부해spring: application.name: demo output.ansi.enabled: always datasource: url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1 driver-class-name: org.h2.Driver username: sa password:
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
[ typoooo ] 1장. 작전3: Spring Batch Listener
JobListener 를 구현하면서 동적으로 executionContext 를 밀어 넣을때 설명이 내가 이해한게 맞다면 오타가 발생한 듯 하다. 이렇게 InfiltrationPlanListener를 JobBuilder의 listener() 메서드로 등록해주면 beforeStep() 메서드에서 동적으로 생성한 데이터를 각 Step에서 참조할 준비가 완료된다.해당 문구의 작업은 JobExecutionListener 로 동작한 부분으로 beforeStep 이 아닌 beforeJob 에 의해서 동적으로 생성되는게 맞지 아니한가?!
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
spring batch 오픈소스
킬구형 ㅎㅇ 요즘 형 spring-batch 오픈소스 이슈에서 잘 보고 있어. 진짜 형 대단한 거 같아.형 그런데 나는 궁금한 게 있어 일단 이번에 나도 간단한 오타 수정으로 기여를 하기는 했는데 나도 이슈를 한번찾아보고 싶은데 감이 안 잡혀서 형은 보통 이슈를 어떻게 찾는지, 아니면 오픈소스를 볼 때 팁 같은 게 있는지 궁금해.
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
4장 RetryPolicy 예제 코드 질문이요
킬구형 RetryPolicy 작동 방식이 policyMap에서 우선 발생한 에러의 상위 카테고리를 찾고, RetryPolicy에 들어있는 SimpleRetryPolicy가 실제로 각 에러에 대해 어떻게 처리할지를 정하는 것 같은데 그러면 두 에러가 상속 관계에 있어야지만 정상적으로 작동하는 거 같은데 맞아?그런데 예제 코드에 있는 HttpTimeoutException와 HttpServerErrorException는 상속 관계가 아니어서 아마 의도대로 작동하지 않을 것 같은데 한 번 검토해봐줄 수 있어?참고로 나는 java17 + spring-boot-starter-batch:3.5.6 환경으로 진행중이야
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
섹션1 | 2장 이미지(Flexible.png) 내 오타 제보
킬구형 강의 특별해서 좋아~ 즐길 수 있을거 같아! 이런 강의 의도를 더 믿음있게 하기 위해서 오타 제보~ 섹션 1. SYSTEM INIT: 스프링 배치 종결의 서막 2. 배치 처리, 시스템 종결의 서막💀 Flexible.png 이미지 내 오타 ItemReader interface 를 상속하는 RedisItemWriter 는 없다. RedisItemReader 가 존재한다
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
킬구형 혼자 삽질하면서 배치 운영하는데 궁금한 부분이 있어
안녕 킬구형 나는 오랜만에 배치 복습하려고 형 강의 구매했어!일단 그냥 책 보면서 실무에서 배치를 운영을 하려니깐 고민이 있어.형 생각이 궁금해서 질문을 남겨 1. Jenkins , Spring Batch형 일단 나는 Batch를 실무에서 Jenkins 스케줄러 + Batch 이렇게 사용하고 있어. 그런데 배치 인프라에 대해서 요즘 고민이 생기는 거 같아. 나는 job enabled를 false로 설정하면서 운영하고 있는데 만약에 배치가 N개로 운영되고 있다고 가정하면 port 충돌에 대해서 고민이야 물론 parameter로 port를 넘겨줄 수 있어서 현재는 스크립트로 남는 포트를 parameter로 넘겨주면서 사용하고 있어. 간편성을 생각한다면 그냥 enabled를 true로 하고 api 형식으로 그냥 호출만 해주면 이런 복잡한 과정 없이 사용할 수 있을 거 같은데 어떻게 생각해. 2. CI/CD하나의 jar에서 여러 개의 배치가 돌아가는데 이때 CI/CD 과정에서 문제가 생길 수 있을 거 같다. 현재는 ln 심볼릭 링크로 해결을 하고 있는데 이게 적절한 방식인지? 아니면 다른 방식이 있는지 궁금해 3. JobParametersIncrementer형 Spring Batch에서 JobParametersIncrementer(RunIdIncrementer 등) 관련해서 궁금한 점이 있어 만약 업무적으로 특별한 파라미터(날짜, 키 등)가 없는 반복 배치라면이 때는 run.id와 같은 증분기 기반의 값으로 JobInstance의 고유성을 관리 하지만 업무적으로 중요한 파라미터(날짜, 키 값 등)가 있다면 이 경우에는 run.id 대신 해당 파라미터를 JobInstance의 기준으로 써야 멱등성 및 실패/재실행 제어가 더 적합하다고 생각해. 실제로 파라미터가 없는 경우에는 run.id로 멱등성을 보장하고, 파라미터가 있는 경우에는 run.id 증분기를 쓰면 오히려 동일 파라미터로 재실행/복구 등 업무상 중요 컨트롤이 어렵지 않나요? 이런 경우 run.id 증분기를 같이 쓰는 게 맞는지, 아니면 아예 쓰지 않는 게 맞는지 설계 원칙이 궁금해요.https://github.com/spring-projects/spring-batch/issues/4910https://github.com/spring-projects/spring-batch/wiki/Spring-Batch-6.0-Migration-Guide
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
AOP 적용 가능한가요?
킬구형 공부중에 문득 궁금해진건데 혹시 스프링배치도 AOP를 적극적으로 활용해?스프링배치도 기본 구조는 스프링이랑 같아서 AOP를 쓸 수는 있을 것 같은데 실제로 활용을 하는지 궁금하네쉽게 쓰기에는 @Transaction 어노테이션도 뭔가 활용성이 있을 것 같고.. API 리퀘스트 실패 시 자동으로 재처리 하는 AOP 기능을 만들어보기도 했는데 FaultTolerance 관련으로 비슷하게 활용할 수도 있을 것 같고..아무튼 스프링배치에서 AOP를 적극적으로 활용하는지 궁금해
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
1장에 들어가기 전에 배치 프로젝트(디렉토리) 구성 방법에 대한 질문
☠ 질문 가이드 ☠ " 시스템 종결자의 지령이다. 질문하기 전에 이 규칙들을 숙지하도록. " 1. 코드 실행에 문제가 있다고?전체 코드를 보여줘라. 단편적인 에러 메시지만으로는 아무것도 알 수 없다.실행 환경도 알려달라. JDK 버전, 스프링 버전 등을 함께. 2. 오타를 발견했나?즉시 제보하도록. 자네같은 날카로운 눈을 가진 동료가 필요하다. 3. 질문은 자유롭게"이런 걸 물어봐도 될까요?" 같은 소심한 멘트는 불필요하다. 궁금한 건 바로 물어봐라. 배치 시스템에 소심한 건 없다. 4. 검색은 기본비슷한 질문이 있는지 먼저 확인하도록.하지만 이해가 안 된다면? 주저하지 말고 추가 질문해라.GPT가 거짓말친다고? 나에게로 오라. 💀 5. 서로 존중하라여기는 모두가 시스템을 지배하고자 하는 동료들이다.서로를 이해하고 돕는 문화를 만들어가자. ⛔ 인프런 서비스 자체에 대한 문의는 1:1 문의하기로.💀그쪽 서버는 막강한 CTO가 있어 건드리지 않는 게 좋을 거다 💀- KILL-9 올림 P.S.존댓말로 질문하면 rm -rf를 시전한다. 편하게 물어보도록.강의에서 놓친 부분이나 더 보충하면 좋을 내용도 자유롭게 제보하라. 너희의 피드백이 이 강의를 더 강력하게 만든다. 🔥 시스템을 함께 진화시켜 나가자.🔥 킬구형님 안녕하세요!(그래도 선생님이신데 반말하기엔 좀 그런것 같아서 존댓말로 하겠습니다..!)먼저 좋은 강의 감사드립니다.사실 구매한지는 좀 되었는데, 지난 1주일동안 Batch와 스케쥴러의 차이점, 왜 이런 어노테이션을 사용하는지부터, 왜 이런 환경설정을 해야하는지, Framework와 Boot의 동작차이점은 무엇인지 세세하게 먼저 이해하는데 집중하다보니 힘이 많이 들었는데 0장 만으로도 상당히 많은 기본기가 쌓인 것을 느낄 수 있었습니다(무엇을 모르고있었고 무엇을 알아야하는지 등).배치가 막연하게 느껴졌는데, 아직 극초반이지만 자신감이 생기고 있습니다. 감사드립니다!1장에 들어가기전에 앞서, 조금이라도 더 실무에 가까운, 가깝지 않더라도 유지보수가 간편하고 알아보기 쉽게 체계를 구성해보고자, 형님께서는 실무적으로 배치 프로젝트를 어떻게 구성하시는지 질문드리고자 합니다.각파일들의 디렉토리 위치가 없는데 임의적으로 해야하나요? - 인프런 | 커뮤니티 질문&답변위 질문에서 형님께서는 상관이 없다고는 하셨는데, 그래도 실무에서는 어떻게 구성하시는지 궁금해서 질문드리게 되었습니다!그리고 0장에서도 간단한 1개의 Job도 5개의 Step으로 이루어져 있는데, 위 질문의 AI답변처럼 1개의 Config 책임으로 두기보다는, Job - Step으로 책임을 분리하여 두는 것이 편할 것 같은데, 이게 실무에서도 실제로 이런 방향으로 관리가 이루어지는지 궁금합니다! 답변내용 참고하면서 본격적으로 1장부터 프로젝트를 구성해보고자 합니다. 감사합니다!
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
리스너의 실무 로직
킬구형 1장 - 작전3에서 아래와 같이 얘기한 부분에 대해 궁금한게 있어. '리스너는 감시와 통제만 담당한다. 실제 시스템 제거 로직(비즈니스 로직)은 분리하라. 리스너가 너무 많은 일을 하면 유지보수가 어려워지고 시스템 동작을 파악하기 힘들어진다' 청크 기반 배치 잡이라고 하고 A라는 테이블에서 데이터를 읽어와서 B라는 테이블에 데이터를 삽입하는데 B 테이블에 데이터가 없다면 삽입, 있다면 수정하는 로직이 있어. 이 과정들이 모두 끝나고 마지막으로 B 테이블에 수정 날짜 컬럼이 잡 시작 시간보다 이르다면 A 테이블에 데이터가 없으므로 B 테이블에서 이러한 데이터들을 삭제하려는 로직을 넣는다고 했을 때 아래 궁금증들이 있어.1. 위 얘기를 토대로 생각해보면 삭제 로직은 청크 기반 스텝 이후 태스크릿과 같은 다음 스텝으로 넣는게 좋은 것 같은데, 실무에서는 해당 잡 전용 리스너를 하나 추가로 만들어서 afterJob 메서드에 배치 상태가 COMPLETED인 경우에 삭제 로직을 실행하도록 하는 방식은 지양하는 편인거야? 전용 리스너를 만들어서 사용하는 경우도 있어?만약 리스너에 삭제 로직을 넣는다고 했을 때 리스너에서 데이터 삭제 과정 중 오류가 발생한 경우에는 잡이 실패 상태로 종료되는거지?2번과 같은 맥락인데 리스너에 삭제 로직을 넣는 경우 트랜잭션이 필요할텐데 리스너는 트랜잭션 범위가 어떻게 돼? 스텝에서는 청크 범위, 태스크릿의 반복 범위라고 본 걸로 기억하는데 리스너는 트랜잭션 설정 자체가 안되는건지 리스너 범위 내부에서만 설정되는건지 궁금해.
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
강의 구매는 했는데
윈도우 유저는 불가능한 강의인가요?
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
스프링 컨테이너 재시작 반복 현상
Jenkins를 바탕으로 Job들을 PipeLine으로 연결해서 사용중이다. Job들이 순차적으로 실행해야해서 아래처럼 파이프라인을 구성했다.pipeline { agent any stages { stage('Print Parameters') { steps { echo "=========================================" echo "🔧 Job Configuration" echo "=========================================" echo "startDate: ${params.startDate}" echo "endDate: ${params.endDate}" echo "pageNo: ${params.pageNo}" echo "numOfRows: ${params.numOfRows}" echo "=========================================" } } stage('Run abandonedAnimalDataLoadJob') { steps { script { echo "🐾 Starting abandonedAnimalDataLoadJob..." sh """ java -Duser.timezone=Asia/Seoul \\ -Dspring.profiles.active=local \\ -jar /var/jenkins_home/SeeYouAgain-Batch-0.0.1-SNAPSHOT.jar \\ --spring.batch.job.name=abandonedAnimalDataLoadJob \\ startDate=${params.startDate} \\ endDate=${params.endDate} \\ pageNo=${params.pageNo},java.lang.Long \\ numOfRows=${params.numOfRows},java.lang.Long """ echo "✅ abandonedAnimalDataLoadJob Completed!" } } } stage('Run s3ProfileUploadJob') { steps { script { echo "📤 Starting s3ProfileUploadJob..." sh """ java -Duser.timezone=Asia/Seoul \\ -Dspring.profiles.active=local \\ -jar /var/jenkins_home/SeeYouAgain-Batch-0.0.1-SNAPSHOT.jar \\ --spring.batch.job.name=s3ProfileUploadJob \\ startDate=${params.startDate} \\ endDate=${params.endDate} """ echo "✅ s3ProfileUploadJob Completed!" } } } } post { success { echo '=========================================' echo '✅ All Batch Jobs Completed Successfully!' echo '=========================================' } failure { echo '=========================================' echo '❌ Batch Job Failed. Please check the logs.' echo '=========================================' } always { echo "Pipeline execution finished at ${new Date()}" } } }로그를 찍어보니 스프링 컨테이너가 켜지고 첫번째 job이 끝난 후 컨테이너가 종료, 2번째 job을 실행하기 위해 스프링 컨테이너 켜지고 job이 끝난 후 컨테이너가 종료되는 비효율적인 현상을 발견했다. Claude한테 물어보니 job 실행시간이 짧아서 크게 문제없다고는 하지만, 개인적으로 Job이 많거나 추후에 오래 걸리는 Job이 추가될 경우엔 시간도 오래걸리고 리소스 낭비라고 생각한다. 이런 상황에선 어떻게 하는게 좋은가??