[TIL] 캐시와 크롤링

Narastro·2021년 7월 21일
0

TIL

목록 보기
3/16
post-thumbnail
post-custom-banner

🥇 캐시

일반적인 캐시 동작방식

CPU가 데이터 인출을 위해 Cache 기억 장치에 먼저 접근을 하고 Cache 메모리에서 데이터를 찾으면 적중(hit), 찾지 못하면 실패(miss)라고 한다. 적중하게 되면, 주기억장치에 방문하지 않고 캐시기억장치에서 빠르게 데이터를 읽어오고, 적중하지 못하면 주기억장치를 방문해서 데이터를 찾게된다. 이렇듯 캐시는 빠른 입출력 등 성능 개선에 큰 도움을 준다.

캐시 교체 정책

  • LRU (Least Recently Used) : 캐시 내에서 가장 오랫동안 참조되지 않은 블록을 교체하는 방식
  • FIFO (First In First Out) : 캐시 내에서 가장 오랫동안 있던 블록을 먼저 교체하는 방식
  • LFU (Least Frequency Used) : 가장 적게 참조되었던 블록을 교체하는 방식

내가 설계한 LRU방식

1. 캐시에 데이터를 저장할 때 객체 형식으로 data, hitCount, order를 부여한다.

ex ) cache = { 'apple' : {'data':..., 'hitCount':1, order:1}, 'google':{data:..., 'hitCount:1, order:2}, ... }

여기서 order변수는 캐시에 접근할 때마다 1씩 증가하고, 캐시가 적중(hit)한 경우에는 해당 키워드에 order를 변경해주고 캐시가 실패(miss)한 경우에는 해당 order를 갖는 객체를 추가해준다.

ex) 캐시 3번 접근시 order=3, 적중시 해당 키워드의 order 갱신, 실패시 해당 order를 갖는 객체 생성

2. 캐시 메모리 용량이 초과된 경우

캐시 메모리 용량을 5라고 했을 때, 6번째 캐시값이 들어오기 전에 현재 order=6값에서 5만큼 뺀 order = 1인 캐시값을 삭제해준다. 캐시가 적중된 경우 order값이 갱신되기 때문에 가장 오래된 값이 삭제된다.

3. 캐시 데이터 입력이 초과된 경우

캐시에 저장된 객체의 데이터 입력 용량을 10이라고 했을 때, 데이터가 11개가 들어온 경우 데이터의 길이를 측정해서 데이터.splice(10)해서 잘라서 저장해 준다.

Javascript 에서 Object를 해시맵처럼 사용하지 마라!

흔히들 Javascript에서 해시맵을 사용할 때 key-value 형태를 가진 Object를 그대로 사용한다. 하지만 자바스크립트는 Map이라는 해시맵을 데이터 구조를 지원한다. 둘의 차이는 무엇일까? 그리고 Map을 사용해야 하는 이유는 무엇인가?

<Object형태>
const map = {};

// 키-값 넣기
map['key1'] = 'value1';
map['key2'] = 'value2';
map['key3'] = 'value3';

// 키를 갖는지 확인
if (map['key1']) {
  console.log('Map contains key1');
}

// 값 출력
console.log(map['key1']); // value1
<Map 형태>
const map = new Map();
// 키-값 넣기
map.set('key1') = 'value1';
map.set('key2') = 'value2';
map.set('key3') = 'value3';

// 키를 갖는지 확인
if (map.get('key1')) {
  console.log('Map contains key1');
}

// 값 출력
console.log(map.get('key1');

Map 장점

  • 1. 더 많은 Key 유형 (Object는 기호나 문자만 가능, Map은 제한 없음)
  • 2. .size 사용 가능 (객체는 일반적인 방법으로 크기를 확인할 수 없음)
    "객체 길이 확인 : Object.keys(obj).length"
  • 3. 더 나은 성능 (데이터의 추가와 제거에 최적화 되어 있기 때문에 성능에 있어서 매우 유리)
    "맥북프로에서 천만개의 데이터 셋을 가지고 테스트 했을 때 Object는 1.6초의 처리시간이 필요했고 Map은 1ms 이하의 처리시간을 보였다."
  • 4. 반복문 사용
    "Object를 사용하여 반복문을 실행시키려면 불편합니다. 하지만 Map은 반복문을 아주 편리하게 사용 할 수 있다. : for (let [key,value] of map){ ..."
  • 5. 순서의 보장 (ECMAScript 2015 이전 버전까지는 Object에서 키의 순서를 보장하지 않았으나 현재는 보장함)
  • 6. 덮어쓰기 금지
    "Object에는 미리 정의된 prototype이 있기 때문에 key가 충돌할 위험이 있습니다. 하지만 Map을 사용할 때는 그런 걱정을 할 필요가 없다."

출처 : https://erim1005.tistory.com/entry/Javascript-%EC%97%90%EC%84%9C-Object%EB%A5%BC-%ED%95%B4%EC%8B%9C%EB%A7%B5%EC%B2%98%EB%9F%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%A7%80-%EB%A7%88%EC%84%B8%EC%9A%94

LRU 캐시 구현시 Object? Map?

Map은 확실히 객체로 구현했을 때보다 더 나은 성능을 보이는 것은 사실이다. 특히나 캐시를 구현할 때는 Map으로 빠르게 조회하고 삭제하는 것이 더더욱 그 의미에 맞다. 그렇기에 훨씬 빠른 Map을 적극 이용하자!

콘솔 입출력시 사용한 라이브러리

npm i prompt-sync : 자바스크립트에 내장된 라이브러리에 readline 함수가 있지만 사용하기 불편하여 입력값을 반환하는 간단한 동기 입력방식인 prompt-sync를 설치 후 사용하였다.

import prompt from "prompt-sync";

const keyword = prompt()("키워드를 입력하세요 (종료하려면 exit입력)> ");

출처 : https://www.codecademy.com/articles/getting-user-input-in-node-js

🎇 크롤링 코드 구조

모듈

  • index.js : 메인 모듈
  • crawl.js : 크롤링 모듈, axios로 html을 GET하고 cheerio로 크롤링 후 결과 반환
  • inOut.js : 입출력 모듈, 사용자 콘솔 입력과 결과값 출력을 담당
  • cache.js : 캐시 관련 모듈, cache를 get, set하거나 정보를 반환하는 모듈

작동 방식

1. 콘솔 입력을 통해 keyword 받기 (inOut.js)

2. 캐시 체크 (cache.js)

캐시 적중시, 캐시에서 바로 데이터를 가져옴.
실패시, 크롤링을 진행함.

3. 캐시 실패시 크롤링 및 가공 (crawl.js)

4. 크롤링 후 캐시 추가 (setCache())

5. 크롤링된 데이터를 출력 (inOut.js)

6. init() 호출을 통해 함수 반복

exit 입력시 process.exit()을 호출하여 함수 종료시킴.

😄 느낀점

무작정 코드를 짜기보단 구조를 먼저 생각하고 어떤 기능과 역할로 구조를 구성하는 설계 과정의 중요성을 느꼈다. 그리고 크롤링할 때 google은 아예 작정하고 못하게 해놨다는 걸 보고, 네이버한테 고마웠다... 네이버 짱!

profile
Earn this, Earn it.
post-custom-banner

0개의 댓글