기존 모듈의 문제점
속도가 느리다
데이터 수집 속도의 중요성
데이터의 갱신이 실제 서비스에 반영되는데 걸리는 시간은 서비스의 품질을 의미한다.
편의점 할인 행사 품목은 하루에 한번 자정에 변경된다. 따라서, 매일 자정에 해당 정보를 수집해야 우리 서비스가 제대로된 할인 정보를 수집할 수 있다. 데이터의 원천이 우리 서비스가 아니기 때문에, 이를 수집하는데 시간 차이가 발생하게 된다. 서비스의 품질을 올리기 위해서는 이 시간 차이를 최대한 작게 하는 것이 중요하다.
기존 모듈의 동작 속도
개선 전 모듈의 동작 속도 : 총 30분이 걸린다.
기존 모듈이 동작을 완료할 때 까지 30분이 걸렸다.
서비스가 제대로 동작하지 않는 상황이 30분이상 지속될 가능성을 의미한다.
모듈의 동작 환경에 필요한 것이 많다
아래 코드는 기존 모듈의 python 코드의 일부분이다.
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
Python
복사
기존 모듈이 크롤링하는 웹 페이지들은 모두 동적생성된 웹 페이지다. 즉, 뼈대가 되는 HTML, CSS, JS 파일을 서버에서 불러온 뒤, 실제 데이터는 별도의 HTTP 통신을 통해 가져와 DOM을 수정하는 형태로 제공되는 웹 페이지다. 따라서, 단순한 정적 크롤링으로 데이터를 수집할 수 없다.
보통 이런 웹사이트를 크롤링할 때는 브라우저의 드라이버를 사용해 사람이 브라우저를 사용하는 행동을 모방하여 데이터를 수집한다. 쉽게 말해, 클릭 매크로의 기능이 정적 크롤링에 추가된 방식이다.
사실상 GUI 환경에서만 동작할 수 있다.
cli 기반의 웹 브라우저와, 드라이버가 있다면 가능하지만, 찾아보기 힘들다. 대부분의 동적 크롤링의 경우 구글의 크롬 브라우저, Mac 의 Safari, Firefox 등의 브라우저를 사용한다.
동적 크롤링은, 코드를 통해 브라우저를 제어해, 특정 위치를 클릭하고 데이터를 수집하는 방식이기 때문에, 코드가 실행되면 브라우저가 실행된다. 따라서, 모니터에 브라우저가 띄워진 상태로 동작하게 된다.
성능 개선하기
크롤링 대상과 방법에 따른 속도 차이
앞선 챕터에서 설명한 대로 기존 모듈은 편의점 공식 홈페이지를 동적 크롤링을 하여 데이터를 수집하고 있다. 이로인한 성능 저하를 피하기 위한 방법에는 크롤링 대상 웹페이지를 변경하는 방법과 크롤링 방식을 변경하는 방법이 있다.
크롤링 대상 웹페이지 변경 vs 크롤링 방식 변경
크롤링 대상 웹페이지를 변경 은 말 그대로 공식 홈페이지가 아닌 다른 페이지를 크롤링해 데이터를 수집하는 방법을 말한다. 즉, 정적 크롤링이 가능한 웹사이트를 찾아 크롤링 하는 방식이다.
크롤링 방식을 변경하는 방법 은 웹 페이지를 그대로 크롤링 하는 것이 아니라, 그 웹페이지에서 보내는 데이터 요청을 크롤링 코드에서 직접 하는 방식이다.
gs25 공식 홈페이지 개발자도구를 이용한 네트워크 분석
브라우저의 개발자도구를 이용하면, 웹페이지에서 수행되는 네트워크 통신을 확인할 수 있다. 이를 분석해 모방하면 웹페이지에 접속하지 않아도 데이터를 수집할 수 있다. 하지만, 통신이 암호화되어있거나, 기타 보안 처리가 되어있으면 이를 활용하기 어려울 수 있다. 와이어 샤크 등의 패킷 캡처 프로그램을 활용할 수도 있다.
멀티 쓰레드 활용
멀티 쓰레드를 활용해 동시에 여러 HTTP통신을 시도해 성능 향상을 가져올 수 있다. 단, CPU의 사양에 따라 적절한 쓰레드를 사용해야 성능 향상을 노릴 수 있다.
데이터 저장 방법에 따른 속도 차이
데이터 수집 모듈은 웹페이지에서 데이터를 수집하는 것 뿐 아니라, 수집한 데이터를 적절히 저장하는 것 까지 포함한다. 따라서, 데이터를 저장하는 과정을 빠르게 할 수 있다면 모듈의 성능이 향상된다.
REST API vs 데이터베이스와 직접 연결
REST API를 통하지 않으면 더 빠르게 데이터베이스와 통신이 가능하다. 대신 변경에 취약해질 수 있다.
데이터베이스의 스키마가 변경되거나, 새로운 테이블이 추가되면, 이를 사용하는 모듈에 변경이 발생한다. 따라서, 하나의 데이터베이스를 많은 모듈이 직접 공유한다면 데이터베이스의 변경이 발생할 경우 모든 모듈의 테스트를 수행해야 한다. 당연히, 이는 단순 테스트에서 그치지 않고 모듈의 변경으로 이어질 수 있다. 게다가, 모듈이 변경되면 그 모듈에 대한 테스트를 다시 해야한다.
반면, REST API 등의 방법으로 모듈간 간접적으로 연결한다면, 그 간접적인 연결 방식의 변경이 발생하지 않았다면 굳이 다른 모듈의 테스트를 할 필요가 없다. 데이터베이스와 직접 연결하는 모듈만 테스트를 하면 된다.
데이터베이스와 직접 연동을 하게 되면, REST API등의 방식으로 모듈간 연결을 하지 않아도 되므로 이 연결 과정에서 소비되는 시간을 아낄 수 있다. 따라서, 데이터베이스 자체가 감당할 수 있는 범위 내라면, 모듈이 직접 데이터베이스와 연결하는 것이 더 적은 시간을 사용한다.
실제 반영한 변경점 및 결과
앞서 서술한 사항들을 적절히 고려해 다음과 같이 모듈을 변경했다.
대상 웹페이지 변경
각 편의점 브랜드의 공식 웹사이트가 아닌, 다른 사이트로 크롤링 대상을 변경했다. 이는 단순히 정적 크롤링이 가능했기 때문만이 아니라, 여러 웹사이트를 크롤링 한다면, 크롤링 코드의 변경이 발생할 가능성이 그만큼 높아지기 때문도 있다.
멀티 쓰레드 활용
웹사이트에 접근하는 코드를 멀티쓰레드를 활용해 병렬처리를 수행했다.
데이터베이스 직접 연결
비즈니스 로직 서버에서 제공하던 REST API를 삭제하고, 데이터 수집 모듈에서 직접 데이터베이스와 연결하도록 변경했다.
언어 및 프레임워크 변경
개발의 편의성을 위해 기존 Python 3에서 Java 11 + Spring Boot 2.7 로 개발 언어및 프레임워크를 변경했다.
성능
기존 30분에서 약 2분으로 실행 시간이 감소되었다!
개선된 모듈 실행 결과 : 약 2분으로 실행 시간이 줄었다!
앞으로 해야 할 작업
[프로젝트]편킹 추천 알고리즘 설계 과정에서 다룬 것 처럼, 데이터 수집 모듈의 동작 과정 중, 수집한 데이터를 정규식을 통해 분석하는 과정이 있다. Java에서 정규식을 다루는 방법이 여러가지 방법이 있는데, 이중 이 모듈에서 사용하고 있는 코드는 아래와 같다.
private boolean calcByRegex(String regex, String keyword, Promotion promotion){
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(promotion.getName());
if(matcher.find()){
promotion.addSubCategory(keyword, 1);
return true;
}
return false;
}
Java
복사
위 메서드는 모듈이 한번 실행될 때 약 5만번정도 반복해서 호출된다. 즉, Pattern 의 인스턴스가 약 5만번 생성되는 것이다. Pattern 의 인스턴스를 생성하는 비용은 일반적인 객체 생성보다 크다. 또한, 5만번의 호출에서 사용되는 정규식은 수십개로 정해져있다. 따라서, Pattern 의 인스턴스를 미리 만들어두는 방식을 채택하면 더욱 빨리 처리될 수 있을 것이다.