Skip to content

fix: EgovFlatFileByteReader 가 ExecutionContext 키를 FlatFileItemReader 이름으로 등록해 재시작 상태가 섞이는 문제 수정 - #365

Open
wantaekchoi wants to merge 1 commit into
eGovFramework:mainfrom
wantaekchoi:fix/flatfilebytereader-context-name
Open

fix: EgovFlatFileByteReader 가 ExecutionContext 키를 FlatFileItemReader 이름으로 등록해 재시작 상태가 섞이는 문제 수정#365
wantaekchoi wants to merge 1 commit into
eGovFramework:mainfrom
wantaekchoi:fix/flatfilebytereader-context-name

Conversation

@wantaekchoi

Copy link
Copy Markdown
Contributor

수정 사유 Reason for modification

  • 버그수정 Bug fixes
  • 기능개선 Enhancements
  • 기능추가 Adding features
  • 기타 Others

수정된 소스 내용 Modified source

EgovFlatFileByteReader 는 생성자에서 자기 이름 대신 FlatFileItemReader 이름을 등록합니다. 같은 모듈의 다른 두 곳은 자기 클래스 이름을 씁니다.

$ git grep -n "setName(" origin/main -- 'Batch/org.egovframe.rte.bat.core/src/main/java/*'
.../item/database/EgovMyBatisPagingItemReader.java:65:        setName(getShortName(EgovMyBatisPagingItemReader.class));
.../item/file/EgovFlatFileByteReader.java:73:        setName(ClassUtils.getShortName(FlatFileItemReader.class));
.../item/file/EgovPartitionFlatFileItemWriter.java:104:        setName(ClassUtils.getShortName(EgovPartitionFlatFileItemWriter.class));

이 이름은 표시용이 아니라 재시작 상태를 저장하는 키의 접두어입니다. ExecutionContextUserSupport.getKey(String)name + "." + suffix 를 만들고, AbstractItemCountingItemStreamItemReaderopen/updateread.count 를 그 키로 읽고 씁니다.

그런데 표준 FlatFileItemReader 는 상속 조상이 아니라 같은 부모를 둔 형제이고, 자기 생성자에서 같은 이름을 무조건 설정합니다.

$ javap -cp spring-batch-infrastructure-5.2.3.jar org.springframework.batch.item.file.FlatFileItemReader
public class org.springframework.batch.item.file.FlatFileItemReader<T> extends org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader<T> implements ...

$ javap -p -c -cp spring-batch-infrastructure-5.2.3.jar org.springframework.batch.item.file.FlatFileItemReader   # 생성자 끝
61: ldc           #11   // class org/springframework/batch/item/file/FlatFileItemReader
63: invokestatic  #51   // Method org/springframework/util/ClassUtils.getShortName
66: invokevirtual #57   // Method setName:(Ljava/lang/String;)V

두 reader 가 한 Step 에 스트림으로 함께 등록되면 FlatFileItemReader.read.count 하나를 공유합니다. CompositeItemStream.register 는 동일 인스턴스만 걸러내므로 이름 충돌은 통과합니다. 나중에 쓴 쪽의 카운트가 앞의 것을 덮어써서, 재시작할 때 아직 처리하지 않은 레코드를 건너뜁니다.

가정한 설정은 아닙니다. 이 저장소의 테스트 Job 이 평범한 FlatFileItemReader 를 Step 스트림으로 명시 등록하고 있습니다.

// CompositeItemWriterSampleJob.java:156 — fileItemReader() 는 FlatFileItemReader<Trade> (같은 파일 :71)
.stream(fileItemReader())

같은 모듈의 DefaultItemReader·EgovIndexFileReader 도 결국 FlatFileItemReader.read.count 에 씁니다(DefaultItemReader.java:130,138 / EgovIndexFileReader.java:108,117 에서 내부 FlatFileItemReader 인스턴스로 open/update 를 위임). 다만 그쪽은 실제로 FlatFileItemReader 를 만들어 쓰므로 이름이 사실과 맞고, 바꾸면 영향 범위가 훨씬 넓어져 이번 수정에서는 제외했습니다.

AS-IS / TO-BE

형제 두 곳의 형태에 맞췄습니다.

 public EgovFlatFileByteReader() {
-    setName(ClassUtils.getShortName(FlatFileItemReader.class));
+    setName(ClassUtils.getShortName(EgovFlatFileByteReader.class));
 }

FlatFileItemReader 임포트는 이 한 줄에서만 쓰여 함께 제거했습니다.

영향 범위

저장소 안에서 이 클래스를 참조하는 운영 코드나 XML 설정은 없습니다. README 표 한 줄과 테스트뿐입니다.

$ git grep -n "EgovFlatFileByteReader" | grep -v EgovFlatFileByteReader.java | grep -v EgovFlatFileByteReaderTest.java
Batch/org.egovframe.rte.bat.core/README.md:61:| **Reader/Writer** | `EgovFlatFileByteReader`, `EgovIndexFileReader` / `EgovIndexFileWriter`, `EgovPartitionFlatFileItemWriter` |

하위호환은 리뷰어 판단이 필요한 부분이라 먼저 적어 둡니다. ExecutionContext 키가 FlatFileItemReader.read.count 에서 EgovFlatFileByteReader.read.count 로 바뀌는데, v5.0.0 FINAL(4d97ab5)은 이미 origin/main 의 조상이고 루트 pom 도 5.0.0 이므로 기존 키는 이미 배포된 키입니다. 기존 키로 저장된 미완료 StepExecution 을 업그레이드 후 재시작하면 새 키를 찾지 못해 처음부터 다시 읽습니다(중복 처리).

open() 에서 새 키가 없을 때만 구 키를 읽는 폴백을 두는 선택지가 있습니다. 릴리스 정책에 달린 문제라고 보아 이번 diff 에는 넣지 않았습니다.

JUnit 테스트 JUnit tests

  • JUnit 테스트 JUnit tests
  • 수동 테스트 Manual testing

기존 EgovFlatFileByteReaderTest 에 재시작 시나리오를 더했습니다. Step 이 실제로 쓰는 CompositeItemStream 에 표준 FlatFileItemReader(4줄)와 EgovFlatFileByteReader(고정길이 4레코드)를 등록하고, 앞의 것 1건 / 뒤의 것 3건을 읽은 뒤 update()·close() 하고 저장된 ExecutionContext 로 다시 엽니다. 리플렉션이나 스텁 없이 실제 클래스만 씁니다.

충돌을 드러내는 것은 표준 FlatFileItemReader 쪽 단언입니다. 1건만 처리했으므로 2번째 라인이 나와야 합니다. 앞에 있는 EgovFlatFileByteReader 단언은 수정 전에도 통과하며, 이번 수정이 자기 쪽 재시작을 깨지 않았는지 보는 회귀 가드입니다.

수정한 한 줄만 되돌리면 실패합니다.

$ mvn -o test -pl Batch/org.egovframe.rte.bat.core -Dtest=EgovFlatFileByteReaderTest
[ERROR] EgovFlatFileByteReaderTest.executionContextKeyMustNotCollideWithFlatFileItemReader:106 표준 FlatFileItemReader 는 1건만 처리했으므로 2번째 라인부터 다시 읽어야 한다 — EgovFlatFileByteReader 가 같은 키를 쓰면 처리하지 않은 라인이 건너뛰어진다 ==> expected: <2> but was: <4>
[ERROR] Tests run: 4, Failures: 1, Errors: 0, Skipped: 0
[INFO] BUILD FAILURE

수정 후 모듈 전체입니다.

$ mvn -o test -pl Batch/org.egovframe.rte.bat.core
[INFO] Tests run: 87, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS

테스트 브라우저 Test Browser

  • Chrome
  • Firefox
  • Edge
  • Safari
  • Opera
  • Internet Explorer
  • 기타 Others

테스트 스크린샷 또는 캡처 영상 Test screenshots or captured video

화면이 없는 실행환경 모듈이라 첨부하지 않았습니다.

…이름으로 등록해 재시작 상태가 섞이는 문제 수정

AbstractItemCountingItemStreamItemReader 에 넘기는 이름은 표시용이 아니라
ExecutionContext 키의 접두어다. update() 가 이 이름으로 read.count 를 쓰고
open() 이 같은 이름으로 읽어 재시작 위치를 복원한다.

이 클래스는 FlatFileItemReader 를 상속하지 않는다. 그런데 생성자가
그 이름을 넣는다. 표준 FlatFileItemReader 는 자기 생성자에서 같은 이름을
무조건 넣으므로, 한 Step 에 둘이 함께 등록되면 FlatFileItemReader.read.count
하나를 나눠 쓴다. 그러면 재시작 때 서로의 진행 위치를 읽어 건너뛰거나
같은 줄을 다시 처리한다.

같은 모듈의 EgovMyBatisPagingItemReader 와 EgovPartitionFlatFileItemWriter 는
자기 클래스 이름을 쓴다. 그 형태에 맞췄다.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant