์ผ๋ฐ์ ์ผ๋ก Prometheus + Grafana์ ๊ฐ์ ์์ฉ ์ ํ์ด๋ ๊ธฐ์ฑ ์คํ ์์ค ๋์์ ์ฌ์ฉํ์ฌ Nginx์ ์๋์ ๋ชจ๋ํฐ๋งํ๊ณ ๋ถ์ํฉ๋๋ค. ์ด๋ ๋ชจ๋ํฐ๋ง์ด๋ ์ค์๊ฐ ๋ถ์์๋ ์ข์ ์ต์ ์ด์ง๋ง ๊ธฐ๋ก ๋ถ์์๋ ๊ทธ๋ค์ง ํธ๋ฆฌํ์ง ์์ต๋๋ค. ๋๋ฆฌ ์ฌ์ฉ๋๋ ๋ชจ๋ ๋ฆฌ์์ค์์๋ nginx ๋ก๊ทธ์ ๋ฐ์ดํฐ ์์ด ๋น ๋ฅด๊ฒ ์ฆ๊ฐํ๊ณ ์์ผ๋ฉฐ, ๋ง์ ์์ ๋ฐ์ดํฐ๋ฅผ ๋ถ์ํ๋ ค๋ฉด ์ข ๋ ์ ๋ฌธ์ ์ธ ๊ฒ์ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ผ๋ฆฌ์ ์ ๋๋ค.
์ด๋ฒ ๊ธ์์๋ ์ด๋ป๊ฒ ํ์ฉํ๋์ง ์๋ ค๋๋ฆฌ๊ฒ ์ต๋๋ค.
TL:DR;
์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ๋ ์ ๋ณด๋ฅผ ์์งํ๊ธฐ ์ํด
Nginx ๋ก๊ทธ ์์ง
๊ธฐ๋ณธ์ ์ผ๋ก Nginx ๋ก๊ทธ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
4/9/2019 12:58:17 PM1.1.1.1 - - [09/Apr/2019:09:58:17 +0000] "GET /sign-up HTTP/2.0" 200 9168 "https://example.com/sign-in" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
4/9/2019 12:58:17 PM1.1.1.1 - - [09/Apr/2019:09:58:17 +0000] "GET /sign-in HTTP/2.0" 200 9168 "https://example.com/sign-up" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
๊ตฌ๋ฌธ ๋ถ์ํ ์ ์์ง๋ง JSON์์ ๋ก๊ทธ๋ฅผ ์์ฑํ๋๋ก Nginx ๊ตฌ์ฑ์ ์์ ํ๋ ๊ฒ์ด ํจ์ฌ ์ฝ์ต๋๋ค.
log_format json_combined escape=json '{ "created_at": "$msec", '
'"remote_addr": "$remote_addr", '
'"remote_user": "$remote_user", '
'"request": "$request", '
'"status": $status, '
'"bytes_sent": $bytes_sent, '
'"request_length": $request_length, '
'"request_time": $request_time, '
'"http_referrer": "$http_referer", '
'"http_x_forwarded_for": "$http_x_forwarded_for", '
'"http_user_agent": "$http_user_agent" }';
access_log /var/log/nginx/access.log json_combined;
์คํ ๋ฆฌ์ง์ฉ S3
๋ก๊ทธ๋ฅผ ์ ์ฅํ๊ธฐ ์ํด S3๋ฅผ ์ฌ์ฉํ๊ฒ ์ต๋๋ค. Athena๋ S3์ ๋ฐ์ดํฐ๋ก ์ง์ ์์ ํ ์ ์์ผ๋ฏ๋ก ์ด๋ฅผ ํตํด ๋ก๊ทธ๋ฅผ ํ ๊ณณ์ ์ ์ฅํ๊ณ ๋ถ์ํ ์ ์์ต๋๋ค. ๊ธฐ์ฌ ๋ท๋ถ๋ถ์์ ๋ก๊ทธ๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ์ถ๊ฐํ๊ณ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์ค๋ช ํ๊ฒ ์ง๋ง ๋จผ์ S3์ ์๋ฌด๊ฒ๋ ์ ์ฅ๋์ง ์๋ ๊นจ๋ํ ๋ฒํท์ด ํ์ํฉ๋๋ค. Athena๋ฅผ ๋ชจ๋ ์ง์ญ์์ ์ฌ์ฉํ ์๋ ์์ผ๋ฏ๋ก ๋ฒํท์ ์์ฑํ ์ง์ญ์ ๋ฏธ๋ฆฌ ๊ณ ๋ คํด ๋ณด๋ ๊ฒ์ด ์ข์ต๋๋ค.
Athena ์ฝ์์์ ํ๋ก ๋ง๋ค๊ธฐ
Athena์์ ๋ก๊ทธ์ฉ ํ ์ด๋ธ์ ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค. Kinesis Firehose๋ฅผ ์ฌ์ฉํ๋ ค๋ ๊ฒฝ์ฐ ์ฐ๊ธฐ์ ์ฝ๊ธฐ ๋ชจ๋์ ํ์ํฉ๋๋ค. Athena ์ฝ์์ ์ด๊ณ ํ ์ด๋ธ์ ์์ฑํฉ๋๋ค.
SQL ํ ์ด๋ธ ์์ฑ
CREATE EXTERNAL TABLE `kinesis_logs_nginx`(
`created_at` double,
`remote_addr` string,
`remote_user` string,
`request` string,
`status` int,
`bytes_sent` int,
`request_length` int,
`request_time` double,
`http_referrer` string,
`http_x_forwarded_for` string,
`http_user_agent` string)
ROW FORMAT SERDE
'org.apache.hadoop.hive.ql.io.orc.OrcSerde'
STORED AS INPUTFORMAT
'org.apache.hadoop.hive.ql.io.orc.OrcInputFormat'
OUTPUTFORMAT
'org.apache.hadoop.hive.ql.io.orc.OrcOutputFormat'
LOCATION
's3://<YOUR-S3-BUCKET>'
TBLPROPERTIES ('has_encrypted_data'='false');
Kinesis Firehose ์คํธ๋ฆผ ์์ฑ
Kinesis Firehose๋ Nginx์์ ๋ฐ์ ๋ฐ์ดํฐ๋ฅผ ์ ํํ ํ์์ผ๋ก S3์ ์์ฑํ์ฌ YYYY/MM/DD/HH ํ์์ ๋๋ ํฐ๋ฆฌ๋ก ๋๋๋๋ค. ์ด๋ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ ๋ ์ ์ฉํฉ๋๋ค. ๋ฌผ๋ก fluentd์์ S3์ ์ง์ ์ธ ์๋ ์์ง๋ง ์ด ๊ฒฝ์ฐ์๋ JSON์ ์์ฑํด์ผ ํ๊ณ ํ์ผ ํฌ๊ธฐ๊ฐ ์ปค์ ๋นํจ์จ์ ์ ๋๋ค. ๋ํ PrestoDB ๋๋ Athena๋ฅผ ์ฌ์ฉํ ๋ JSON์ ๊ฐ์ฅ ๋๋ฆฐ ๋ฐ์ดํฐ ํ์์ ๋๋ค. Kinesis Firehose ์ฝ์์ ์ด๊ณ "์ ์ก ์คํธ๋ฆผ ์์ฑ"์ ํด๋ฆญํ ํ "์ ์ก" ํ๋์์ "์ง์ PUT"์ ์ ํํฉ๋๋ค.
๋ค์ ํญ์์ "๋ ์ฝ๋ ํ์ ๋ณํ" - "ํ์ฑํ"๋ฅผ ์ ํํ๊ณ ๋
น์ ํ์์ผ๋ก "Apache ORC"๋ฅผ ์ ํํฉ๋๋ค. ์ผ๋ถ ์ฐ๊ตฌ์ ๋ฐ๋ฅด๋ฉด
์คํ ๋ฆฌ์ง๋ก S3๋ฅผ ์ ํํ๊ณ ์์ ์์ฑํ ๋ฒํท์ ์ ํํฉ๋๋ค. ๋์ค์ ์ค๋ช ํ Aws Glue Crawler๋ S3 ๋ฒํท์ ์ ๋์ฌ๋ฅผ ์ฌ์ฉํ ์ ์์ผ๋ฏ๋ก ๋น์ ๋๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
๋๋จธ์ง ์ต์ ์ ๋ถํ์ ๋ฐ๋ผ ๋ณ๊ฒฝ๋ ์ ์๋๋ฐ ์ ๋ ์ฃผ๋ก ๊ธฐ๋ณธ ์ต์ ์ ์ฌ์ฉํฉ๋๋ค. S3 ์์ถ์ ์ฌ์ฉํ ์ ์์ง๋ง ORC๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๊ธฐ๋ณธ ์์ถ์ ์ฌ์ฉํฉ๋๋ค.
์ ์ฐฝํจ
์ด์ ๋ก๊ทธ ์ ์ฅ ๋ฐ ์์ ์ ๊ตฌ์ฑํ์ผ๋ฏ๋ก ์ ์ก์ ๊ตฌ์ฑํด์ผ ํฉ๋๋ค. ์ฐ๋ฆฌ๋ ์ฌ์ฉํ ๊ฒ์ด๋ค
๋จผ์ fluent.conf ๊ตฌ์ฑ ํ์ผ์ด ํ์ํฉ๋๋ค. ๊ทธ๊ฒ์ ๋ง๋ค๊ณ ์์ค๋ฅผ ์ถ๊ฐํ์ญ์์ค :
ํฌํธ 24224
๋ฐ์ธ๋ 0.0.0.0
์ด์ Fluentd ์๋ฒ๋ฅผ ์์ํ ์ ์์ต๋๋ค. ๋ณด๋ค ๊ณ ๊ธ ๊ตฌ์ฑ์ด ํ์ํ ๊ฒฝ์ฐ ๋ค์์ผ๋ก ์ด๋ํ์ธ์.
$ docker run
-d
-p 24224:24224
-p 24224:24224/udp
-v /data:/fluentd/log
-v <PATH-TO-FLUENT-CONF>:/fluentd/etc fluentd
-c /fluentd/etc/fluent.conf
fluent/fluentd:stable
์ด ๊ตฌ์ฑ์ ๋ค์ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํฉ๋๋ค. /fluentd/log
๋ณด๋ด๊ธฐ ์ ์ ๋ก๊ทธ๋ฅผ ์บ์ํฉ๋๋ค. ์ด ์์
์์ด๋ ํ ์ ์์ง๋ง ๋ค์ ์์ํ๋ฉด ํ๋ ์์
์ผ๋ก ์ธํด ์บ์๋ ๋ชจ๋ ํญ๋ชฉ์ด ์์ค๋ ์ ์์ต๋๋ค. ๋ชจ๋ ํฌํธ๋ฅผ ์ฌ์ฉํ ์๋ ์์ต๋๋ค. 24224๊ฐ ๊ธฐ๋ณธ Fluentd ํฌํธ์
๋๋ค.
์ด์ Fluentd๊ฐ ์คํ๋์์ผ๋ฏ๋ก Nginx ๋ก๊ทธ๋ฅผ ๊ทธ๊ณณ์ผ๋ก ๋ณด๋ผ ์ ์์ต๋๋ค. ์ฐ๋ฆฌ๋ ์ผ๋ฐ์ ์ผ๋ก Docker ์ปจํ ์ด๋์์ Nginx๋ฅผ ์คํํฉ๋๋ค. ์ด ๊ฒฝ์ฐ Docker์๋ Fluentd์ฉ ๊ธฐ๋ณธ ๋ก๊น ๋๋ผ์ด๋ฒ๊ฐ ์์ต๋๋ค.
$ docker run
--log-driver=fluentd
--log-opt fluentd-address=<FLUENTD-SERVER-ADDRESS>
--log-opt tag="{{.Name}}"
-v /some/content:/usr/share/nginx/html:ro
-d
nginx
Nginx๋ฅผ ๋ค๋ฅด๊ฒ ์คํํ๋ ๊ฒฝ์ฐ ๋ก๊ทธ ํ์ผ์ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ Fluentd๋
์์์ ๊ตฌ์ฑํ ๋ก๊ทธ ๊ตฌ๋ฌธ ๋ถ์์ Fluent ๊ตฌ์ฑ์ ์ถ๊ฐํด ๋ณด๊ฒ ์ต๋๋ค.
<filter YOUR-NGINX-TAG.*>
@type parser
key_name log
emit_invalid_record_to_error false
<parse>
@type json
</parse>
</filter>
๊ทธ๋ฆฌ๊ณ ๋ค์์ ์ฌ์ฉํ์ฌ Kinesis์ ๋ก๊ทธ๋ฅผ ๋ณด๋
๋๋ค.
<match YOUR-NGINX-TAG.*>
@type kinesis_firehose
region region
delivery_stream_name <YOUR-KINESIS-STREAM-NAME>
aws_key_id <YOUR-AWS-KEY-ID>
aws_sec_key <YOUR_AWS-SEC_KEY>
</match>
์ํ ๋
๋ชจ๋ ๊ฒ์ ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌ์ฑํ๋ค๋ฉด ์ ์ ํ(๊ธฐ๋ณธ์ ์ผ๋ก Kinesis๋ ์์ ๋ ๋ฐ์ดํฐ๋ฅผ 10๋ถ๋ง๋ค ๊ธฐ๋กํจ) S3์ ๋ก๊ทธ ํ์ผ์ด ํ์๋์ด์ผ ํฉ๋๋ค. Kinesis Firehose์ "๋ชจ๋ํฐ๋ง" ๋ฉ๋ด์์๋ S3์ ๊ธฐ๋ก๋ ๋ฐ์ดํฐ์ ์๊ณผ ์ค๋ฅ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค. Kinesis ์ญํ ์ S3 ๋ฒํท์ ๋ํ ์ฐ๊ธฐ ์ก์ธ์ค ๊ถํ์ ๋ถ์ฌํ๋ ๊ฒ์ ์์ง ๋ง์ญ์์ค. Kinesis๊ฐ ๋ฌด์ธ๊ฐ๋ฅผ ๊ตฌ๋ฌธ ๋ถ์ํ ์ ์์ผ๋ฉด ๋์ผํ ๋ฒํท์ ์ค๋ฅ๋ฅผ ์ถ๊ฐํฉ๋๋ค.
์ด์ Athena์์ ๋ฐ์ดํฐ๋ฅผ ๋ณผ ์ ์์ต๋๋ค. ์ค๋ฅ๋ฅผ ๋ฐํํ ์ต๊ทผ ์์ฒญ์ ์ฐพ์๋ณด๊ฒ ์ต๋๋ค.
SELECT * FROM "db_name"."table_name" WHERE status > 499 ORDER BY created_at DESC limit 10;
๊ฐ ์์ฒญ์ ๋ํ ๋ชจ๋ ๊ธฐ๋ก ๊ฒ์
์ด์ ๋ก๊ทธ๊ฐ ์ฒ๋ฆฌ๋์ด ORC์ S3์ ์ ์ฅ๋์์ผ๋ฉฐ ์์ถ๋์ด ๋ถ์ํ ์ค๋น๊ฐ ๋์์ต๋๋ค. Kinesis Firehose๋ ์ด๋ฅผ ๋งค ์๊ฐ๋ง๋ค ๋๋ ํฐ๋ฆฌ๋ก ๊ตฌ์ฑํ์ต๋๋ค. ๊ทธ๋ฌ๋ ํ ์ด๋ธ์ด ๋ถํ ๋์ง ์๋ ํ Athena๋ ๋๋ฌธ ์์ธ๋ฅผ ์ ์ธํ๊ณ ๋ชจ๋ ์์ฒญ์ โโ๋ํด ์ ์ฒด ์๊ฐ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํฉ๋๋ค. ์ด๋ ๋ค์ ๋ ๊ฐ์ง ์ด์ ๋ก ํฐ ๋ฌธ์ ์ ๋๋ค.
- ๋ฐ์ดํฐ์ ์์ด ์ง์์ ์ผ๋ก ์ฆ๊ฐํ์ฌ ์ฟผ๋ฆฌ ์๋๊ฐ ๋๋ ค์ง๋๋ค.
- Athena๋ ์ค์บํ ๋ฐ์ดํฐ์ ์์ ๊ธฐ์ค์ผ๋ก ์๊ธ์ด ์ฒญ๊ตฌ๋๋ฉฐ ์์ฒญ๋น ์ต์ 10MB์ ๋๋ค.
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ฐ๋ฆฌ๋ S3์ ๋ฐ์ดํฐ๋ฅผ ํฌ๋กค๋งํ๊ณ Glue Metastore์ ํํฐ์ ์ ๋ณด๋ฅผ ์ฐ๋ AWS Glue Crawler๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์ด๋ฅผ ํตํด Athena๋ฅผ ์ฟผ๋ฆฌํ ๋ ํํฐ์ ์ ํํฐ๋ก ์ฌ์ฉํ ์ ์์ผ๋ฉฐ ์ฟผ๋ฆฌ์ ์ง์ ๋ ๋๋ ํฐ๋ฆฌ๋ง ์ค์บํฉ๋๋ค.
Amazon Glue ํฌ๋กค๋ฌ ์ค์
Amazon Glue Crawler๋ S3 ๋ฒํท์ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ์ค์บํ๊ณ ํํฐ์ ์ด ์๋ ํ ์ด๋ธ์ ์์ฑํฉ๋๋ค. AWS Glue ์ฝ์์์ Glue ํฌ๋กค๋ฌ๋ฅผ ์์ฑํ๊ณ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ ๋ฒํท์ ์ถ๊ฐํฉ๋๋ค. ์ฌ๋ฌ ๋ฒํท์ ๋ํด ํ๋์ ํฌ๋กค๋ฌ๋ฅผ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ, ์ด ๊ฒฝ์ฐ ์ง์ ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฒํท ์ด๋ฆ๊ณผ ์ผ์นํ๋ ์ด๋ฆ์ ๊ฐ์ง ํ ์ด๋ธ์ด ์์ฑ๋ฉ๋๋ค. ์ด ๋ฐ์ดํฐ๋ฅผ ์ ๊ธฐ์ ์ผ๋ก ์ฌ์ฉํ ๊ณํ์ด๋ผ๋ฉด ํ์์ ๋ง๊ฒ ํฌ๋กค๋ฌ์ ์คํ ์ผ์ ์ ๊ตฌ์ฑํด์ผ ํฉ๋๋ค. ์ฐ๋ฆฌ๋ ๋ชจ๋ ํ ์ด๋ธ์ ๋ํด ๋งค์๊ฐ ์คํ๋๋ ํ๋์ ํฌ๋กค๋ฌ๋ฅผ ์ฌ์ฉํฉ๋๋ค.
ํํฐ์ ์ ๋๋ ํ ์ด๋ธ
ํฌ๋กค๋ฌ๋ฅผ ์ฒ์ ์์ํ ํ์๋ ์ค์บํ ๊ฐ ๋ฒํท์ ๋ํ ํ ์ด๋ธ์ด ์ค์ ์ ์ง์ ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ํ๋์ผ ํฉ๋๋ค. Athena ์ฝ์์ ์ด๊ณ Nginx ๋ก๊ทธ๊ฐ ์๋ ํ ์ด๋ธ์ ์ฐพ์ต๋๋ค. ๋ญ๊ฐ๋ฅผ ์ฝ์ด ๋ด ์๋ค:
SELECT * FROM "default"."part_demo_kinesis_bucket"
WHERE(
partition_0 = '2019' AND
partition_1 = '04' AND
partition_2 = '08' AND
partition_3 = '06'
);
์ด ์ฟผ๋ฆฌ๋ 6๋ 7์ 8์ผ ์ค์ 2019์๋ถํฐ ์ค์ XNUMX์ ์ฌ์ด์ ์์ ๋ ๋ชจ๋ ๋ ์ฝ๋๋ฅผ ์ ํํฉ๋๋ค. ํ์ง๋ง ํํฐ์ ๋์ง ์์ ํ ์ด๋ธ์์ ์ฝ๋ ๊ฒ๋ณด๋ค ์ด๊ฒ์ด ์ผ๋ง๋ ๋ ํจ์จ์ ์ผ๊น์? ํ์์คํฌํ๋ก ํํฐ๋งํ์ฌ ๋์ผํ ๋ ์ฝ๋๋ฅผ ์ฐพ์ ์ ํํด ๋ณด๊ฒ ์ต๋๋ค.
๋จ ์ผ์ฃผ์ผ ๊ฐ์ ๋ก๊ทธ๊ฐ ํฌํจ๋ ๋ฐ์ดํฐ ์ธํธ์ 3.59์ด ๋ฐ 244.34MB์ ๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค. ํํฐ์ ๋ณ๋ก ํํฐ๋งํด ๋ณด๊ฒ ์ต๋๋ค.
์กฐ๊ธ ๋ ๋น ๋ฅด์ง๋ง ๊ฐ์ฅ ์ค์ํ ๊ฒ์ ๋ฐ์ดํฐ๊ฐ 1.23MB์ ๋ถ๊ณผํ๋ค๋ ๊ฒ์ ๋๋ค! ๊ฐ๊ฒฉ์ด ์์ฒญ๋น ์ต์ 10MB๊ฐ ์๋๋ผ๋ฉด ํจ์ฌ ์ ๋ ดํ ๊ฒ์ ๋๋ค. ๊ทธ๋ฌ๋ ์ฌ์ ํ ํจ์ฌ ๋ ์ข์ผ๋ฉฐ ๋๊ท๋ชจ ๋ฐ์ดํฐ ์ธํธ์์๋ ๊ทธ ์ฐจ์ด๊ฐ ํจ์ฌ ๋ ์ธ์์ ์ ๋๋ค.
Cube.js๋ฅผ ์ฌ์ฉํ์ฌ ๋์๋ณด๋ ๊ตฌ์ถ
๋์๋ณด๋๋ฅผ ๊ตฌ์ฑํ๊ธฐ ์ํด Cube.js ๋ถ์ ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํฉ๋๋ค. ๊ฝค ๋ง์ ๊ธฐ๋ฅ์ด ์์ง๋ง ์ฐ๋ฆฌ๋ ๋ ๊ฐ์ง, ์ฆ ํํฐ์
ํํฐ๋ฅผ ์๋์ผ๋ก ์ฌ์ฉํ๋ ๊ธฐ๋ฅ๊ณผ ๋ฐ์ดํฐ ์ฌ์ ์ง๊ณ์ ๊ด์ฌ์ด ์์ต๋๋ค. ๋ฐ์ดํฐ ์คํค๋ง๋ฅผ ์ฌ์ฉํฉ๋๋ค.
์๋ก์ด Cube.js ์ ํ๋ฆฌ์ผ์ด์
์ ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค. ์ฐ๋ฆฌ๋ ์ด๋ฏธ AWS ์คํ์ ์ฌ์ฉํ๊ณ ์์ผ๋ฏ๋ก ๋ฐฐํฌ์ Lambda๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๋
ผ๋ฆฌ์ ์
๋๋ค. Heroku ๋๋ Docker์์ Cube.js ๋ฐฑ์๋๋ฅผ ํธ์คํ
ํ๋ ค๋ ๊ฒฝ์ฐ ์์ฑ์ ์ํด Express ํ
ํ๋ฆฟ์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋ฌธ์๋ ๋ค๋ฅธ ์ฌ๋๋ค์ ์ค๋ช
ํฉ๋๋ค
$ npm install -g cubejs-cli
$ cubejs create nginx-log-analytics -t serverless -d athena
ํ๊ฒฝ ๋ณ์๋ Cube.js์์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ก์ธ์ค๋ฅผ ๊ตฌ์ฑํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ์์ฑ๊ธฐ๋ ํค๋ฅผ ์ง์ ํ ์ ์๋ .env ํ์ผ์ ์์ฑํฉ๋๋ค.
์ด์ ์ฐ๋ฆฌ๋ ํ์ํฉ๋๋ค
๋๋ ํ ๋ฆฌ์์ schema
, ํ์ผ์ ์์ฑํ์ธ์ Logs.js
. ๋ค์์ nginx์ ๋ฐ์ดํฐ ๋ชจ๋ธ ์์์
๋๋ค.
๋ชจ๋ธ ์ฝ๋
const partitionFilter = (from, to) => `
date(from_iso8601_timestamp(${from})) <= date_parse(partition_0 || partition_1 || partition_2, '%Y%m%d') AND
date(from_iso8601_timestamp(${to})) >= date_parse(partition_0 || partition_1 || partition_2, '%Y%m%d')
`
cube(`Logs`, {
sql: `
select * from part_demo_kinesis_bucket
WHERE ${FILTER_PARAMS.Logs.createdAt.filter(partitionFilter)}
`,
measures: {
count: {
type: `count`,
},
errorCount: {
type: `count`,
filters: [
{ sql: `${CUBE.isError} = 'Yes'` }
]
},
errorRate: {
type: `number`,
sql: `100.0 * ${errorCount} / ${count}`,
format: `percent`
}
},
dimensions: {
status: {
sql: `status`,
type: `number`
},
isError: {
type: `string`,
case: {
when: [{
sql: `${CUBE}.status >= 400`, label: `Yes`
}],
else: { label: `No` }
}
},
createdAt: {
sql: `from_unixtime(created_at)`,
type: `time`
}
}
});
์ฌ๊ธฐ์๋ ๋ณ์๋ฅผ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.
๋ํ ๋์๋ณด๋์ ํ์ํ๋ ค๋ ์ธก์ ํญ๋ชฉ๊ณผ ๋งค๊ฐ๋ณ์๋ฅผ ์ค์ ํ๊ณ ์ฌ์ ์ง๊ณ๋ฅผ ์ง์ ํฉ๋๋ค. Cube.js๋ ์ฌ์ ์ง๊ณ๋ ๋ฐ์ดํฐ๋ก ์ถ๊ฐ ํ ์ด๋ธ์ ์์ฑํ๊ณ ๋ฐ์ดํฐ๊ฐ ๋์ฐฉํ๋ฉด ์๋์ผ๋ก ์ ๋ฐ์ดํธํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ฟผ๋ฆฌ ์๋๊ฐ ๋นจ๋ผ์ง ๋ฟ๋ง ์๋๋ผ Athena ์ฌ์ฉ ๋น์ฉ๋ ์ ๊ฐ๋ฉ๋๋ค.
๋ฐ์ดํฐ ์คํค๋ง ํ์ผ์ ๋ค์ ์ ๋ณด๋ฅผ ์ถ๊ฐํด ๋ณด๊ฒ ์ต๋๋ค.
preAggregations: {
main: {
type: `rollup`,
measureReferences: [count, errorCount],
dimensionReferences: [isError, status],
timeDimensionReference: createdAt,
granularity: `day`,
partitionGranularity: `month`,
refreshKey: {
sql: FILTER_PARAMS.Logs.createdAt.filter((from, to) =>
`select
CASE WHEN from_iso8601_timestamp(${to}) + interval '3' day > now()
THEN date_trunc('hour', now()) END`
)
}
}
}
์ฐ๋ฆฌ๋ ์ด ๋ชจ๋ธ์์ ์ฌ์ฉ๋ ๋ชจ๋ ์งํ์ ๋ํ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ ์ง๊ณํ๊ณ ์๋ณ ๋ถํ ์ ์ฌ์ฉํด์ผ ํ๋ค๊ณ ์ง์ ํฉ๋๋ค.
์ด์ ๋์๋ณด๋๋ฅผ ์กฐ๋ฆฝํ ์ ์์ต๋๋ค!
Cube.js ๋ฐฑ์๋๋ ๋ค์์ ์ ๊ณตํฉ๋๋ค.
Cube.js ์๋ฒ๋ ๋ค์์ ์์ฒญ์ ์๋ฝํฉ๋๋ค.
{
"measures": ["Logs.errorCount"],
"timeDimensions": [
{
"dimension": "Logs.createdAt",
"dateRange": ["2019-01-01", "2019-01-07"],
"granularity": "day"
}
]
}
NPM์ ํตํด Cube.js ํด๋ผ์ด์ธํธ์ React ๊ตฌ์ฑ ์์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค์นํด ๋ณด๊ฒ ์ต๋๋ค.
$ npm i --save @cubejs-client/core @cubejs-client/react
์ฐ๋ฆฌ๋ ๋ถํ์ ์์
ํฉ๋๋ค cubejs
ะธ QueryRenderer
๋ฐ์ดํฐ๋ฅผ ๋ค์ด๋ก๋ํ๊ณ ๋์๋ณด๋๋ฅผ ์์งํ๋ ค๋ฉด ๋ค์์ ์ํํ์ธ์.
๋์๋ณด๋ ์ฝ๋
import React from 'react';
import { LineChart, Line, XAxis, YAxis } from 'recharts';
import cubejs from '@cubejs-client/core';
import { QueryRenderer } from '@cubejs-client/react';
const cubejsApi = cubejs(
'YOUR-CUBEJS-API-TOKEN',
{ apiUrl: 'http://localhost:4000/cubejs-api/v1' },
);
export default () => {
return (
<QueryRenderer
query={{
measures: ['Logs.errorCount'],
timeDimensions: [{
dimension: 'Logs.createdAt',
dateRange: ['2019-01-01', '2019-01-07'],
granularity: 'day'
}]
}}
cubejsApi={cubejsApi}
render={({ resultSet }) => {
if (!resultSet) {
return 'Loading...';
}
return (
<LineChart data={resultSet.rawData()}>
<XAxis dataKey="Logs.createdAt"/>
<YAxis/>
<Line type="monotone" dataKey="Logs.errorCount" stroke="#8884d8"/>
</LineChart>
);
}}
/>
)
}
๋์๋ณด๋ ์์ค๋ ๋ค์์์ ํ์ธํ ์ ์์ต๋๋ค.
์ถ์ฒ : habr.com