MONITORING/Log Monitoring

로그 모니터링 개발 기록 01. 검색엔진의 저장 구조 이해하기

행운개발자 2024. 1. 22. 19:55
728x90

2021년 11월부터 현재(24년 1월)까지 약 2년동안 와탭 로그 모니터링을 개발하면서 배우고 느꼈던 것들을 정리하려고 합니다. 전체적인 흐름을 어떻게 구조를 잡을까 고민해보다가 Lucene In Action에서 Lucene에 대해서 설명하고 있는 구조를 그대로 차용하겠습니다.


와탭 로그 모니터링 훑어보기

먼저 와탭 로그 모니터링의 화면을 가볍게 훑어보면서 어떤 종류의 서비스인지 느낌을 알아보겠습니다. 아래의 사진은 ‘로그 트렌드’ 라는 페이지입니다. 왼쪽 사이드바에서 Key-Value 쌍을 선택하면 지정된 시간 범위에서 ‘1분당 수집된 로그 수’를 알 수 있고, 해당 1분 구간에 수집된 로그를 확인할 수 있습니다. 아래의 예시는 문제가 발생한 13:00 ~ 13:10 시간에 문제가 된 서버 이름(oname)으로 로그를 검색했을 때 조회된 로그입니다. 트러블 슈팅 과정에서 로그 모니터링을 사용하는 예시는 아래의 글에서 확인할 수 있습니다.

2024.01.20 - [트러블슈팅] - PessimisticLockException 원인 파악하고 해결하기

검색 엔진의 저장 구조

와탭 로그 모니터링은 ‘문자열로 문자열을 찾아내는 검색 엔진’의 역할을 수행합니다. 대표적인 검색 엔진인 Apache Lucene의 동작 과정을 이해하면 와탭 로그 모니터링을 포함한 대부분의 검색 엔진의 동작 과정을 유추할 수 있으므로 HTML 문서 하나를 Lucene에서 검색하는 과정에 대해서 간략하게 알아보겠습니다.

Lucene의 동작 방식

하나의 HTML 문서가 있습니다. 이 문서에는 HEAD 태그와 BODY 태그가 있습니다. 그리고 사용자는 임의의 문자열로 검색해서 수 많은 HTML 문서 중에서 가장 연관도가 높은 문서를 찾고 싶습니다. 이 과정은 어떻게 동작할까요?

원본 문서를 색인하고 검색하기 좋은 형태로 변환하면 됩니다. 원본 파일의 내용을 분석해서 검색의 대상이 되는 내용을 Field로 지정합니다. Document는 이러한 Field 들의 집합입니다. 하나의 HTML 문서에서 HEAD와 BODY 부분은 모두 검색의 대상이 될 수 있지만, 개념적으로 구분이 필요합니다. 이럴 때에는 서로 다른 Field로 지정되고 하나의 HTML 문서에서 추출된 것을 나타내기 위해서 하나의 Document 객체로 묶어집니다.

  • Document : 하나의 HTML 문서의 내용이 포함되는 객체
    • Field A: HEAD의 내용을 포함
    • Field B : BODY의 내용을 포함

다음 단계로는 텍스트 분석이 진행됩니다. HEAD와 BODY에 포함된 각각의 단어들을 분석해서 검색 대상이 되는 Token으로 가공합니다. 이 과정에서 a, an, the와 같이 분석에 무의미한 토큰은 사라지고 오자, 동의어, 유의어, 단수와 복수, 대소문자 등이 고려된 상태로 Token화 됩니다.

  • Document
    • Field A
      • List<Token> : HEAD의 내용을 분석한 결과
    • Field B
      • List<Token> : BODY의 내용을 분석한 결과

이제 색인에 문서를 추가할 차례입니다. 색인은 특정 단어로 직접 이동할 수 있는 자료구조입니다. 사용자가 검색어를 입력할 때마다 순차적으로 파일을 읽어가면서 확인하지 않도록, 검색어에 매칭되는 원본 파일로 바로 이동할 수 있는 역색인구조를 사용합니다. 역 색인 구조는 ByteByteGo에서 시각화한 좋은 자료가 있습니다.

위의 그림 중 Inverted Index 부분을 Document - Field - Token의 구조로 표현하면 다음과 같습니다.

  • Document
    • Field A : my name is Alex.
      • List<Token> : name, Alex
    • FIeld B : What day is today
      • List<Token> : day, today, “What day is today”
    • Field C : I bought a book today.
      • List<Token> : bought, book, today

Field B에는 분석된 결과 중에 “What day is today”라는 Field의 값이 그대로 포함되어 있습니다. 그리고 각각의 Field에 따라 검색 관련성이 적은 my, is ,a 와 같은 토큰들은 제거되었습니다. 색인이 저장되었으면 색인을 기반으로 조회를 할 수 있습니다. 색인을 조회할 때에는 여러 가지 방식이 있지만 SSTable 방식을 사용할 수 있습니다.

Index file의 Key가 이전 단계에서 분석된 Token으로 생각하면 됩니다. Token 객체에는 Field 객체에 대한 offset과 length 정보가 포함되어 있습니다. 이 정보를 사용해서 blob file의 데이터를 full scan하지 않고 임의의 위치(offset)으로 바로 이동한 뒤 length만큼 읽을 수 있습니다.

Whatap 로그 모니터링의 동작 방식

앞서 보았던 이 사진에서 어떻게 동작하는지를 Lucene의 동작방식과 비교해서 설명해보겠습니다. Lucene에서 Document - Field - Token의 저장 구조를 사용했다면, 와탭에서는 아래와 같은 구조를 사용합니다.

public class LogSinkPack {
	private long time; // 로그가 수집된 시간
	private int oid; // 로그를 수집한 에이전트의 식별 번호
	private String category; // 수집한 로그의 종류, 로그를 수집한 모니터링 대상의 종류, 수집한 로그 파일의 이름 등
	private MapValue tags = new MapValue();
	private MapValue fields = new MapValue();

	private long tagHash;
	private long line;
	private String content;

}

한 줄 한 줄의 로그를 LogSinkPack이라는 단위로 변환하고 원본 로그는 content 필드에 저장합니다. content 필드 안에서 검색 대상이 되는 값들을 추출해서 MapValue 자료 구조에 저장합니다. 이 때 추출된 데이터의 성격에 따라서 tags에 저장하기도 하고 fields에 저장하기도 합니다. (tags와 fields에 대한 차이점은 이번 글에서는 설명하지 않겠습니다.)

 

Lucene에서는 Field를 추출하고 Token으로 분석하는 과정을 Analyzer라는 이름으로 굉장히 세부적으로 커스터마이징할 수 있도록 제공하고 있습니다. 와탭에서는 현 시점 기준으로 범용성이 높은 두 가지 방법만 제공합니다. Grok Parser, Json Parser. 이 두 가지 방식 모두 원본 문자열에서 유의미한 Key-Value 쌍을 추출하기 위한 목적을 가집니다. Grok 파서는 정규 표현식을 사용하기 때문에 모든 문자열에 대해서 적용할 수 있는 장점이 있는 반면에 정규 표현식을 잘 알아야 사용할 수 있다는 단점이 있습니다. Json 파서는 로그의 전체 또는 일부가 Json 포멧일 때 해당 부분을 자동으로 Key - Value 쌍으로 추출해줍니다. 이렇게 추출된 Key-Value 쌍은 '로그 트렌드' 페이지에서 왼쪽에 검색할 수 있는 키워드로 표현됩니다.

 

파싱 단계 이후에 색인을 저장하고 조회하는 과정은 Lucene의 Inverted Index, SSTable의 방식과 동일한 개념을 사용합니다. Inverted Index의 구조를 사용해서 사용자가 입력한 키워드에서 원본 로그를 찾아갈 수 있도록 하고, SSTable의 저장 구조에서 offset, length 이외에 '분당 건수' 등의 정보를 추가적으로 저장해서 조회합니다.

여기에 더해 성능 관점에서 추가적으로 고려되어야할 부분이 산더미이지만, 여기까지만 이해하면 전체적인 기본적인 검색 엔진의 구조는 설명이 끝났습니다.

 

다음에는 '검색엔진의 조회 구조 이해하기'와 '검색엔진의 병목 지점과 최적화'의 주제로 글을 써보겠습니다.

 

728x90