안녕하세요! 코드브릭스(Code-Bricks)를 만들어가고 있는 프론트엔드 개발자입니다.
오늘은 제 개인 웹사이트에 새롭게 런칭할 유용한 생산성 툴인 '텍스트 차이점 분석기(Diff Checker)'의 개발 비하인드 스토리를 들려드리려고 합니다. (※ 현재 열심히 마무리 작업 중이며, 실제 동작하는 차이점 분석기 사이트 링크는 추후 본문 하단에 연결해 둘 예정이니 많은 기대 부탁드립니다!)
개발자라면 누구나 Git이나 IDE를 통해 매일같이 보는 화면이 바로 코드의 변경점(Diff)을 보여주는 화면일 텐데요. 문득 "개발자가 아닌 일반인들은 두 개의 긴 문서를 어떻게 비교할까?"라는 호기심에서 이 토이 프로젝트가 시작되었습니다.
서버 비용 0원으로 완벽한 보안을 자랑하는 클라이언트 사이드 Diff Checker의 기획부터, 텍스트 비교의 핵심 두뇌인 'LCS 알고리즘'의 딥다이브까지! 개발 블로그 [메이킹 로그 1편]을 지금 바로 시작하겠습니다.
1. 텍스트 차이점 분석기, 도대체 왜 만들었을까? (기획 배경)
개발자들에게는 소스 코드의 변경 이력을 추적하는 것이 숨 쉬는 것만큼 자연스러운 일입니다. 추가된 코드는 초록색으로, 삭제된 코드는 빨간색으로 보여주는 편리한 도구들이 널려 있으니까요. 하지만 시선을 조금만 돌려보면, 우리 주변에는 두 개의 텍스트를 눈이 빠지게 비교해야 하는 직군이 정말 많습니다.
가장 대표적인 예가 바로 법무팀이나 계약서 검토자입니다. 수십 장에 달하는 계약서 초안을 상대방 업체에 보냈는데, 상대방이 교묘하게 독소 조항 하나를 슬쩍 수정해서 최종본이라고 보내왔다면 어떨까요? 사람이 두 눈으로 수십 페이지의 텍스트를 대조하며 바뀐 단어 하나를 찾아내는 것은 엄청난 시간 낭비이자 스트레스입니다.
작가나 번역가, 에디터 분들 역시 자신의 원고가 교정 과정을 거치며 어디가 어떻게 윤문되었는지 확인해야 할 때가 많죠. 그래서 저는 개발자의 전유물처럼 여겨지던 'Diff(차이점 비교)' 기능을 누구나 웹 브라우저에서 직관적이고 가볍게 쓸 수 있도록 심플한 웹 기반의 텍스트 차이점 분석기를 기획하게 되었습니다.

2. 핵심 기획 의도: 서버비 0원, 기밀 문서 유출률 0%
이 툴을 기획하면서 가장 중요하게 생각한 원칙은 단 하나, "절대로 사용자의 텍스트 데이터를 서버로 전송하지 않는다"는 것이었습니다.
사용자들이 이 분석기에 넣고 돌릴 데이터는 무엇일까요? 출시 전의 신제품 소스 코드, 회사의 기밀이 담긴 B2B 계약서, 출판 전의 미공개 소설 원고 등 극비 문서일 확률이 매우 높습니다. 만약 제가 이 텍스트들을 제 서버(Node.js나 Spring 등)로 전송받아 비교 연산을 한 뒤 결과값을 돌려주는 백엔드 아키텍처로 설계했다면 어땠을까요?
사용자들은 "이 사이트 개발자가 내 기밀 문서를 서버 DB에 몰래 저장해서 훔쳐보면 어떡하지?"라는 불안감 때문에 절대 이 툴을 쓰지 않을 것입니다.
그래서 저는 백엔드 연산을 과감히 포기하고, 100% 클라이언트 사이드(Client-side)에서 동작하는 구조를 채택했습니다. 사용자가 입력한 원본 텍스트와 수정본 텍스트는 오직 사용자의 컴퓨터(웹 브라우저) 내에서 자바스크립트(JavaScript) 엔진에 의해서만 연산됩니다. 연산이 끝나면 결과만 화면에 뿌려줄 뿐, 인터넷망을 타고 외부로 나가는 데이터는 1바이트도 없습니다. 브라우저 창을 닫거나 새로고침하는 순간 모든 데이터는 공중으로 완벽하게 증발하죠.
이러한 클라이언트 사이드 아키텍처 덕분에 사용자들에게는 '보안 100%'라는 강력한 신뢰를 줄 수 있었고, 제 입장에서는 연산 처리를 위한 값비싼 AWS나 EC2 서버를 대여할 필요가 없어 '서버 유지비 0원'이라는 일석이조의 효과를 달성할 수 있었습니다.
3. 차이점 분석기의 두뇌: LCS 알고리즘의 이해
자, 그렇다면 브라우저 안에서 두 개의 텍스트가 어떻게 다르고 같은지 알아내는 수학적 원리는 무엇일까요? 여기서 등장하는 것이 바로 알고리즘 문제 풀이(PS)를 해보신 분들이라면 한 번쯤 들어보셨을 동적 계획법(DP)의 꽃, LCS(Longest Common Subsequence, 최장 공통 부분 수열) 알고리즘입니다.
LCS 알고리즘은 두 개의 문자열(혹은 배열)이 주어졌을 때, 두 문자열에 공통으로 존재하는 부분 문자열 중에서 가장 긴 것을 찾아내는 방법입니다. 여기서 중요한 점은 부분 수열(Subsequence)은 연속되지 않아도 순서만 맞으면 인정된다는 것입니다.
예를 들어, "ABCBDAB"라는 원본 문자열과 "BDCAB"라는 수정본 문자열이 있다고 가정해 봅시다. 이 두 문자열의 가장 긴 공통 분모는 "BCAB" 혹은 "BDAB"가 됩니다. 이 공통 분모(뼈대)를 먼저 확실하게 찾아내면, 나머지 문자들은 원본에서 삭제된 것(-)이거나 수정본에 새롭게 추가된 것(+)으로 아주 쉽게 분류할 수 있게 됩니다.
// LCS 알고리즘의 기본적인 DP 2차원 배열 초기화 로직 (JS 예시)
function buildLCSMatrix(text1, text2) {
const n = text1.length;
const m = text2.length;
const dp = Array(n + 1).fill(null).map(() => Array(m + 1).fill(0));
for (let i = 1; i <= n; i++) {
for (let j = 1; j <= m; j++) {
if (text1[i - 1] === text2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1; // 문자가 같으면 대각선 값 + 1
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); // 다르면 위나 왼쪽 중 큰 값
}
}
}
return dp;
}

4. LCS 알고리즘의 시간 복잡도와 성능 최적화 고민
텍스트 비교의 정확도를 위해 LCS는 필수적이지만, 사실 이 알고리즘은 성능 면에서 꽤 무거운 편에 속합니다.
LCS 알고리즘의 연산 방식을 수학적으로 분석해 보면, 길이가 인 문자열과 길이가 인 문자열을 비교하기 위해 크기의 2차원 배열(Matrix)을 메모리에 만들고 모든 칸을 순회하며 값을 채워 넣어야 합니다.
점화식을 수학적 기호로 나타내면 다음과 같습니다. 만약 라면:
만약 라면:
이 공식에 따라 2중 for 문이 돌아가기 때문에, 전체 시간 복잡도와 공간 복잡도는 모두 두 텍스트 길이의 곱에 비례하게 됩니다.
일반적인 몇백 줄의 텍스트 비교는 현대의 빠른 V8 자바스크립트 엔진이 눈 깜짝할 새(1초 이내)에 처리해 냅니다. 하지만 만약 사용자가 1만 줄이 넘어가는 방대한 서버 로그 파일이나 수만 단어의 소설 원고를 통째로 집어넣는다면 어떻게 될까요? (1억) 번 이상의 연산이 브라우저의 메인 스레드에서 동기적으로 돌게 되고, 결국 연산이 끝날 때까지 화면이 멈춰버리는(Freezing) 치명적인 성능 저하가 발생할 수 있습니다.
단순히 알고리즘을 구현하는 것을 넘어, 브라우저가 뻗지 않도록 대용량 텍스트 연산의 병목 현상을 어떻게 해결할 것인지가 프론트엔드 개발자로서 해결해야 할 다음 과제였습니다.
이 성능 저하 문제를 해결하기 위해 Web Worker를 도입하고, GitHub처럼 직관적인 추가(+)/삭제(-) 색상 하이라이팅 UI를 어떻게 렌더링했는지는 다음 포스팅인 [메이킹 로그 2편: UI/UX 설계와 대용량 텍스트 렌더링 최적화]에서 더 깊이 있게 다뤄보도록 하겠습니다!
코드브릭스의 '텍스트 차이점 분석기' 완성본 링크는 작업이 완료되는 대로 이곳에 추가해 둘 테니 잊지 말고 다시 찾아와 주세요. 오늘도 즐거운 코딩 하시길 바랍니다. 감사합니다!