23.01 / 프로젝트 Reboot

신년맞이 새 출발

새해를 맞이했다. 그리고 어느덧 서비스 2주년 차다. 그리고 서비스 코드는 난잡 그 자체가 되어있었다.

어쨌든, 포트폴리오 용으로 시작되었던 나의 프로젝트는 이제 하루 이틀 서비스하다 튈 수 있는 상황이 아니란 것을 깨달았다. 사실 서비스 자체에서 나오는 수익이 얼마 안 되서 동기부여가 약해진 시기도 있었다. 그럴 때 블로그나 카페 댓글에서 내 사이트를 추천해주는 댓글을 보며 다시 힘을 얻기도 했지만, 항상 이건 내가 만들고 홍보한 서비스이기 때문에 도시 공학 업계가 망해서(?) DAU 0명 찍는 날 까지는 운영해야 한다는 소소한 책임감을 항상 가지고 있었기 때문에 그래도 여기까지 오지 않았나 싶다.

이제는 서비스 장기 유지/보수를 위해서는 대대적인 개선이 필요한 시점이었다. 개선을 위해서는 현재 문제 파악이 가장 중요한 법, 2년 간 어떤 점이 나를 나락의 구렁텅이로 끌고 갔었는지, 어떻게 해결할 것인지 곰곰이 생각해 보았다.

개선 포인트 1. 이미지 크롤링

정말 힘들었던 아이다. 이 놈 문제가 진짜 너무 많았는데, 최대한 간추려서 적어 보겠다.

문제의 배경

21.08 / 서비스 기능 확장 성공담 편에 자세히 적어 놓기는 했는데, 간단하게 요약하고 시작하자면,

초창기에는 네이버 지도 만을 이용한 크롤링을 제공했는데, 카카오 지도를 원하는 유저들이 많았다. 보통 사람들이 위성 이미지를 많이 크롤링 해가는데, 카카오가 위성 이미지 최신화 속도가 빨랐다. 근데 네이버와 카카오 api의 결정적 차이가 네이버는 위,경도 값에 따른 이미지 rest api를 제공해 주었고 카카오는 자바스크립트 라이브러리를 호출해서 프론트 단에서 위성 이미지를 동적으로 만들어야 했다. 카카오 지도를 만들기 위해서는 일반 유저가 하는 것과 같이 브라우저를 띄우고, 이미지 로딩을 기다리는 수 밖에 없었다.

1. 서버 자원

문제는 나와 같이 영세한 서버에서는 이런 과정이 너무 부담스러웠다. 메모리 사용량도 문제이거니와, 순간적으로 그 넓은 지도를 로딩하고 캡쳐하는 과정에서 CPU 점유율도 치솟는 바람에 다른 서비스들도 순간적으로 먹통이 된 적이 많았다.

22.05 / 유저 몰리는 시간에 서비스 속도 저하 편에서 서버를 2대로 나눠서 해결한 내용이 있다. 그런데 이거 오라클이 무료 서버 2대를 제공해줘서 다행이지, 아니었으면 답도 없는 상황이었다. 게다가 자꾸 오라클이 서버를 강제로 회수해 간다는 이슈들이 들려서 aws로 이주 준비 중 이었는데 서버를 기존처럼 2대 빌릴 지, 성능 좋은 1대를 빌려야 하는지 골머리가 아팠다.

2. 대기열 발생

서버를 2대로 나눠도 여전히 문제는 많았다. 한 번에 여러 개의 이미지를 렌더링 할 형편이 안 되서 유저 요청을 한 번에 하나씩 처리하는 방식으로 만들었는데, 이미지 한 장 만드는데 30초 소요되는데 1시간 동안 유저 80명씩 오는 날에는 대책이 안 섰다.

3. 웹소켓 사용으로 인한 코드 복잡도

대기열은 있는데, 유저한테 대기 순번 정도는 알려줘야 할 것 같아서 웹소켓을 이용하게 되었었다. 근데 도저히 내 능력으로는 코드가 깔끔하게 정리가 되지를 않았다. 최대한 기존 컨트롤러들과 결이라도 좀 비슷하게 유지해보려고 Stomp같은 구독 / 발행 라이브러리로 유사 컨트롤러 흉내는 냈지만, 결국 남은건 스파게티 뿐이었다.

해결 과정

1. aws Lambda

이를 해결하기 위해서 제일 눈여겨 봤던 서비스였다. 실행 환경을 공유하지 않고, 일종의 1회용 컴퓨터를 빌릴 수 있다는 점이 매력적이었다. 그런데 선뜻 손이 안 가던 것이, 21.07 / 서비스 기능 확장 실패담 에서 비슷한 기능인 네이버 클라우드 펑션을 사용하다가 요금 폭격 한 번 맞기도 했었고, 람다의 응답 용량이 1MB 밑으로 제한된다는 점에서 이미지를 어떤 방식으로 전송해야 할 지 감이 안 왔다. 물론 기존 서비스에서는 위성 이미지를 분할해서 보내주는데, 람다로 어떻게 구현해야 할 지 고민이 많았다. 이미지를 쪼개서 여러 번 보내려면 커넥션을 계속 유지해야 하고, 이러면 또 웹소켓 지옥 시작일 것 같아서 일단 보류하고 있었다.

2. Mapshot <-> aws Lambda

 for (let y = 0; y < goal_width; y += WIDTH) {
    for (let x = 0; x < goal_width; x += WIDTH) {

       await page.evaluate((x, y) => {
           window.scrollBy(x, y);
       }, x, y);

       let imageBuffer = await page.screenshot({
            type: "jpeg"
        });
    
        let gen_uuid = uuidv4();
        
        // 내 서버에다가 임시 보관 요청
        await axios.post(domain + "/image/storage", {
            "uuid": gen_uuid,
            "base64EncodedImage": imageBuffer.toString('base64'),
        });
          
        let response = {
            "uuid": gen_uuid,
            "x": x,
            "y": y
        };

      
      response_arr.push(response);
    }
  }

어느 날 친구랑 밥 먹다가 문득 람다가 떠올랐다. 그런데 이전과는 다른 발상이 떠올랐다. 굳이 람다로 직접 이미지를 전달할 것이 아니라, 내 Mapshot 서버에다가 이미지를 잠깐 쟁여놓게 하고 유저에게는 그 이미지를 찾을 키 값만 전달해 주면 되겠다는 생각이었다. 불현듯 떠오른 이 생각 덕분에 자원, 대기열, 웹소켓의 늪에서 벗어날 수 있었다.

개선 포인트 2. 프론트 / API 서버 분리

도입 배경

이건 장애 대비용으로 시작한 것이 제일 컸다. 다행히 아직까지는 서버 프로그램, 즉 jar 인스턴스 자체가 죽는 일은 없었지만 혹시 모르기도 하고, 프론트 서버는 별도로 배포하기에 좋은 서비스들이 많이 존재하기 때문에(netlify, cloudflare Pages) 인프라 단에서 내 실수로 서비스가 장애가 날 일은 거의 없다고 판단했다. 그리고 내 서비스 시작이 정적 페이지였던 만큼, 클라이언트 단에서 모두 처리가 가능한 기능들도 존재하기 때문에 만약 API 서버가 모조리 죽어도 어느 정도 정상 작동이 가능하다.

개선 포인트 3. 기술 스택 업그레이드

1. Vue.js 도입

이건 예전부터 생각 중이었는데, IE라는 장벽에 계속 막히고 있었다. 높을 때는 서비스 이용자의 20%가 인터넷 익스플로러를 통해 접속 중 이었는데, 이로 인해 CSS 라이브러리, 자바스크립트 일부 문법 등 많은 제약들이 있었다. 그런데 어느 순간 IE를 통한 접속이 점점 줄기 시작하더니, 드디어 거의 미비한 수준으로 수렴했다. 사실 Vue를 되게 능숙하게 다루진 못하지만, 그래도 편하다고 느낀 것이 UI 수정이었다. 그 전에는 일일이 document.getElementById 이런 식으로 요소에 접근해서 속성이나 텍스트 값을 바꿔주다 보니 코드 관리도 힘들었는데, 이제 관련 기능을 Vue에서 처리해주니 한결 편했다.

2. Docker 도입

사실 원래 의도는 프로세스 사용량에 제한을 걸기 위해 도입했었다. 람다를 고려하고 있지 않은 상태에서 프로젝트를 갈아엎기 시작했고, 1대의 서버만으로 이미지 크롤링 + 기타 API들을 원활히 구동하기 위해서는 이미지 크롤링 프로세스 혼자서 CPU를 다 점유하는 상황은 막아야 하지 않을까 싶어서 도입을 했다. 그런데 이제 이미지 프로세싱을 람다에 위임하다 보니, 원래 의도랑 달라져서 빼버릴까 싶기도 했는데 아직 구축하지 않은 CI/CD에서 Github Action + Docker hub 조합을 사용하면 상당히 편하다는 말을 들어서 일단은 계속 사용하는 방향으로 가닥을 잡았다.

개선 포인트 1번부터 너무 글을 길게 적은 것 같아서 최대한 간략하게 나머지를 쓰려고 하니 뒤로 갈수록 어째 글이 빈약해보인다. 일단 계획은 이번 구정 즈음에 새 서비스 배포가 목표다. 코드 너무 많이 바꾸면 항상 어딘가에서 터지던데, 부디 별 탈 없이 진행되면 좋겠다.

Last updated