Amazon Athena ๋ฐ Cube.js๋ฅผ ์‚ฌ์šฉํ•œ Nginx ๋กœ๊ทธ ๋ถ„์„

์ผ๋ฐ˜์ ์œผ๋กœ Prometheus + Grafana์™€ ๊ฐ™์€ ์ƒ์šฉ ์ œํ’ˆ์ด๋‚˜ ๊ธฐ์„ฑ ์˜คํ”ˆ ์†Œ์Šค ๋Œ€์•ˆ์„ ์‚ฌ์šฉํ•˜์—ฌ Nginx์˜ ์ž‘๋™์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ  ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋ชจ๋‹ˆํ„ฐ๋ง์ด๋‚˜ ์‹ค์‹œ๊ฐ„ ๋ถ„์„์—๋Š” ์ข‹์€ ์˜ต์…˜์ด์ง€๋งŒ ๊ธฐ๋ก ๋ถ„์„์—๋Š” ๊ทธ๋‹ค์ง€ ํŽธ๋ฆฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ๋ชจ๋“  ๋ฆฌ์†Œ์Šค์—์„œ๋Š” nginx ๋กœ๊ทธ์˜ ๋ฐ์ดํ„ฐ ์–‘์ด ๋น ๋ฅด๊ฒŒ ์ฆ๊ฐ€ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ๋งŽ์€ ์–‘์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•˜๋ ค๋ฉด ์ข€ ๋” ์ „๋ฌธ์ ์ธ ๊ฒƒ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋…ผ๋ฆฌ์ ์ž…๋‹ˆ๋‹ค.

์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•˜๋Š”์ง€ ์•Œ๋ ค๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค. ์•„ํ…Œ๋‚˜ Nginx๋ฅผ ์˜ˆ๋กœ ๋“ค์–ด ๋กœ๊ทธ๋ฅผ ๋ถ„์„ํ•˜๊ณ , ์˜คํ”ˆ ์†Œ์Šค Cube.js ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด ๋ฐ์ดํ„ฐ์—์„œ ๋ถ„์„ ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค. ์ „์ฒด ์†”๋ฃจ์…˜ ์•„ํ‚คํ…์ฒ˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

Amazon Athena ๋ฐ Cube.js๋ฅผ ์‚ฌ์šฉํ•œ Nginx ๋กœ๊ทธ ๋ถ„์„

TL:DR;
์™„์„ฑ๋œ ๋Œ€์‹œ๋ณด๋“œ์— ๋Œ€ํ•œ ๋งํฌ.

์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ์ •๋ณด๋ฅผ ์ˆ˜์ง‘ํ•˜๊ธฐ ์œ„ํ•ด ์œ ์ฐฝํ•จ, ์ฒ˜๋ฆฌ์šฉ - AWS Kinesis ๋ฐ์ดํ„ฐ ํŒŒ์ด์–ดํ˜ธ์Šค ะธ AWS ์ ‘์ฐฉ์ œ, ์ €์žฅ์šฉ - AWS S3. ์ด ๋ฒˆ๋“ค์„ ์‚ฌ์šฉํ•˜๋ฉด nginx ๋กœ๊ทธ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๊ธฐํƒ€ ์ด๋ฒคํŠธ ๋ฐ ๊ธฐํƒ€ ์„œ๋น„์Šค์˜ ๋กœ๊ทธ๋„ ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ๋ถ€ ๋ถ€ํ’ˆ์„ ์Šคํƒ๊ณผ ์œ ์‚ฌํ•œ ๋ถ€ํ’ˆ์œผ๋กœ ๊ต์ฒดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด nginx์—์„œ ์ง์ ‘ kinesis์— ๋กœ๊ทธ๋ฅผ ์ž‘์„ฑํ•˜๊ฑฐ๋‚˜ fluentd๋ฅผ ์šฐํšŒํ•˜๊ฑฐ๋‚˜ logstash๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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"์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

Amazon Athena ๋ฐ Cube.js๋ฅผ ์‚ฌ์šฉํ•œ Nginx ๋กœ๊ทธ ๋ถ„์„

๋‹ค์Œ ํƒญ์—์„œ "๋ ˆ์ฝ”๋“œ ํ˜•์‹ ๋ณ€ํ™˜" - "ํ™œ์„ฑํ™”"๋ฅผ ์„ ํƒํ•˜๊ณ  ๋…น์Œ ํ˜•์‹์œผ๋กœ "Apache ORC"๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ถ€ ์—ฐ๊ตฌ์— ๋”ฐ๋ฅด๋ฉด ์˜ค์›ฌ ์˜ค๋ง๋ฆฌ, ์ด๋Š” PrestoDB ๋ฐ Athena์— ๊ฐ€์žฅ ์ ํ•ฉํ•œ ํ˜•์‹์ž…๋‹ˆ๋‹ค. ์œ„์—์„œ ๋งŒ๋“  ํ…Œ์ด๋ธ”์„ ์Šคํ‚ค๋งˆ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. Kinesis์—์„œ๋Š” S3 ์œ„์น˜๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ํ…Œ์ด๋ธ”์—์„œ๋Š” ์Šคํ‚ค๋งˆ๋งŒ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋‹ค๋ฅธ S3 ์œ„์น˜๋ฅผ ์ง€์ •ํ•˜๋ฉด ์ด ํ…Œ์ด๋ธ”์—์„œ ํ•ด๋‹น ๋ ˆ์ฝ”๋“œ๋ฅผ ์ฝ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

Amazon Athena ๋ฐ Cube.js๋ฅผ ์‚ฌ์šฉํ•œ Nginx ๋กœ๊ทธ ๋ถ„์„

์Šคํ† ๋ฆฌ์ง€๋กœ S3๋ฅผ ์„ ํƒํ•˜๊ณ  ์•ž์„œ ์ƒ์„ฑํ•œ ๋ฒ„ํ‚ท์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. ๋‚˜์ค‘์— ์„ค๋ช…ํ•  Aws Glue Crawler๋Š” S3 ๋ฒ„ํ‚ท์˜ ์ ‘๋‘์‚ฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ๋น„์›Œ ๋‘๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

Amazon Athena ๋ฐ Cube.js๋ฅผ ์‚ฌ์šฉํ•œ Nginx ๋กœ๊ทธ ๋ถ„์„

๋‚˜๋จธ์ง€ ์˜ต์…˜์€ ๋ถ€ํ•˜์— ๋”ฐ๋ผ ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ๋Š”๋ฐ ์ €๋Š” ์ฃผ๋กœ ๊ธฐ๋ณธ ์˜ต์…˜์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. S3 ์••์ถ•์€ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์ง€๋งŒ ORC๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๊ธฐ๋ณธ ์••์ถ•์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์œ ์ฐฝํ•จ

์ด์ œ ๋กœ๊ทธ ์ €์žฅ ๋ฐ ์ˆ˜์‹ ์„ ๊ตฌ์„ฑํ–ˆ์œผ๋ฏ€๋กœ ์ „์†ก์„ ๊ตฌ์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค ์œ ์ฐฝํ•จ, ์ €๋Š” Ruby๋ฅผ ์ข‹์•„ํ•˜๊ธฐ ๋•Œ๋ฌธ์— Logstash๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ๋กœ๊ทธ๋ฅผ Kinesis๋กœ ์ง์ ‘ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Fluentd ์„œ๋ฒ„๋Š” ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์œผ๋กœ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ๊ฐ„๋‹จํ•˜๊ณ  ํŽธ๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— docker์— ๋Œ€ํ•ด ๋ง์”€๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

๋จผ์ € 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์— ๋กœ๊ทธ๋ฅผ ๋ณด๋ƒ…๋‹ˆ๋‹ค. Kinesis Firehose ํ”Œ๋Ÿฌ๊ทธ์ธ:

<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์‹œ ์‚ฌ์ด์— ์ˆ˜์‹ ๋œ ๋ชจ๋“  ๋ ˆ์ฝ”๋“œ๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํŒŒํ‹ฐ์…˜๋˜์ง€ ์•Š์€ ํ…Œ์ด๋ธ”์—์„œ ์ฝ๋Š” ๊ฒƒ๋ณด๋‹ค ์ด๊ฒƒ์ด ์–ผ๋งˆ๋‚˜ ๋” ํšจ์œจ์ ์ผ๊นŒ์š”? ํƒ€์ž„์Šคํƒฌํ”„๋กœ ํ•„ํ„ฐ๋งํ•˜์—ฌ ๋™์ผํ•œ ๋ ˆ์ฝ”๋“œ๋ฅผ ์ฐพ์•„ ์„ ํƒํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

Amazon Athena ๋ฐ Cube.js๋ฅผ ์‚ฌ์šฉํ•œ Nginx ๋กœ๊ทธ ๋ถ„์„

๋‹จ ์ผ์ฃผ์ผ ๊ฐ„์˜ ๋กœ๊ทธ๊ฐ€ ํฌํ•จ๋œ ๋ฐ์ดํ„ฐ ์„ธํŠธ์— 3.59์ดˆ ๋ฐ 244.34MB์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ํŒŒํ‹ฐ์…˜๋ณ„๋กœ ํ•„ํ„ฐ๋งํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

Amazon Athena ๋ฐ Cube.js๋ฅผ ์‚ฌ์šฉํ•œ Nginx ๋กœ๊ทธ ๋ถ„์„

์กฐ๊ธˆ ๋” ๋น ๋ฅด์ง€๋งŒ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฒƒ์€ ๋ฐ์ดํ„ฐ๊ฐ€ 1.23MB์— ๋ถˆ๊ณผํ•˜๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค! ๊ฐ€๊ฒฉ์ด ์š”์ฒญ๋‹น ์ตœ์†Œ 10MB๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด ํ›จ์”ฌ ์ €๋ ดํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์—ฌ์ „ํžˆ ํ›จ์”ฌ ๋” ์ข‹์œผ๋ฉฐ ๋Œ€๊ทœ๋ชจ ๋ฐ์ดํ„ฐ ์„ธํŠธ์—์„œ๋Š” ๊ทธ ์ฐจ์ด๊ฐ€ ํ›จ์”ฌ ๋” ์ธ์ƒ์ ์ž…๋‹ˆ๋‹ค.

Cube.js๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋Œ€์‹œ๋ณด๋“œ ๊ตฌ์ถ•

๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ตฌ์„ฑํ•˜๊ธฐ ์œ„ํ•ด Cube.js ๋ถ„์„ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ฝค ๋งŽ์€ ๊ธฐ๋Šฅ์ด ์žˆ์ง€๋งŒ ์šฐ๋ฆฌ๋Š” ๋‘ ๊ฐ€์ง€, ์ฆ‰ ํŒŒํ‹ฐ์…˜ ํ•„ํ„ฐ๋ฅผ ์ž๋™์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋Šฅ๊ณผ ๋ฐ์ดํ„ฐ ์‚ฌ์ „ ์ง‘๊ณ„์— ๊ด€์‹ฌ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ, SQL์„ ์ƒ์„ฑํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด Javascript๋กœ ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ์—์„œ ํŒŒํ‹ฐ์…˜ ํ•„ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๋งŒ ๋‚˜ํƒ€๋‚ด๋ฉด ๋ฉ๋‹ˆ๋‹ค.

์ƒˆ๋กœ์šด 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`
    }
  }
});

์—ฌ๊ธฐ์„œ๋Š” ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. FILTER_PARAMSํŒŒํ‹ฐ์…˜ ํ•„ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ SQL ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

๋˜ํ•œ ๋Œ€์‹œ๋ณด๋“œ์— ํ‘œ์‹œํ•˜๋ ค๋Š” ์ธก์ •ํ•ญ๋ชฉ๊ณผ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜๊ณ  ์‚ฌ์ „ ์ง‘๊ณ„๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. 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 ๋ฐฑ์—”๋“œ๋Š” ๋‹ค์Œ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. REST API ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ํ”„๋ŸฐํŠธ์—”๋“œ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์œ„ํ•œ ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ธํŠธ์ž…๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ํด๋ผ์ด์–ธํŠธ์˜ React ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜์—ฌ ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ตฌ์ถ•ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. Cube.js๋Š” ๋ฐ์ดํ„ฐ๋งŒ ์ œ๊ณตํ•˜๋ฏ€๋กœ ์‹œ๊ฐํ™” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๋งˆ์Œ์— ๋“ญ๋‹ˆ๋‹ค. ๋‹ค์‹œ ์ฐจํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋‹ค, ๊ทธ๋Ÿฌ๋‚˜ ๋ฌด์—‡์ด๋“  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Cube.js ์„œ๋ฒ„๋Š” ๋‹ค์Œ์˜ ์š”์ฒญ์„ ์ˆ˜๋ฝํ•ฉ๋‹ˆ๋‹ค. JSON ํ˜•์‹, ํ•„์ˆ˜ ์ธก์ •ํ•ญ๋ชฉ์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด Nginx๊ฐ€ ์ผ๋ณ„ ์˜ค๋ฅ˜ ์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•˜๋ ค๋ฉด ๋‹ค์Œ ์š”์ฒญ์„ ๋ณด๋‚ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

{
  "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

์ฝ”๋ฉ˜ํŠธ๋ฅผ ์ถ”๊ฐ€