

안녕하세요, 에이플랫폼입니다.
Amazon S3를 사용하면 대용량 데이터를 효율적으로 저장하고 관리할 수 있지만, 접근 로그(Access Logs)를 분석하는 것은 쉽지 않은 일입니다. S3 로그는 원시 형태(raw format)로 제공되기 때문에 원하는 정보를 빠르게 추출하고 활용하려면 추가적인 분석 과정이 필요합니다.
하지만 SingleStore 파이프라인을 활용하면 S3에서 액세스 로그를 손쉽게 스트리밍하여 실시간으로 분석할 수 있습니다. 이를 통해 로그 데이터를 빠르게 로드하고, 강력한 SQL 쿼리를 사용해 원하는 정보를 즉시 추출할 수 있습니다.
이번 글에서는 SingleStore를 활용하여 S3 접근 로그를 효과적으로 분석하는 방법을 소개합니다. 직접 테스트한 내용을 바탕으로, 로그 데이터를 빠르게 로드하고 쿼리하는 방법을 설명하겠습니다.
그럼 시작해보겠습니다!
AWS S3 서버 액세스 로깅 활성화 하기
📌
액세스 로그를 추적할 버킷을 소스 버킷, 액세스 로그가 저장되는 버킷을 타켓 버킷 이라고 합니다.
소스 버킷과 타켓 버킷은 모두 동일한 AWS 리전에 있어야 하며 동일한 계정이 소유해야 합니다.
타켓 버킷에는 S3 객체 잠금 기본 보존 기간 구성이 없어야 합니다.
또한 타켓 버킷에는 요청자 지불이 활성화되어 있지 않아야 합니다.
소스 버킷 자체를 포함하여 소스 버킷과 동일한 리전에 있는 소유한 모든 버킷으로 로그를 전송할 수 있습니다.
그러나 더 간단한 로그 관리를 위해서는 액세스 로그를 다른 버킷에 저장하는 것이 좋습니다.
소스 버킷과 타켓 버킷이 동일한 버킷인 경우 버킷에 기록되는 로그에 대해 추가 로그가 생성되어 로그의 무한 루프가 생성됩니다. 이 작업은 스토리지 청구액이 증가할 수 있습니다.
또한 로그에 대한 추가 로그로 인해 찾고 있는 로그를 찾기가 더 어려워질 수 있습니다.
소스 버킷에 액세스 로그를 저장하기로 선택한 경우 모든 로그 객체 키에 대해 접두사를 지정하는 것이 좋습니다.
접두사를 지정하면 모든 로그 개체 이름이 공통 문자열로 시작하므로 로그 개체를 더 쉽게 식별할 수 있습니다.
타겟 버킷 설정

버킷의 속성 클릭

서버 액세스 로깅이 비활성화 상태라면 편집을 눌러서 활성화시키기

활성화 후 소스 S3 찾아보기를 눌러 버킷의 액세스 로그가 저장될 타겟 버킷 및 접두사 설정
로그 예시
아래는 AWS 문서에 나온 5개의 예제 로그입니다.
79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be amzn-s3-demo-bucket1 [06/Feb/2019:00:00:38 +0000] 192.0.2.3 79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be 3E57427F3EXAMPLE REST.GET.VERSIONING - "GET /amzn-s3-demo-bucket1?versioning HTTP/1.1" 200 - 113 - 7 - "-" "S3Console/0.4" - s9lzHYrFp76ZVxRcpX9+5cjAnEH2ROuNkd2BHfIa6UkFVdtjf5mKR3/eTPFvsiP/XV/VLi31234= SigV4 ECDHE-RSA-AES128-GCM-SHA256 AuthHeader amzn-s3-demo-bucket1.s3.us-west-1.amazonaws.com TLSV1.2 arn:aws:s3:us-west-1:123456789012:accesspoint/example-AP Yes
79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be amzn-s3-demo-bucket1 [06/Feb/2019:00:00:38 +0000] 192.0.2.3 79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be 891CE47D2EXAMPLE REST.GET.LOGGING_STATUS - "GET /amzn-s3-demo-bucket1?logging HTTP/1.1" 200 - 242 - 11 - "-" "S3Console/0.4" - 9vKBE6vMhrNiWHZmb2L0mXOcqPGzQOI5XLnCtZNPxev+Hf+7tpT6sxDwDty4LHBUOZJG96N1234= SigV4 ECDHE-RSA-AES128-GCM-SHA256 AuthHeader amzn-s3-demo-bucket1.s3.us-west-1.amazonaws.com TLSV1.2 - -
79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be amzn-s3-demo-bucket1 [06/Feb/2019:00:00:38 +0000] 192.0.2.3 79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be A1206F460EXAMPLE REST.GET.BUCKETPOLICY - "GET /amzn-s3-demo-bucket1?policy HTTP/1.1" 404 NoSuchBucketPolicy 297 - 38 - "-" "S3Console/0.4" - BNaBsXZQQDbssi6xMBdBU2sLt+Yf5kZDmeBUP35sFoKa3sLLeMC78iwEIWxs99CRUrbS4n11234= SigV4 ECDHE-RSA-AES128-GCM-SHA256 AuthHeader amzn-s3-demo-bucket1.s3.us-west-1.amazonaws.com TLSV1.2 - Yes
79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be amzn-s3-demo-bucket1 [06/Feb/2019:00:01:00 +0000] 192.0.2.3 79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be 7B4A0FABBEXAMPLE REST.GET.VERSIONING - "GET /amzn-s3-demo-bucket1?versioning HTTP/1.1" 200 - 113 - 33 - "-" "S3Console/0.4" - Ke1bUcazaN1jWuUlPJaxF64cQVpUEhoZKEG/hmy/gijN/I1DeWqDfFvnpybfEseEME/u7ME1234= SigV4 ECDHE-RSA-AES128-GCM-SHA256 AuthHeader amzn-s3-demo-bucket1.s3.us-west-1.amazonaws.com TLSV1.2 - -
79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be amzn-s3-demo-bucket1 [06/Feb/2019:00:01:57 +0000] 192.0.2.3 79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be DD6CC733AEXAMPLE REST.PUT.OBJECT s3-dg.pdf "PUT /amzn-s3-demo-bucket1/s3-dg.pdf HTTP/1.1" 200 - - 4406583 41754 28 "-" "S3Console/0.4" - 10S62Zv81kBW7BB6SX4XJ48o6kpcl6LPwEoizZQQxJd5qDSCTLX0TgS37kYUBKQW3+bPdrg1234= SigV4 ECDHE-RSA-AES128-SHA AuthHeader amzn-s3-demo-bucket1.s3.us-west-1.amazonaws.com TLSV1.2 - Yes
테이블 만들기
이제 본격적으로 S3 로그를 저장할 테이블을 생성하겠습니다.
AWS S3 로그의 컬럼들은 공식문서에서 확인할 수 있지만,
https://docs.aws.amazon.com/AmazonS3/latest/userguide/LogFormat.html
Amazon S3 server access log format - Amazon Simple Storage Service
Any field can be set to - to indicate that the data was unknown or unavailable, or that the field was not applicable to this request.
docs.aws.amazon.com
실제 SQL로 변환하면 아래와 같은 형태가 됩니다.
create database aws_access_logs;
use aws_access_logs;
CREATE TABLE `aws_access_logs` (
`bucket_owner` blob not null,
`bucket` blob not null,
`request_time` datetime not null,
`remote_ip` blob not null,
`requester` blob not null,
`request_id` blob not null,
`operation` blob not null,
`key` blob not null,
`request_uri` blob not null,
`http_status` bigint not null,
`error_code` bigint not null,
`bytes_sent` bigint not null,
`object_size` bigint not null,
`total_time` bigint not null,
`turn_around_time` bigint not null,
`referer` blob not null,
`user_agent` blob not null,
`version_id` blob not null,
`host_id` blob not null,
`signature_version` blob not null,
`cipher_suite` blob not null,
`auth_type` blob not null,
`host_head` blob not null,
`tls_version` blob not null,
`acess_arn` blob not null,
`acl_required` blob,
SORT KEY (request_time)
);
📌blob 데이터 타입은 TEXT 등.. 데이터 타입을 조정하여 사용 가능합니다.
제가 구성한 테이블에서는 request_time 컬럼에 정렬 키(sort key)를 추가했습니다. 이렇게 하면 시간 기반 필터링 쿼리의 성능이 최적화됩니다. 물론, 사용 패턴에 따라 다른 컬럼을 정렬 키로 선택할 수도 있습니다.
파이프라인 생성
이제 데이터를 로드할 차례입니다. SingleStore 파이프라인을 활용하면 이 과정을 매우 간소화할 수 있습니다. 더욱이, 날짜 형식 변환과 관련된 복잡한 부분을 미리 처리해 두었기 때문에, 아래의 파이프라인 코드를 그대로 사용할 수 있습니다.
주의해야 할 점은 다음과 같습니다
- Amazon에서 acl_required 필드를 때때로 생략하는 경우가 있습니다.
- 이를 해결하기 위해 IGNORE 절을 사용해야 합니다.
이러한 설정을 통해 데이터 로딩 과정에서 발생할 수 있는 문제를 사전에 방지하고, 원활한 파이프라인 운영을 보장할 수 있습니다.
CREATE PIPELINE `aws_access_logs_pipeline`
AS LOAD DATA S3 '<your-audit-logging-bucket>'
CONFIG '{\"region\":\"us-east-1\"}'
CREDENTIALS '{
"aws_access_key_id":<youraccessid>,
"aws_secret_access_key":<yoursecretkey>
}'
IGNORE
INTO TABLE `aws_access_logs`
FIELDS TERMINATED BY ' ' ENCLOSED BY '"'
(
`aws_logs`.`bucket_owner`,
`aws_logs`.`bucket`,
@tp1,
@tp2,
`aws_logs`.`remote_ip`,
`aws_logs`.`requester`,
`aws_logs`.`request_id`,
`aws_logs`.`operation`,
`aws_logs`.`key`,
`aws_logs`.`request_uri`,
`aws_logs`.`http_status`,
`aws_logs`.`error_code`,
@bs,
`aws_logs`.`object_size`,
`aws_logs`.`total_time`,
`aws_logs`.`turn_around_time`,
`aws_logs`.`referer`,
`aws_logs`.`user_agent`,
`aws_logs`.`version_id`,
`aws_logs`.`host_id`,
`aws_logs`.`signature_version`,
`aws_logs`.`cipher_suite`,
`aws_logs`.`auth_type`,
`aws_logs`.`host_head`,
`aws_logs`.`tls_version`,
`aws_logs`.`acess_arn`,
`aws_logs`.`acl_required`
)
SET
request_time = str_to_date(concat(@tp1, ' ', @tp2), '[%d/%b/%Y:%H:%i:%s +0000]'),
bytes_sent = if(@bs = '-', 0, @bs);
파이프라인은 새 데이터가 나타날 때마다 지속적으로 데이터를 로드하기 때문에 로그를 손쉽게 스트리밍 할 수 있습니다.
파일 목록을 확인 해보겠습니다.
select * from information_schema.pipelines_files;
# 어떤 파일들을 받아오는지 파일명을 볼 수 있습니다.
|DATABASE_NAME |PIPELINE_NAME |SOURCE_TYPE|FILE_NAME |FILE_SIZE|FILE_STATE|
|---------------|------------------------|-----------|----------------------------------------|---------|----------|
|aws_access_logs|aws_access_logs_pipeline|S3 |log/2025-03-21-03-12-04-FBE1FB4DEEB6E9D0|1,550 |Loaded |
|aws_access_logs|aws_access_logs_pipeline|S3 |log/2025-03-21-03-12-57-7FFC56E84CE98F78|2,199 |Loaded |
|aws_access_logs|aws_access_logs_pipeline|S3 |log/2025-03-21-03-12-59-02034D523034658F|2,199 |Loaded |
|aws_access_logs|aws_access_logs_pipeline|S3 |log/2025-03-21-03-13-32-78CE7FDBCC46028A|2,195 |Loaded |
데이터 소스(Ex: Amazon S3)에서 추출된 파일들에 대한 정보를 볼 수 있습니다.
이제 foreground 에서 배치를 실행 해 보겠습니다.
start pipeline aws_access_logs_pipeline foreground limit 1 batches;
이렇게 하면 파이프라인의 한 배치가 foreground 에서 실행 됩니다.
데이터를 확인해 보겠습니다.
select * from aws_access_logs limit 10;
제가 직접 실행 했을 때는 이런 식으로 보였습니다.
+-------------+------------+---------------------+----------+-----------+---------------+------------------+------------------------------------------------------+----------------------------------------------------------------------------------------+-------------+------------+------------+-------------+------------+------------------+---------+-------------------------------------------------------------------------+------------+-----------+-------------------+----------------+------------+----------------------------+-------------+-----------+--------------+
| bucket_owner| bucket | request_time | remote_ip| requester | request_id | operation | key | request_uri | http_status | error_code | bytes_sent | object_size | total_time | turn_around_time | referer | user_agent | version_id | host_id | signature_version | cipher_suite | auth_type | host_head | tls_version | acess_arn | acl_required |
+-------------+------------+---------------------+----------+-----------+---------------+------------------+------------------------------------------------------+----------------------------------------------------------------------------------------+-------------+------------+------------+-------------+------------+------------------+---------+-------------------------------------------------------------------------+------------+-----------+-------------------+----------------+------------+----------------------------+-------------+-----------+--------------+
| {owner_name}| jwy-source | 2025-03-21 04:02:55 | {ip} |{requester}| {requester_id}| REST.GET.OBJECT | test/{key_path}/2025-03-21_14763792787876195952_2001 | GET /jwy-source/test/{request_uri_path}/2025-03-21_14763792787876195952_2001 HTTP/1.1 | 200 | 0 | 123903 | 123903 | 76 | 74 | - | aws-sdk-cpp/1.8.185 Linux/5.14.0-503.14.1.el9_5.x86_64 x86_64 GCC/8.4.0 | - | {host_id} | SigV4 | {cipher_suite} | {auth_type}| s3.us-west-2.amazonaws.com | TLSv1.3 | - | - |
| {owner_name}| jwy-source | 2025-03-21 04:09:46 | {ip} |{requester}| {requester_id}| REST.HEAD.OBJECT | test/{key_path}/2025-03-21_14763792787876195952_2001 | HEAD /jwy-source/test/{request_uri_path}/2025-03-21_14763792787876195952_2001 HTTP/1.1 | 200 | 0 | 0 | 131858 | 13 | 0 | - | aws-sdk-cpp/1.8.185 Linux/5.14.0-503.14.1.el9_5.x86_64 x86_64 GCC/8.4.0 | - | {host_id} | SigV4 | {cipher_suite} | {auth_type}| s3.us-west-2.amazonaws.com | TLSv1.3 | - | - |
| {owner_name}| jwy-source | 2025-03-21 04:11:21 | {ip} |{requester}| {requester_id}| REST.PUT.OBJECT | test/{key_path}/2025-03-21_14763792787876195952_2001 | PUT /jwy-source/test/{request_uri_path}/2025-03-21_14763792787876195952_2001 HTTP/1.1 | 200 | 0 | 0 | 134293 | 761 | 14 | - | aws-sdk-cpp/1.8.185 Linux/5.14.0-503.14.1.el9_5.x86_64 x86_64 GCC/8.4.0 | - | {host_id} | SigV4 | {cipher_suite} | {auth_type}| s3.us-west-2.amazonaws.com | TLSv1.3 | - | - |
+-------------+------------+---------------------+----------+-----------+---------------+------------------+------------------------------------------------------+----------------------------------------------------------------------------------------+-------------+------------+------------+-------------+------------+------------------+---------+-------------------------------------------------------------------------+------------+-----------+-------------------+----------------+------------+----------------------------+-------------+-----------+--------------+
마지막으로 파이프라인을 시작해 보겠습니다.
start pipeline aws_access_logs_pipeline;
이제 데이터가 스트리밍 되고 있습니다! 다음을 실행하여 파이프라인의 최근 배치 상태를 언제든지 확인할 수 있습니다.
select * from information_schema.pipelines_batches_summary;
추가 팁: 쿼리 최적화와 Persistent Computed Column 활용
대부분의 경우, 지정된 정규식과 일치하는 값을 추출하는 것으로 충분할 것입니다. 하지만 이 과정에서 몇 가지 고려해야 할 점이 있습니다:
- 반복적인 정규식 입력의 문제점:
- 매번 정규식을 입력하는 것은 번거롭습니다.
- 쿼리 실행마다 불필요한 연산이 발생하여 성능 저하를 초래할 수 있습니다.
- SingleStore의 최적화 기능
- SingleStore는 자주 사용되는 정규식을 인식하고 쿼리 실행을 최적화할 수 있습니다.
- 이를 통해 정규식과 일치하지 않는 데이터 세그먼트는 처리하지 않아 효율성을 높일 수 있습니다.
2. Persistent Computed Column의 이점
- 정규식을 한 번만 계산한 후 결과를 저장합니다.
- 이후 쿼리에서 불필요한 반복 연산을 피하고 실행 속도를 크게 향상시킬 수 있습니다.
예를 들어, 특정 문자열이 포함된 키를 확인하고 싶다면 다음과 같은 SQL 문을 사용할 수 있습니다
ALTER TABLE aws_access_logs ADD COLUMN is_foo AS `key` LIKE '%foo%' PERSISTED TINYINT;
이렇게 생성된 is_foo 컬럼은 다른 일반 컬럼과 동일하게 SQL에서 사용할 수 있으며, 쿼리 실행 시 자동으로 최적화됩니다.
이를 통해 데이터 분석의 효율성과 속도를 크게 개선할 수 있습니다.

'SingleStoreDB > 엔지니어링' 카테고리의 다른 글
SingleStore - Apache Flink를 사용한 실시간 스트리밍 파이프라인 구축 (0) | 2025.04.07 |
---|---|
SingleStore로 구현한 실시간 자동완성과 오타 허용 (0) | 2025.03.14 |
DashApp 따라하기 with SingleStore (0) | 2025.02.27 |
Semantic Search 따라하기 with SingleStore (0) | 2025.02.24 |
SingleStore 클러스터 구축 (0) | 2025.02.04 |