자바 스크립트에서 함수는 객체이기 때문에 프로퍼티를 가질 수 있습니다. 그리고 언제든지 함수에 사용자 정의 프로퍼티를 추가할 수도 있습니다. 함수에 프로퍼티를 추가하여 결과(반환 값)을 캐시하면 다음 호출 시점에 복잡한 연산을 반복하지 않을 수 있습니다. 이런 활용 방법을 **메모이제이션 패턴**이라고 합니다.
다음 코드에서는 myFunc 함수에 cache 프로퍼티를 생성합니다. 이 프로퍼티는 일반적인 프로퍼티처럼 myFunc.cache와 같은 형태로 접근할 수 있습니다. cache 프로퍼티는 함수로 전달된 param 매개변수를 키로 사용해서 계산의 결과를 값으로 가지는 객체(해시)입니다. 결과 값은 필요에 따라 복잡한 데이터 구조로 저장할 수도 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13
var myFunc = function (param) { if (!myFunc.cache[param]) { var result = {}; // ... // 비용이 많이 드는 수행 후 result에 결과 저장 // ... myFunc.cache[param] = result; } return myFunc.cache[param]; };
// 캐시 저장공간 myFunc.cache = {};
메모이제이션 적용하기
메모이제이션을 공부한 후 현재 진행중인 에밀리(개인 프로젝트)에 적용해 보았습니다. 어느 부분에 적용했는지 적용 전과 적용 후 얼마나 효율이 올라갔는지를 알아보겠습니다.
수정할 부분
메모이제이션 패턴을 적용할 코드가 현재 하고 있는 기능은 다음과 같습니다. 사용자의 요청(학생식당, 카페테리아, 사범대식당, 기숙사식당, 교직원식당)에 따라 미리 크롤링 후 DB에 저장되어 있는 데이터(메뉴)를 가져와 응답합니다.
메모이제이션 패턴을 사용함으로써 얻을 수 있는 이점은 비용이 많이 드는 결과를 캐싱하고 그 이후에 재사용함으로써 비용을 줄일 수 있다는 것입니다. 현재 코드에서 비용이 많이 드는 작업은 DB를 조회하는 부분입니다. DB에는 다음과 같이 미리 크롤링한 데이터가 날짜별로 저장되어 있습니다.
사용자의 요청에 따라 해당 날짜의 데이터를 조회한 후 사용자가 요청한 식당에 맞는 데이터를 결과로 반환합니다. 그렇기 때문에 해당 날짜의 최초 요청이 이루어진 후 그 하루 동안에는 계속해서 DB에 같은 쿼리를 통해 같은 결과를 얻게 됩니다. 이 부분이 비용이 많이 드는 작업이기 때문에 메모이제이션 패턴을 통해 개선해보았습니다.
메모이제이션 적용 전
먼저 메모이제이션 패턴을 적용하기 전 코드입니다. menuHandler 클래스의 getMenu 함수는 menuService를 통해 DB에서 해당 날짜의 식당 메뉴를 가져와 매개변수로 전달받은 식당의 이름을 사용하여 결과로 반환하는 역할을 합니다.
classmenuHandler{ staticgetMenu(place) { returnnewPromise((_s, _f) => { const today = newDate().yyyymmdd(); let menu = '';
menuService.show(today) .then(menuList => { if (!menuList) { menu = '데이터가 없습니다. 관리자에게 문의해주세요'; }
switch (place) { case'학생식당': menu = menuList.student.join('\n\n'); break; case'카페테리아': menu = menuList.cafeteria.join('\n\n'); break; case'사범대식당': menu = menuList.education.join('\n\n'); break; case'기숙사식당': menu = menuList.dormitory.join('\n\n'); break; case'교직원식당': menu = menuList.staff.join('\n\n'); break; }
_s(menu); }) .catch(err => { _f(err); }); }); } }
module.exports = menuHandler;
3. 메모이제이션 적용 후
메모이제이션 패턴을 적용 한 후 코드입니다. setCache, getCache, pickMenu 함수가 추가되었고, getMenu 함수의 코드도 조금 변경되었습니다. 기존 코드의 getMenu 함수에서는 바로 DB를 조회하여 결과를 반환하였지만, 변경된 코드에서는 getCache 함수를 통해 캐시에 저장된 데이터가 있는지 확인 후 분기하여 처리합니다.
메모이제이션 패턴을 적용하기 전과 적용한 후의 성능 차이는 아래 보이는것처럼 눈에 띄게 차이가 납니다. 메모이제이션 패턴을 적용한 코드는 첫번째 요청(캐싱 하기 전)때는 메모이제이션 패턴 적용 전과 응답속도가 비슷하지만 그 이후의 응답은 캐싱된 데이터를 이용하기 때문에 비교될 정도로 빨라졌습니다.
마치며
메모이제이션을 적용하기 전과 후 모두 같은 기능을 결과를 만들어내는 코드이지만 코드를 작성하는 방법에 따라 더욱 더 빠른 효율적인 서비스를 만들 수 있다는 것을 느끼게 되었습니다. 현재 제 상황에서는 캐싱된 데이터도 하루가 지나게되면 쓰이지 않고 계속 메모리에 남아있게 되는데 그 부분에 대한 처리를 추가해야할것 같습니다. 이번에 적용한 코드 뿐만 아니라 아직 프로잭트 내에 메모이제이션 패턴을 적용할 수 있는 부분이 더 있습니다. 앞으로 디자인패턴 공부를 계속하여 새로운 패턴들을 적용시키며 리팩토링을 해야겠습니다.