From f148559774216ee4bbdc1f07fbdd2fefdc51e2dd Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Tue, 14 Jul 2026 21:01:21 +0000
Subject: [PATCH 1/4] =?UTF-8?q?=E2=9A=A1=20Bolt:=20=EB=B7=B0=EC=96=B4=20?=
=?UTF-8?q?=EC=89=98=20HTML=20=EB=A0=8C=EB=8D=94=EB=A7=81=20=EC=84=B1?=
=?UTF-8?q?=EB=8A=A5=20=EC=B5=9C=EC=A0=81=ED=99=94?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
뷰어 컨트롤러에서 HTML을 렌더링할 때 크기가 큰 텍스트 템플릿에 여러 번의 `.replace()` 호출을 체이닝하는 방식을 사용했습니다. 이는 매 `.replace()` 호출마다 전체 문자열 길이만큼 새로운 문자열 객체를 할당하여 가비지 컬렉터와 성능에 악영향을 미칩니다.
정적 템플릿 부분들을 `static final` 상수로 분리하고 문자열 연결(concatenation) 방식으로 변경함으로써, `StringConcatFactory`를 통해 동적 객체 할당량을 크게 줄이고 렌더링 성능을 개선했습니다.
---
.jules/bolt.md | 3 +
.../viewer/controller/ViewerUiController.java | 162 ++++++++++--------
2 files changed, 91 insertions(+), 74 deletions(-)
diff --git a/.jules/bolt.md b/.jules/bolt.md
index 284d180..3f11c24 100644
--- a/.jules/bolt.md
+++ b/.jules/bolt.md
@@ -19,3 +19,6 @@
## 2026-07-13 - 단일 패스 문자열 치환 최적화 (O(N) 단일 스캔 및 지연 할당)
**Learning:** `String.replace()`를 여러 번 체이닝하여 호출하면, 문자열 치환이 발생하지 않는 경우에도 내부적으로 불필요한 스캔이 중복 발생하고, 치환 시마다 새로운 문자열 객체와 char 배열이 할당되어 메모리 낭비와 성능 저하(GC 압박)가 발생한다.
**Action:** 여러 문자를 한 번에 치환해야 하는 경우, O(N) 단일 스캔을 통해 `charAt()`으로 문자를 확인하고, 치환이 실제로 필요한 경우에만 `StringBuilder`를 지연 할당(Lazy allocation)하여 성능을 최적화하고 불필요한 메모리 할당을 방지한다.
+## 2026-07-14 - 뷰어 쉘 HTML 렌더링 성능 최적화
+**Learning:** 크기가 큰 텍스트 블록에 대해 여러 번의 `.replace()` 메서드 체이닝을 사용할 경우, 호출할 때마다 새로운 문자열 객체가 생성되어 불필요한 메모리 할당이 발생합니다.
+**Action:** 긴 문자열 템플릿에 동적 변수를 삽입해야 할 때는 정적인 템플릿 상수(static final)들로 분리한 뒤 문자열 연결(concatenation)을 사용하면, 컴파일 시 `StringConcatFactory`를 통해 최적화되어 요청당 객체 할당량을 크게 줄일 수 있습니다.
diff --git a/src/main/java/com/clearfolio/viewer/controller/ViewerUiController.java b/src/main/java/com/clearfolio/viewer/controller/ViewerUiController.java
index 5e7de3c..f00d00a 100644
--- a/src/main/java/com/clearfolio/viewer/controller/ViewerUiController.java
+++ b/src/main/java/com/clearfolio/viewer/controller/ViewerUiController.java
@@ -59,81 +59,95 @@ private static String escapeHtmlAttribute(String value) {
return HtmlUtils.htmlEscape(value);
}
- private static String viewerShellHtml(String docId, String initialState) {
+ // ⚡ Bolt: Use static template parts to avoid chained replace()
+ // allocations on every request.
+
+ /** Part 1 of the viewer shell template. */
+ private static final String VIEWER_SHELL_PART_1 = """
+
+
+
+
+ Document preview
+ Preparing preview shell...
+
+
+ Preview status
+
+ Loading...
+
+
+
Unable to load preview
+
+
+
+
+
+
+
+ Preview
+
+
+
+
When ready, the converted artifact will appear here.
+
+
+
+
+
+
+
+
+
+ """;
+
+ private static String viewerShellHtml(final String docId, final String initialState) {
String docIdString = escapeHtmlAttribute(docId);
- String template = """
-
-
-
-
- Document preview
- Preparing preview shell...
-
-
- Preview status
-
- Loading...
-
-
-
Unable to load preview
-
-
-
-
-
-
-
- Preview
-
-
-
-
When ready, the converted artifact will appear here.
-
-
-
-
-
-
-
-
-
- """;
-
- return template
- .replace("{{DOC_ID}}", docIdString)
- .replace("{{INITIAL_STATE}}", initialState)
- .replace("{{PDFJS_VIEWER_PATH}}", PDF_JS_VIEWER_PATH);
+ return VIEWER_SHELL_PART_1 + docIdString + VIEWER_SHELL_PART_2 + initialState
+ + VIEWER_SHELL_PART_3 + PDF_JS_VIEWER_PATH + VIEWER_SHELL_PART_4;
}
private static String demoShellHtml() {
From a984e59515b00fe4301ec73ca60e4ed615e8f01e Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Tue, 14 Jul 2026 21:37:42 +0000
Subject: [PATCH 2/4] =?UTF-8?q?=E2=9A=A1=20Bolt:=20=EB=B7=B0=EC=96=B4=20?=
=?UTF-8?q?=EC=89=98=20HTML=20=EB=A0=8C=EB=8D=94=EB=A7=81=20=EC=84=B1?=
=?UTF-8?q?=EB=8A=A5=20=EC=B5=9C=EC=A0=81=ED=99=94?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
뷰어 컨트롤러에서 HTML을 렌더링할 때 크기가 큰 텍스트 템플릿에 여러 번의 `.replace()` 호출을 체이닝하는 방식을 사용했습니다. 이는 매 `.replace()` 호출마다 전체 문자열 길이만큼 새로운 문자열 객체를 할당하여 가비지 컬렉터와 성능에 악영향을 미칩니다.
정적 템플릿 부분들을 `static final` 상수로 분리하고 문자열 연결(concatenation) 방식으로 변경함으로써, `StringConcatFactory`를 통해 동적 객체 할당량을 크게 줄이고 렌더링 성능을 개선했습니다.
From d44099ffec8598993d33c0094109a181a632bddf Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Tue, 14 Jul 2026 22:56:16 +0000
Subject: [PATCH 3/4] =?UTF-8?q?=E2=9A=A1=20Bolt:=20=EB=B7=B0=EC=96=B4=20?=
=?UTF-8?q?=EC=89=98=20HTML=20=EB=A0=8C=EB=8D=94=EB=A7=81=20=EC=84=B1?=
=?UTF-8?q?=EB=8A=A5=20=EC=B5=9C=EC=A0=81=ED=99=94?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
뷰어 컨트롤러에서 HTML을 렌더링할 때 크기가 큰 텍스트 템플릿에 여러 번의 `.replace()` 호출을 체이닝하는 방식을 사용했습니다. 이는 매 `.replace()` 호출마다 전체 문자열 길이만큼 새로운 문자열 객체를 할당하여 가비지 컬렉터와 성능에 악영향을 미칩니다.
정적 템플릿 부분들을 `static final` 상수로 분리하고 문자열 연결(concatenation) 방식으로 변경함으로써, `StringConcatFactory`를 통해 동적 객체 할당량을 크게 줄이고 렌더링 성능을 개선했습니다.
From 9e227f546297db58f9335d26225044f4440e0e89 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Tue, 14 Jul 2026 23:32:11 +0000
Subject: [PATCH 4/4] =?UTF-8?q?=E2=9A=A1=20Bolt:=20=EB=B7=B0=EC=96=B4=20?=
=?UTF-8?q?=EC=89=98=20HTML=20=EB=A0=8C=EB=8D=94=EB=A7=81=20=EC=84=B1?=
=?UTF-8?q?=EB=8A=A5=20=EC=B5=9C=EC=A0=81=ED=99=94?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
뷰어 컨트롤러에서 HTML을 렌더링할 때 크기가 큰 텍스트 템플릿에 여러 번의 `.replace()` 호출을 체이닝하는 방식을 사용했습니다. 이는 매 `.replace()` 호출마다 전체 문자열 길이만큼 새로운 문자열 객체를 할당하여 가비지 컬렉터와 성능에 악영향을 미칩니다.
정적 템플릿 부분들을 `static final` 상수로 분리하고 문자열 연결(concatenation) 방식으로 변경함으로써, `StringConcatFactory`를 통해 동적 객체 할당량을 크게 줄이고 렌더링 성능을 개선했습니다.