본문 바로가기

개발노트/JAVA

Apache poi 이용하여 excel 에 아주 많은 이미지 넣을 때 급격히 느려지는 현상 우회하는 야매 방법

5000개 가량의 이미지가 apache poi 로 생성되는 엑셀 파일에 삽입되어야 하는 needs 가 있었는데, apache poi 라이브러리에서 병목 구간이 있어 이를 찾아내고 우회하는 방법을 정리해두면 나중에 급할 때 이런식으로라도 문제를 해결할 수 있음을 확인하기 위해 정리해본다. 몇시간을 날려먹었는지...

옛날 프로젝트라 apache poi 3.15 버전이 dependency 로 import 되어있었다. 프로젝트 빌드 툴은 maven 기반이었다.

디버깅으로 각 메소드 호출이 느려지는 부분을 찾아 들어가보니 `POIXMLDocumentPart` 의 `addRelation` 메소드에서 `findExistingRelation()` 메소드를 호출하는 것이 병목 구간임을 알 수 있었다.

private PackageRelationship findExistingRelation(POIXMLDocumentPart part) {
        String ppn = part.getPackagePart().getPartName().getName();

        try {
            Iterator var3 = this.packagePart.getRelationships().iterator();

            while(var3.hasNext()) {
                PackageRelationship pr = (PackageRelationship)var3.next();
                if (pr.getTargetMode() != TargetMode.EXTERNAL) {
                    PackagePart pp = this.packagePart.getRelatedPart(pr);
                    if (ppn.equals(pp.getPartName().getName())) {
                        return pr;
                    }
                }
            }

            return null;
        } catch (InvalidFormatException var6) {
            throw new POIXMLException("invalid package relationships", var6);
        }
    }

Iterator 의 size 가 cell 수가 늘어나면 늘어날수록 몇천 단위가 되게 되는데, MS office 제품 군에서 사용되는 개념인 relation(이게 뭔진 정확하게 아직도 모르겠음)을 찾는 로직 실행이 가장 느렸다.

그래서 이미지를 삽입하는데 호출되는 저 메소드에서 existingRelation 을 찾아서 제대로 return 하느냐?

디버깅을 해보니 항상 null 로 리턴되는 것을 확인할 수 있었다.

그래서 해당 코드만 무조건 null 로 리턴되게 하면 느려지지 않을 것 같았다.

생각이 여기까진 금방 도달했다.

하지만 컴파일된 외부 라이브러리 코드를 어떻게 수정하지?

시도해본 방법은 아래와 같다.

1. 소스 코드 다운 받은 후 로컬 maven repository 에 가서 파일 수정 후 다시 jar 로 만들어 봐야지 

-> 실패. intelliJ 에서 받은 소스코드로는 pom.xml 도 없고 build.xml 등도 없어서 컴파일/빌드를 할 수 없었다.

2. (미친 생각) 모든 관련 라이브러리 파일을 수정가능하게 내 프로젝트의 src dir 에 복/붙

-> 실패. 생각 자체가 잘못됐기도 했지만 막상 다 넣어보니 꼬리에 꼬리를 무는 의존성->의존성에 결국은 import 할 수 없는 클래스가 내 소스 코드에서 import 되어있으니 compile 자체가 안 되는 상황

3. https://archive.apache.org/dist/poi/release/src/ 에서 full source code 를 다운받은 후 소스 코드 수정 후 ant build 를 수행하여 수정된 코드가 포함된 jar 파일을 만들어 그걸 로컬 maven repository 에 덮어쓰기

-> 성공. 시간도 가장 적게걸렸고 자연스러운 방법이었던 것 같다.

수정한 부분은 딱 2줄이다. `import` 문과 `PackageRelationship pr = relationshipType == XSSFRelation.IMAGES ? null : this.findExistingRelation(part);` 부분.

// POIXMLDocumentPart.class
import org.apache.poi.xssf.usermodel.XSSFRelation;
...
    public final POIXMLDocumentPart.RelationPart addRelation(String relId, POIXMLRelation relationshipType, POIXMLDocumentPart part) {
        PackageRelationship pr = relationshipType == XSSFRelation.IMAGES ? null : this.findExistingRelation(part);
        if (pr == null) {
            PackagePartName ppn = part.getPackagePart().getPartName();
            String relType = relationshipType.getRelation();
            pr = this.packagePart.addRelationship(ppn, TargetMode.INTERNAL, relType, relId);
        }

        this.addRelation(pr, part);
        return new POIXMLDocumentPart.RelationPart(pr, part);
    }

코드 수정 후 Ant build 이용, jar 패키징

수정 후 아래와 같이 덮어쓰기 하고 나서,

덮어 씌운 후

해당 라이브러리를 사용하는 프로젝트에 가서 External Libraries 에서 찾아보니 내가 수정한대로 디컴파일된 클래스를 확인할 수 있었다

수정이 반영된 라이브러리 인식

그런 다음 다시 엑셀 생성 로직을 실행시켜 보면 전혀 느려지지 않고 linear 한 속도로 완료됨을 볼 수 있다.

엑셀 자체가 많은 이미지를 보여주기 위한 프로그램이 아닌데, 아무래도 업 특성상 가끔씩 이런 일이 필요한데 null 로 return 될 것을 뻔히 아는 값을 하염없이 기다리는 것은 비효율적이라 삽질을 좀 많이 했지만 이런식으로라도 처리할 수 있었음을 정리해봄.

반응형