Backend Developer

오션뷰 개발일지 - v1 해대인

해대인

우연한 기회로 다니던 대학교에서 사용하는 유틸앱을 만들 기회가 생겼다. 나는 서버 개발을 맡게 되었고 처음으로 실제로 운영하는 서비스를 만들게 되었다.

지금은 오션뷰라는 이름으로 운영되고 있지만, 초기 서비스명은 해대인이었다. 해양대학교를 다니는 학생이 타겟이므로 해대인이라는 이름이었던것 같다.(나의 추측)

앱이 만들어지기 이전에 [해대인]이라는 이름으로 카카오챗봇 서비스가 운영되고 있었는데 챗봇 개발자님의 졸업 + 카카오 챗봇 유료화가 되면서 앱으로 전환하게 되었다.

챗봇의 기능은 크게 3가지였다.

  1. 버스 시간 제공: 학교 홈페이지에 제공되는 버스 시간을 크롤링해서 현재 시간과 비교해 남은 시간을 알려준다.
  2. 학식 정보 제공: 학교 홈페이지에 제공되는 학식 정보를 크롤링해서 보여준다.
  3. 편의시설 정보 제공: 학교 홈페이지에 제공되는 편의시설 정보를 크롤링해서 보여준다.

나도 유용하게 잘 쓰고 있었던 서비스이고, 꽤 많은 학생들이 사용했기 때문에 프로젝트에 합류했을때 너무너무 기뻤고 한편으로는 모르는게 너무 많아서 걱정도 많이 했다.

기술 결정하기(=돈을 아끼기 위한 고민)

앱? 서버?

앱에서도 http 통신으로 학교 홈페이지에서 버스 시간을 불러와 계산하고, 학식 정보를 불러와 계산할 수 있다. 그렇지만 앱에서 바로 크롤링하는 경우, 홈페이지에 있는 시간표 table의 양식이 조금이라도 바뀌면 앱 업데이트를 해야했고, 하위 버전을 사용하고 있는 유저는 이상한 데이터를 제공받을 수 있다. 자체 DB를 사용하지 않고, 학교 홈페이지를 크롤링하는 서비스이기 때문에 앱에서 바로 학교 홈페이지를 크롤링하기에는 리스크가 너무 크다고 판단해, 크롤링하는 서버를 따로 두기로 하였다.

서버

서버는 내가 담당하기로 하였는데 그 당시 내가 할 수 있었던건 node로 웹서버 띄우기 뿐이라서, node로 서버를 만들기로 하였다.

내가 할 줄 아는건 로컬에서 서버 띄우기 뿐인데…🤔

내가 할 줄 아는건 로컬에서 서버 띄우기 뿐이고, 실제로 서버를 배포해본 적이 없었다. 주위에 개발자 친구도 없어서 막막한 상태에서 서버 배포하는 법을 열심히 구글링 하였다. 그 때는 뭐든것이 처음이라 키워드도 몰라서 구글링도 제대로 못했던것 같다.

열심히 구글링해서 겨우 몇가지 찾았는데 조건은 무료 + 배포하기 쉬워야함 였다.

  1. firebase cloud function: 호출 횟수로 요금을 책정한다. 일정 사용량 이하로는 무료인듯하다.
  2. aws: ec2를 프리티어로 사용할 수 있다. 사용시간만큼 요금을 책정한다. 스팟 인스턴스라는 서비스를 사용하면 요금을 획기적으로 줄일 수 있다는데…

해대인은 사용자가 한정되어있고(해양대 학생, 직원 대상) 밤, 새벽시간에는 사용자가 거의 없을 것이므로 사용시간으로 요금을 내기에는 손해라고 생각되어, 호출 수대로 요금을 책정하는 firebase cloud function을 선택하게 되었다.(사실 그 당시에는 ec2 배포하는게 너무 어려워 보여서 상대적으로 배포하기 쉬운 firebase cloud function이 최선의 선택이었다..)

Firebase Cloud Function 적용하기

Cloud Function에 대한 자료가 별로 없어서 공식문서를 열심히 보면서 작업했었던것 같다. 부끄러워서 코드는 비공개로 돌렸지만, 나름 express로 앱을 만들어서 route로 분리도 하고, 에러 처리도 하였다. 지금 생각해보면 고칠것이 너무 많은 코드이다…🤭 (cloud function에서 express사용하기 참고)

router.get('/', (req, res, next) => {
    try {
        const nowTime = moment(new Date()).add(9,'hours').toISOString();
        const days = ["","","","","","",""];
        let busData = {
            status: null,
            cur: `${moment(nowTime).format("YYYY.MM.DD")} ${days[moment(nowTime).format("e")]} ${moment(nowTime).format("HH:mm")}`
        };
        busData["result"] = {};
        busData["result"]["bus190"] = get190TimeTable(new Date(nowTime), busSchedule190);
        busData["result"]["shuttle"] = getTimeTable(new Date(nowTime), busSchedule);
        busData.status = "success";
        res.status(200).json(busData);
    } catch (err) {
        console.error(err);
        next(err);
    }
});
router.get('/', async (req, res, next) => {
    try {
        res.status(200).json({status:"success",result: calendar});
    } catch (err) {
        console.log(err);
        res.status(200).json({status:"error",result: null});
    }
});

lint도 하나도 적용안하고, HTTP statusCode도 무지성 200으로 넣고 정말 엉망으로 넣었던것 같다…

최적화를 위한 고민

구현해야하는 기능이 크게 아래의 3가지였는데

  1. 버스 정보
  2. 학식 정보
  3. 학사일정 정보

사실 학식 정보 제외하고는 매일 바뀌는 정보도 아니고, 학식 정보도 하루에 한 번 갱신되므로 요청이 올때마다 매번 학교 홈페이지를 크롤링하는건 부담이 많이 크다. 여기서 최적화를 위해 내가 고민한 방법은 이거였다.

  1. 버스 정보, 학사 일정 정보는 서버에 하드 코딩으로 저장해놓는다.
    1. DB에 저장하는것이 베스트지만, 결국 비용이 발생하기도 하고, 저장해야할 정보량이 작기때문에 서버 내부에 json파일로 저장하는걸 선택했다. 버스 정보가 변경되면 json파일을 수정해서 재배포하면 된다. 버스 정보가 변경되는 일은 1년에 1~2번 있을까 말까이다.
  2. 학식 정보는 캐시를 사용한다.
    1. 학식 정보는 하루에 한 번 갱신되는 값이므로 매일 서버 재배포를 할 수는 없으니 캐시를 도입하였다. 캐시도 파일을 사용하는 방식으로 하였다.(그 때는 cloud function 같이 호출때마다 인스턴스가 생성되는 경우 캐시가 소용없다는걸 몰랐다..) NodeCache라는 패키지를 사용하였다.

드디어 오픈🎉

배포하기

사실 서버는 앱 배포와 상관없이 미리 배포해놓으면 되므로 아래 명령어로 배포하였다.

firebase deploy --only functions

배포하면 해당 프로젝트 콘솔에서 http 요청할 수 있는 url을 확인할 수 있고(트리거를 확인하면 된다.), 클라이언트에서 이 url로 http 요청을 할 수 있다.

1년이 넘도록 거의 무료로 사용할 수 있었고, 현재는 cloud function이 아니라 다른 서비스를 사용하고 있기 때문에 호출수가 적다.(아직 구버전을 사용하고 있나보다..)

3년이 지난 지금 생각해보면 Cloud Function같이 호출 당 가격을 책정하는 FaaS를 사용하는 경우, 캐싱을 도입해도 인스턴스가 사라지면 소용없으므로, 최대한 호출 수를 줄이는 방법으로 최적화를 했을 것 같다. 이러저러 아쉬운 점이 많지만 이걸로 학교 홍보 동아리에서 인터뷰도 하고, 처음으로 서버 배포도 해보고 운영도 해보는 값진 경험이었다.