Nginx 캐싱을 사용해서 크롤링 속도 개선하기

JSM·2023년 11월 23일
0

프로젝트

목록 보기
3/10
post-thumbnail

Puppetear / Nginx 캐싱을 선택한 이유

  • 크롤링을 해와야 할때 어떤 라이브러리를 골라야할지 고민했다.
  • Puppetear의 큰 장점으로는 CSS와 JS 파일도 불러올 수 있다는 것이다.
  • 하지만 큰 장점을 가지는 만큼 엄청난 단점이 하나 존재하는데 바로 크롤링 시간이다.
  • 실제 크롤링해오려는 페이지를 보니 3초에서 6초까지도 시간이 걸린다.
  • 이런 불편함을 줄여보고자 캐싱을 선택했고 로컬 캐싱과 Redis 캐싱을 고민했다.
  • 프로젝트에서 멀티 클러스터링 환경을 고민한 만큼, 멀티 클러스터 서버가 공유할 수 있는 캐싱 구조가 필요했기에 Redis 캐싱을 선택했다.
  • Redis 캐싱을 선택하고 보니 Nginx에서 Redis에 접근할 수 있다는 것을 알게 되었고 서버의 부하를 줄여보기로 했다.

Cheerio vs Puppetear

  • JS 크롤링 라이브러리에는 크게 Cheerio와 Puppetear가 있다.
  • Puppeteer는 크롬 브라우저를 실제로 실행하는데 즉, 브라우저의 모든 기능을 이용할 수 있음을 의미한다.
  • 따라서 JS, AJAX 호출, CSS 등이 포함된 동적 웹 페이지를 완전히 렌더링하고 크롤링할 수 있다는 것이다.
  • Cheerio는 서버 측에서 HTML을 파싱하고 조작하기 위한 경량 라이브러리다.
  • 즉, 원본 HTML 파일에 이미 포함된 데이터를 크롤링할 수 있지만, 동적으로 생성되거나 변경되는 데이터는 처리할 수 없다.

Robot.txt가 무엇일까?

  • 법적인 강제력은 없지만 기본적으로 robot.txt 파일을 준수해야한다.
  • 어떤 부분을 크롤링할 수 있고 없는지에 대한 정보를 가진다.

캐싱이 뭘까?

  • 캐싱은 말 그대로 데이터를 저장해두는 것이다.
  • 한번 캐싱을 해둔 후, 나중에 해당 데이터를 조회할때 캐싱된 데이터를 반환하는 것이다.
  • 캐싱은 다양한 곳에서 사용되는데 JIT 컴파일러 / 도커 이미지 등 다양한 분야에 사용되는 개념이다.
  • 캐싱은 크게 2가지 장점을 가진다.
  • 먼저 시간적인 장점을 가진다. 내 크롤링은 한 요청당 3~5초의 시간이 걸린다. 이는 클라이언트 입장에서 긴 시간처럼 느껴질 수 있으므로 캐싱한 데이터를 통해 빠르게 조회를 가능하게 한다.
  • 다음으로 지나친 요청을 방지할 수 있다. 캐싱을 하면서 해당 데이터를 가지고오는 요청 과정을 생략할 수 있으므로 큰 장점을 가진다.

지나친 캐싱은 어떤 문제가 있을까?

  • 데이터 동기화 문제이다. 최신 데이터가 필요한데 만약 캐싱이 되어있다고 해서 기존 데이터를 가지고 오면 정합성 문제가 발생할 수 있다.
  • 서버 리소스 낭비 문제가 있을 수 있고 너무 많은 캐싱은 관리의 복잡성을 키운다.

Nginx가 무엇일까요?

  • Nginx는 고성능 웹서버로 WAS 서버 앞단에서 트래픽을 관리해주는 역할을 합니다.
  • 리버스 프록시를 통해 특정 경로로 온 요청을 적절한 서버로 라우팅 해줍니다. 이는 nginx.conf 파일의 location 정보를 통해 라우팅합니다.
  • 자주 요청되는 이미지 CSS JS 파일을 캐싱하여 응답 속도를 높입니다.
  • SSL/TLS 통신을 가능하게 해주며 클라이언트에서 HTTPS로 요청이 오면 이 요청을 HTTP로 변환하여 서버에 전달합니다.

Redis를 통한 Nginx 캐싱 구현하기

크롤링 시 3.22S에서 캐싱을 적용하면 43MS로 캐싱 전후에 너무 큰 차이가 발생한다.

1. Nginx 말고 openresty를 사용해야 한다.

  • openresty는 Nginx의 확장판이라고 보면된다.
  • openresty는 lua 스크립트를 제공한다. 즉 동적 프로그래밍을 지원해주는데 이를 통해 캐싱을 활용할 수 있다.
  • Nginx도 lua 스크립트를 적용할 수 있지만 ngx_http_lua_module을 별도로 추가해야 한다.
  • 또한 openresty는 LuaJIT를 통해 스크립트의 실행 속도를 높인다.
  • 다만 lua 스크립트를 통해 캐싱된 데이터로 응답을 생성하면 추가 CORS 설정이 필요하다.

Openresty 도커 파일 작성

  • nginx.conf 파일과 디렉토리에 대한 설정을 추가
FROM openresty/openresty

RUN mkdir -p /var/log/nginx
COPY ./nginx/conf.d/ /usr/local/openresty/nginx/conf/

Lua 스크립트 작성

  • Redis 연결 및 keep Alive를 통해 연결 유지
  • 응답에 대한 Cors 설정
  • 키에 대한 정보가 없을시 리다이렉트 기능
  • 키에 대한 정보가 조회될 시 즉시 응답
local redis = require "resty.redis"
local red = redis:new()

red:set_timeout(1000)
ngx.header["Access-Control-Allow-Origin"] = "*"

-- Redis 서버에 연결
local ok, err = red:connect("IP주소", 포트번호)
if not ok then
    ngx.say("레디스 연결에 실패하였습니다: ", err)
    return
end

-- Redis 인증
local ok, err = red:auth("인증 패스워드")
if not ok then
    ngx.say("인증에 실패하였습니다: ", err)
    return
end

-- 요청 쿼리 파라미터에서 URI 정보를 가지고 옴
local args = ngx.req.get_uri_args()
local key = args.url

-- Redis에서 값 조회 후 연결 여부 결정
local res, err = red:get(key)
if res == ngx.null or res == "" then
    local server_response = ngx.location.capture("/crawler?url=" .. ngx.escape_uri(args.url))
    if server_response.status == ngx.HTTP_OK then
        red:setex(key, 6000, server_response.body)
        ngx.say(server_response.body)
    else
        ngx.say("서버 에러가 발생했습니다: ", server_response.status)
        return ngx.exit(server_response.status)
    end
else
    ngx.say(res)
    ngx.exit(200)
end

red:set_keepalive(60000, 100)

2. Nginx 리버스 프록시 설정

  • cache라는 경로로 요청을 보냈을때 Nginx에서 lua 스크립트를 실행한다.
  • 이때 lua 스크립트를 통해 캐싱 값을 조회하고 만약 값이 없다면 크롤링 경로로 리다이렉트 한다.
events {
    worker_connections 1024;
}

http {

    upstream blue_backend {
        server blue:4000;
    }

    upstream green_backend {
        server green:4000;
    }

    server {
        listen 80;
        server_name IP주소;

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log info;

        location / {
            proxy_pass http://blue_backend;
            proxy_set_header Host $host:$server_port;
            proxy_set_header X-Forwarded-Host $server_name;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        location /cache {
            proxy_pass http://blue_backend;
            proxy_set_header Host $host:$server_port;
            proxy_set_header X-Forwarded-Host $server_name;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            content_by_lua_file /usr/local/openresty/nginx/conf/cache.lua;
        }


        location /crawler {
            proxy_pass http://blue_backend;
            proxy_set_header Host $host:$server_port;
            proxy_set_header X-Forwarded-Host $server_name;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}

3. 컴포즈 파일 수정

  • openresty 설정에 맞게 컴포즈 파일을 수정한다.
version: '3.8'

services:
  nginx:
    container_name: nginx
    image: image 이름
    ports:
      - 80:80
    depends_on:
      - blue
      - green
    volumes:
      - ./nginx/conf.d:/usr/local/openresty/nginx/conf/

Nginx 캐싱 구현하기

  • 사실 기본 HTML CSS JS 파일이므로 기본 Nginx 캐싱으로도 충분합니다.
  • 또한 메모리 관점에서 redis를 사용하는 것보다는 디스크에 데이터를 저장하는 Nginx 캐싱이 더 적합할 수 있습니다.
proxy_cache_path /usr/local/openresty/nginx/cache levels=1:2 keys_zone=my_cache:10m max_size=4g inactive=60m use_temp_path=off;

location /nginx {
  proxy_cache my_cache;
  proxy_cache_key "$request_uri";
  proxy_cache_valid 200 302 60m;
  proxy_cache_valid 404 1m;
  proxy_cache_methods GET HEAD;
  proxy_cache_lock on;

  proxy_pass http://blue_backend;
  proxy_set_header Host $host:$server_port;
  proxy_set_header X-Forwarded-Host $server_name;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

Nginx로 Docker 환경에서 권한 관련해서 주의할점

sudo mkdir -p /var/cache/nginx
sudo chown -R www-data:www-data /var/cache/nginx
sudo chmod -R 755 /var/cache/nginx
  • nginx는 nginx 또는 www-data 사용자 계정을 실행되므로 이 사용자가 파일과 디렉토리 권한에 접근할 수 있어야 합니다.
  • 즉 nginx가 루트 디렉토리에 쓰기 권한을 가져야 하므로 접근 권한을 부여합니다.
profile
내 기술적 고민들을 모은 곳...

0개의 댓글