동기 부여 서비스에 대한 높은 수준의 관찰 가능성을 확보하는 것보다 더 중요한 것은 없으며 애플리케이션 로그를 위한 안정적이고 빠른 저장소 없이는 불가능합니다. 요즘 가장 인기 있는 솔루션 중 하나는 ELK( - ogstash- )이지만 보기만큼 보편적이지는 않습니다. ElasticSearch L Kibana 문제 설명 ELK는 유연하고 편리하며 복잡한 로그 수집 및 분석 솔루션입니다. 확장 가능하고 강력하며 유연한 쿼리 필터링이며 보편적입니다. 그러나 ELK를 사용하면 다음과 같은 단점이 있습니다. 높은 메모리 및 CPU 리소스 소비 레코드 증가에 따른 색인 및 검색 속도 저하 전체 텍스트 검색 인덱스 오버헤드 복잡한 QueryDSL 이 모든 것이 나를 궁금하게 만듭니다. 로그에 관해 이야기할 때 ELK에 대한 대안이 있습니까? 로그 처리 솔루션에 대한 요구 사항 목록은 다음과 같습니다. 빠르고 유연한 집계 빠른 삽입, 빠른 선택 설치가 쉽고 리소스가 최소화됨 grafana 호환 솔루션 그러나 정기적인 분석과 임시 분석을 수행할 수 있는 유연하고 빠른 도구가 있다는 것은 좋은 일입니다. 제가 구현할 시스템에 대해 좀 더 구체적으로 설명하자면 다음과 같습니다. Nginx 액세스 로그인 2천만 개 이상의 로그 라인을 처리합니다. 잘못된 HTTP 응답 수 가져오기(4xx, 5xx) 봇 및 크롤러 카운터와 이들이 방문하는 URL을 확인하세요. 의심스러운 봇인 상위 5개 IP 주소를 확인하세요. ClickHouse는 무엇이고 왜 사용하기로 결정했나요? ClickHouse는 선언된 대로 온라인 분석 처리를 위한 고성능 열 기반 SQL 데이터베이스 관리 시스템입니다. 나에게 가장 중요한 기능은 다음과 같습니다. 열 기반 스토리지는 ClickHouse가 필요한 경우에만 디스크에서 읽을 수 있음을 의미합니다. ClickHouse는 사용 가능한 모든 리소스(CPU 코어 및 디스크)를 활용하여 단일 쿼리도 실행할 수 있습니다. 데이터 압축 SQL 지원, 마지막으로 중요한 것은 기술 솔루션 Github 저장소 튜토리얼 에서 로그 생성기와 함께 튜토리얼을 찾을 수 있습니다. GitHub ClickHouse 설정 및 연결 우선 두 서비스를 모두 정의하기 위해 docker-compose.yml을 생성해 보겠습니다. ( ) https://github.com/bp72/nginxlogprocessor/blob/init-commit/docker-compose.yml version: '3.6' services: ch: image: clickhouse/clickhouse-server container_name: clickhouse restart: always volumes: - clickhousedata:/var/lib/clickhouse/ ports: - '8123:8123' - '9000:9000' ulimits: memlock: soft: -1 hard: -1 nofile: soft: 262144 hard: 262144 # This capabilities prevents Docker from complaining about lack of those cap_add: - SYS_NICE - NET_ADMIN - IPC_LOCK volumes: clickhousedata: 실행하고 모든 것이 작동하는지 확인하겠습니다. Docker 컨테이너에서 ClickHouse 클라이언트를 사용하여 ClickHouse 인스턴스에 연결하고 localhost 및 포트를 통해 사용 가능한지 확인합니다. > docker-compose up -d [+] Running 3/3 ⠿ Network nginxlogprocessor_default Created 0.1s ⠿ Container clickhouse Started 0.6s 인스턴스에 연결하려면 ClickHouse 클라이언트를 선호합니다. docker-compose exec ch clickhouse-client ClickHouse client version 23.9.1.1854 (official build). Connecting to localhost:9000 as user default. Connected to ClickHouse server version 23.9.1 revision 54466. a8c8da069d94 :) 데이터베이스 및 테이블 생성 이제 모든 것이 설정되면 데이터베이스와 테이블을 생성해 보겠습니다. 처리된 추적하는 데이터베이스와 특정 서비스(예: 에 대한 데이터베이스 및 첫 번째 테이블 . 로그를 nginx ) nginx.access 입니다 ClickHouse의 중요한 장점 중 하나는 정의 및 쿼리를 위한 SQL 구문입니다. 엄밀히 말하면 SQL 표준은 아니지만 매우 유사합니다. CREATE DATABASE IF NOT EXISTS nginx CREATE DATABASE IF NOT EXISTS logs CREATE TABLE IF NOT EXISTS nginx.access ( reqid String, ts DateTime64(3), level Enum(''=0, 'debug'=1, 'info'=2, 'warn'=3 ,'error'=4), domain String, uri String, ua String, ref String, is_bot Boolean, is_mobile Boolean, is_tablet Boolean, is_pc Boolean, client String, duration Float32, response_code UInt16, addrIPv4 Nullable(IPv4), addrIPv6 Nullable(IPv6), upstream_connect_time Float32, upstream_header_time Float32, upstream_response_time Float32 ) ENGINE MergeTree PRIMARY KEY reqid ORDER BY reqid CREATE TABLE IF NOT EXISTS logs.logfiles ( filename String ) ENGINE MergeTree PRIMARY KEY filename ORDER BY filename 문을 자세히 살펴보면 약간 다른 유형과 Enum 및 IPv4와 같은 완전히 새로운 유형을 볼 수 있습니다. ClickHouse는 리소스 사용량을 줄이고 이를 위해 Enum과 같은 멋진 기능을 사용하여 최적화합니다. 기본적으로 문자열을 8비트 또는 16비트의 int로 매핑하는 키-값 매핑입니다. 삽입 시 자동으로 변환되고 문자열 값이 int로 변환되고 선택 시 반대 방향( )으로 변환됩니다. CREATE TABLE link IPv4, IPv6는 가장 최적의 방식(unsigned int)으로 주소를 저장하고 이를 사람이 읽을 수 있는 방식으로 표현하는 특수 유형입니다. 따라서 기본적으로 삽입 시 IP 주소의 문자열 표현을 제공하고 ClickHouse가 모든 작업을 수행합니다. 당신: 선택 시 압축이 풀린 int 및 서버로 저장합니다. 로그 삽입 ClickHouse의 이념은 빠르게 삽입하는 것입니다. 이를 위해 ClickHouse는 하나씩 삽입하는 것보다 일괄 삽입을 더 잘 처리합니다. 따라서 삽입 스크립트는 그리 복잡하지 않습니다. 함수는 ClickHouse 클라이언트가 삽입할 최대 50,000개 레코드의 데이터 청크를 생성합니다. 각 청크 항목은 목록의 열 이름과 관련된 값 목록을 나타냅니다. readFile 열 # it's not an actual code. # the working implementation you can find at https://github.com/bp72/nginxlogprocessor import clickhouse_connect from config import CLICKHOUSE_HOST, CLICKHOUSE_PORT from log import log client = clickhouse_connect.get_client(host=CLICKHOUSE_HOST, port=CLICKHOUSE_PORT) def loadToClickHouse(client, chunk): cols = [ 'reqid', 'ts', 'level', 'domain', 'uri', 'ua', 'ref', 'is_bot', 'is_mobile', 'is_tablet', 'is_pc', 'client', 'duration', 'response_code', 'addrIPv4', 'addrIPv6', 'upstream_connect_time', 'upstream_header_time', 'upstream_response_time', ] client.insert('nginx.access', chunk, column_names=cols) def processFeed(feed, client, chunk_size=10_000): total = 0 for chunk in readFile(feed, chunk_size=chunk_size): total += len(chunk) loadToClickHouse(client, chunk=chunk) log.info(f'process {feed=} inserted={len(chunk)} {total=}') 내 PC의 실제 실행 및 타이밍을 보면 800k 레코드 파일의 구문 분석 및 삽입에 21초의 Python 실행 시간이 걸렸다는 것을 알 수 있습니다. 나쁘지 않다! > .venv/bin/python ./main.py I:2023-10-15 12:44:02 [18764] f=transport.py:1893 Connected (version 2.0, client OpenSSH_8.9p1) I:2023-10-15 12:44:02 [18764] f=transport.py:1893 Authentication (publickey) successful! I:2023-10-15 12:44:02 [18764] f=fetcher.py:14 connect host='*.*.*.*' port=22 user='root' password=None I:2023-10-15 12:44:02 [18764] f=fetcher.py:18 run cmd='ls /var/log/nginx/*access*.log-*' I:2023-10-15 12:44:02 [18764] f=fetcher.py:34 download src=/var/log/nginx/access.log-2023100812.gz dst=/tmp/access.log-2023100812.gz I:2023-10-15 12:44:07 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=50000 I:2023-10-15 12:44:08 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=100000 I:2023-10-15 12:44:10 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=150000 I:2023-10-15 12:44:11 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=200000 I:2023-10-15 12:44:13 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=250000 I:2023-10-15 12:44:14 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=300000 I:2023-10-15 12:44:15 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=350000 I:2023-10-15 12:44:17 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=400000 I:2023-10-15 12:44:18 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=450000 I:2023-10-15 12:44:20 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=500000 I:2023-10-15 12:44:21 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=550000 I:2023-10-15 12:44:23 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=600000 I:2023-10-15 12:44:24 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=650000 I:2023-10-15 12:44:25 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=700000 I:2023-10-15 12:44:27 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=750000 I:2023-10-15 12:44:28 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=800000 I:2023-10-15 12:44:28 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=2190 total=802190 I:2023-10-15 12:44:28 [18764] f=fetcher.py:34 download src=/var/log/nginx/access.log-2023100814.gz dst=/tmp/access.log-2023100814.gz I:2023-10-15 12:44:31 [18764] f=main.py:20 process feed='/tmp/access.log-2023100814.gz' inserted=50000 total=50000 I:2023-10-15 12:44:32 [18764] f=main.py:20 process feed='/tmp/access.log-2023100814.gz' inserted=50000 total=100000 I:2023-10-15 12:44:33 [18764] f=main.py:20 process feed='/tmp/access.log-2023100814.gz' inserted=30067 total=130067 로그 분석 및 문제 감지 ClickHouse는 SQL을 사용하여 대부분의 소프트웨어 엔지니어에게 매우 편안하고 직관적으로 간단한 DB를 쿼리합니다. 우리가 가지고 있는 레코드 수를 확인하는 것부터 시작하겠습니다. 22M입니다. a8c8da069d94 :) select count(1) from nginx.access; SELECT count(1) FROM nginx.access Query id: f94881f3-2a7d-4039-9646-a6f614adb46c ┌──count()─┐ │ 22863822 │ └──────────┘ 다양한 분류로 쿼리를 수행하는 것은 쉽습니다. 이는 문제 감지 및 해결에 유용할 수 있습니다. 예를 들어 호스트가 어떤 IP 주소에서 취약점을 검사하는지 알고 싶습니다. 이 쿼리는 유연한 데이터 쿼리를 ELK와 비교할 수 있는 방법을 보여줍니다. WITH .. AS 문과 IN / NOT IN, 하위 쿼리, 집계, 필터링 기능을 통해 ClickHouse가 매우 편리해졌습니다. a8c8da069d94 :) with baduri as (select uri, count(1) from nginx.access where response_code = 404 and uri not in ('/about/', '/favicon.ico') group by 1 having count(1) > 3 order by 2 desc limit 10) select IPv4NumToStringClassC(addrIPv4), count(1) from nginx.access where uri in (select uri from baduri) and addrIPv4 is not null group by 1 order by 2 desc limit 5 WITH baduri AS ( SELECT uri, count(1) FROM nginx.access WHERE (response_code = 404) AND (uri NOT IN ('/about/', '/favicon.ico')) GROUP BY 1 HAVING count(1) > 3 ORDER BY 2 DESC LIMIT 10 ) SELECT IPv4NumToStringClassC(addrIPv4), count(1) FROM nginx.access WHERE (uri IN ( SELECT uri FROM baduri )) AND (addrIPv4 IS NOT NULL) GROUP BY 1 ORDER BY 2 DESC LIMIT 5 Query id: cf9bea33-212b-4c58-b6af-8e0aaae50b83 ┌─IPv4NumToStringClassC(addrIPv4)─┬─count()─┐ │ 8.219.64.xxx │ 961 │ │ 178.128.220.xxx │ 378 │ │ 103.231.78.xxx │ 338 │ │ 157.245.200.xxx │ 324 │ │ 116.203.28.xxx │ 260 │ └─────────────────────────────────┴─────────┘ 5 rows in set. Elapsed: 0.150 sec. Processed 45.73 million rows, 1.81 GB (303.88 million rows/s., 12.01 GB/s.) Peak memory usage: 307.49 MiB. 도메인당 가장 인기 있는 상위 5개 URI를 구해 보겠습니다. 이 쿼리는 편리한 LIMIT x BY <field> 함수를 사용합니다. a8c8da069d94 :) select domain, uri, count(1) from nginx.access where domain in ('example.com', 'nestfromthebest.com', 'az.org') group by 1, 2 order by 1, 3 desc limit 5 by domain SELECT domain, uri, count(1) FROM nginx.access WHERE domain IN ('example.com', 'nestfromthebest.com', 'az.org') GROUP BY 1, 2 ORDER BY 1 ASC, 3 DESC LIMIT 5 BY domain Query id: 2acd328c-ed82-4d36-916b-8f2ecf764a9d ┌─domain──────┬─uri────────────┬─count()─┐ │ az.org │ /about/ │ 382543 │ │ az.org │ /contacts/ │ 42066 │ │ az.org │ /category/id7 │ 2722 │ │ az.org │ /category/id14 │ 2704 │ │ az.org │ /category/id2 │ 2699 │ │ example.com │ /about/ │ 381653 │ │ example.com │ /contacts/ │ 42023 │ │ example.com │ /category/id2 │ 2694 │ │ example.com │ /category/id8 │ 2688 │ │ example.com │ /category/id13 │ 2670 │ └─────────────┴────────────────┴─────────┘ ┌─domain──────────────┬─uri────────────┬─count()─┐ │ nestfromthebest.com │ /about/ │ 383377 │ │ nestfromthebest.com │ /contacts/ │ 42100 │ │ nestfromthebest.com │ /category/id8 │ 2726 │ │ nestfromthebest.com │ /category/id14 │ 2700 │ │ nestfromthebest.com │ /category/id4 │ 2696 │ └─────────────────────┴────────────────┴─────────┘ 15 rows in set. Elapsed: 0.062 sec. Processed 23.97 million rows, 918.43 MB (388.35 million rows/s., 14.88 GB/s.) Peak memory usage: 98.67 MiB. 결론 ClickHouse는 로그와 같은 특정 데이터를 대규모로 저장하고 조작할 수 있는 훌륭한 도구입니다. 예를 들어 중첩된 데이터 구조, 샘플링 도구, 창 기능 등을 더 배우고 이해할 가치가 있습니다. 이 작은 기사가 도움이 되기를 바랍니다.