withReader 람다에서 스트림을 밖으로 반환Stream<String> lines = BatchUtils.withReader(file, r -> r.lines());
lines.forEach(System.out::println); // UncheckedIOException: Stream closed람다가 끝나는 순간 try-with-resources가 BufferedReader를 닫습니다. 지연 스트림은 그 뒤에 읽으려 하므로 실패합니다.
✅ 람다 안에서 소비를 끝내고 결과(리스트, 숫자)를 돌려줍니다.
List<String> lines = BatchUtils.withReader(file, r -> r.lines().toList());clear()로 재사용List<String> buf = new ArrayList<>();
for (String line : lines) {
buf.add(line);
if (buf.size() == size) { executor.submit(() -> save(buf)); buf.clear(); } // 비동기 작업이 빈 리스트를 봄
}핸들러가 비동기이거나 청크를 보관하면, clear() 후 같은 객체가 비워지거나 다음 청크로 덮입니다.
✅ 청크마다 새 리스트를 만듭니다: buf = new ArrayList<>(size);
for (String line : lines) {
buf.add(line);
if (buf.size() == size) { handler.accept(buf); buf = new ArrayList<>(); }
}
// 루프 끝. buf에 남은 3건은?총 건수가 청크 크기의 배수가 아니면 마지막 몇 건이 처리되지 않습니다. 테스트 데이터가 우연히 배수면 발견하지 못합니다.
✅ 루프 뒤에 if (!buf.isEmpty()) handler.accept(buf);를 반드시 둡니다.
try (BufferedWriter w = Files.newBufferedWriter(out)) {
for (...) w.write(...); // 중간에 죽으면 반쪽 파일
}
// 다음 실행: Files.exists(out) → true → "이미 처리됨"으로 스킵. 반쪽 결과가 영원히 남음파일이 존재한다는 것과 완전하다는 것은 다릅니다.
✅ writeAtomically처럼 .tmp에 쓰고 ATOMIC_MOVE로 이름을 바꿉니다. 존재 = 완전.
retry(5, () -> Integer.parseInt("abc")); // 5번 모두 즉시 같은 NumberFormatException
retry(5, () -> callApi()); // 서버가 과부하인데 0ms 간격으로 5연타결정적 실패는 몇 번을 해도 같고, 일시적 실패는 간격 없이 때리면 상대를 더 악화시킵니다.
✅ 재시도할 예외 종류를 지정하고 백오프를 넣습니다.
static <T> T retry(int max, long waitMs, Class<? extends Exception> retryOn, Supplier<T> action) {
for (int i = 1; ; i++) {
try { return action.get(); }
catch (RuntimeException e) {
if (!retryOn.isInstance(e) || i == max) throw e;
try { Thread.sleep(waitMs * i); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); throw e; }
}
}
}LocalDate.now() 사용public static String todayFileName(String prefix) {
return prefix + "_" + LocalDate.now().format(YMD) + ".csv"; // 어제 데이터를 재처리할 방법이 없음
}날짜가 코드에 박히면 밀린 잡 재실행, 테스트, 자정 근처 실행이 모두 문제가 됩니다.
✅ 날짜는 항상 파라미터(JobParams.date)로 받고, "오늘"은 main에서 한 번만 결정합니다.
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
uploadAllLogsToS3(); // 수 분 소요. 스케줄러가 타임아웃으로 kill -9
}));훅은 JVM 종료를 붙잡습니다. 컨테이너 환경은 보통 10~30초 후 강제 종료하므로 그 안에 끝나야 합니다.
✅ 훅에는 삭제·플래그 기록처럼 밀리초 단위 작업만 두고, 무거운 정리는 다음 실행 시작 시 "이전 실행 잔재 정리" 단계로 옮깁니다.