๐Ÿ”

Forecasting based on time-series data

Journey to find abnormal activities from logging data with periodic patterns

Background

Monitoring and configuring alerts for a new Single Sign-On service was done easily, but traditional metrics to find abnormal activities remains tough.
โ€ข
Facebook Prophetย ์‹œ๊ณ„์—ด ์˜ˆ์ธก ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉ
โ€ข
์‹ ๊ทœ SSO ์„ธ์…˜ ๋ชจ๋‹ˆํ„ฐ๋ง์„ ์œ„ํ•ด ์ˆ˜์ง‘ํ•œ ์„œ๋น„์Šค๋ณ„ ์‹œ๊ณ„์—ด ์„ธ์…˜ ์นด์šดํŠธ ํ™œ์šฉ
โ€ข
3์‹œ๊ฐ„ ์˜ˆ์ธก ๋ฐ์ดํ„ฐ ์ƒ์„ฑ: 1์‹œ๊ฐ„๋งˆ๋‹ค ์‹คํ–‰์‹œ์  28์ผ๊ฐ„ ์ด์ „ ๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜์œผ๋กœ ์ถ”์ถœ
โ€ข
์ œ์ผ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ๊ทธ๋ฃน์›จ์–ด ์„ธ์…˜๋งŒ ์˜ˆ์ธก ์ ์šฉ
โ€ข
๊ธฐ์กด Linear ํ•œ ์ ˆ๋Œ€๊ฐ’์œผ๋กœ๋งŒ ์•Œ๋ฆผ์„ ์„ค์ •ํ–ˆ๋‹ค๋ฉด, ์˜ˆ์ธก์„ ํ†ตํ•œ ๊ฐ€๋ณ€์ ์ธ ์•Œ๋ฆผ ์„ค์ •์ด ๊ฐ€๋Šฅ
๊ธฐ์กด
์˜ˆ์ธก
๊ฐ€์น˜
์•กํ‹ฐ๋ธŒ ์„ธ์…˜
Variable (์˜ˆ์ธก ๋ฒ”์œ„์— ๋ฒ—์–ด๋‚˜๋Š” ๊ฒฝ์šฐ ์•Œ๋ฆผ)
ํŠน์ • ์ง€์ ์„ ์ง€์ •ํ•˜์—ฌ ๋…ธํ‹ฐ๋ฅผ ๋ฐ›๊ธฐ ๋ณด๋‹ค, ์ถ”์ด์— ๋งž๋Š” ์•Œ๋ฆผ์„ ์„ค์ •ํ•˜์—ฌ ์˜คํƒ์„ ์ค„์ž„
๊ณต์œ ์ผ
์ œ์™ธ ๊ฐ€๋Šฅ
ํŒจํ„ด ์ค‘ ๊ฐ€์ค‘์น˜ ๋ฐ ๊ฐ€๋ณ€ ์š”์†Œ๋ฅผ ํ™œ์šฉ ํ•  ์ˆ˜ ์žˆ์Œ

Data Collection

๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ์ค€๋น„

โ€ข
SSO ์„ธ์…˜์„ 1๊ฐœ๋งŒ ์ ‘์†ํ•˜์—ฌ Refresh Token ์œผ๋กœ ์ตœ๋Œ€ํ•œ ์žฌ์‚ฌ์šฉํ•˜๊ณ  ์ƒˆ๋กœ์šด ์„ธ์…˜์„ ๋ฐ›์•„์˜ค๋Š” ๋กœ์ง
#!/bin/bash REFRESH_TOKEN=`cat /var/opt/aerobase/dashboard/refresh_token` RESULT=`curl --silent --location --request POST 'https://xxxx.co.kr/auth/realms/xxxx/protocol/openid-connect/token' --header 'Content-Type: application/x-www-form-urlencoded' --data-urlencode 'grant_type=refresh_token' --data-urlencode 'client_id=view' --data-urlencode 'client_secret=************************' --data-urlencode 'refresh_token='$REFRESH_TOKEN''` if echo ${RESULT} | grep -i "error"; then echo "refresh failed, create a new session" RESULT=`curl --silent --location --request POST 'https://xxxx.co.kr/auth/realms/xxxx/protocol/openid-connect/token' --header 'Content-Type: application/x-www-form-urlencoded' --data-urlencode 'grant_type=client_credentials' --data-urlencode 'client_id=view' --data-urlencode 'client_secret=************************'` fi echo ${RESULT} | jq '.access_token' | sed -e 's/\"//g' > /var/opt/aerobase/dashboard/token echo ${RESULT} | jq '.refresh_token' | sed -e 's/\"//g' > /var/opt/aerobase/dashboard/refresh_token
Bash
๋ณต์‚ฌ

๋ฐ์ดํ„ฐ ์ถ”์ถœ, ์„ ์ฒ˜๋ฆฌ, InfluxDB ๋กœ ์ €์žฅ

#!/bin/bash DB_NAME='app' TABLE_NAME='sso-session' TOKEN=`cat /var/opt/aerobase/dashboard/token` # SSO API ์„ธ์…˜์ •๋ณด ์š”์ฒญ curl --silent --location --request GET 'https://xxxx.co.kr/auth/admin/realms/xxxx/client-session-stats' --header 'Authorization: Bearer '$TOKEN'' > /var/opt/aerobase/dashboard/result # ์ˆ˜์‹  ๋ฐ์ดํ„ฐ์—์„œ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ์ถ”์ถœํ•˜์—ฌ ์„ ์ฒ˜๋ฆฌ RESULT=`cat /var/opt/aerobase/dashboard/result | jq '.[] | {(.clientId):.active}'` DATA=`echo $RESULT | sed -e 's/\:/\=/g' | sed -e 's/{\| //g' | sed -e 's/\"//g' | sed -e 's/}/,/g' | sed -e 's/.$//'` # InfluxDB ๋กœ ์ €์žฅ ์š”์ฒญ curl -s -i -XPOST http://xxxx:8086/write?db=${DB_NAME} --data-binary "${TABLE_NAME} ${DATA}" | grep 'HTTP/1.1 204' > /dev/null 2>&1
Bash
๋ณต์‚ฌ

์˜ˆ์ œ) SSO API ํ˜ธ์ถœ ํ›„ ์ˆ˜์‹ ๋ฐ›์€ ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ

[{"offline":"0","clientId":"idm-client","active":"1","id":"2690c992-bb59-4891-8220-62f86d7a6d33"},{"offline":"0","clientId":"data","active":"28","id":"77cbc073-c6c4-4fd1-92f3-3e2b44ef7912"},{"offline":"0","clientId":"portal","active":"418","id":"9d7f3882-f9be-4584-b16d-1c6c573d7a9d"},{"offline":"0","clientId":"iam-admin","active":"22","id":"5c2edf26-60e7-4ffc-b7d6-4a9a7e4cea82"},{"offline":"0","clientId":"iam","active":"4","id":"309c8cfc-e49e-4303-964a-2e0dfe79e566"}]
Bash
๋ณต์‚ฌ

Facebook Prophet Docker ํ™˜๊ฒฝ ์ค€๋น„

Dockerfile

FROM lppier/docker-prophet RUN pip install -U pip influxdb tox workalendar ENTRYPOINT ["python"]
Bash
๋ณต์‚ฌ

Docker image ์ƒ์„ฑ

[server ~]$ sudo docker build -t prophet-influxdb-phil:202005081129 . ... [server ~]$ sudo docker images REPOSITORY TAG IMAGE ID CREATED SIZE prophet-influxdb-phil 202005081129 55794cdad742 7 seconds ago 1.03GB lppier/docker-prophet latest 2a09f62db268 15 months ago 1.01GB
Bash
๋ณต์‚ฌ

์˜ˆ์ธก ๋ฐ์ดํ„ฐ ์ƒ์„ฑ

Prophet - InfluxDB ์—ฐ๋™ํ•˜์—ฌ Forecast ์ƒ์„ฑํ•˜๋Š” ์†Œ์Šค์ฝ”๋“œ

class InfluxDataFrameIO(DataframeIO): def read(self, days: int = 7): r = self.client.query(""" SELECT last(portal) as y FROM "{:1}" WHERE time >= now() - {:2}d GROUP BY time(1m) fill(null)""".format(self.influxdb_input_measurement, days)) df = pd.DataFrame(r.get(self.influxdb_input_measurement), columns=['y']) df = df.tz_convert(None) df.reset_index(inplace=True) df.rename(inplace=True, columns={'index':'ds'}) return df[:-1] def write(self, outputDataFrame: pd.DataFrame): self.client.write_points( outputDataFrame, measurement = self.influxdb_output_measurement, tags=self.influxdb_output_tags, time_precision='m' ) def forecast(df: pd.DataFrame): m = Prophet( holidays=get_holidays(), holidays_prior_scale=100, interval_width=0.99, daily_seasonality=10, weekly_seasonality=20, ) m.fit(df) # https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html future = m.make_future_dataframe( periods=180, freq='1T' # Minute ) forecast = m.predict(future) out = forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']] o = out.set_index('ds') return o if __name__ == "__main__": io = InfluxDataFrameIO( client=DataFrameClient( host='influxdb', port=8086, username='****', password='****', database='****' ), influxdb_input_measurement='sso-session', influxdb_output_measurement='sso-session', influxdb_output_tags = { 'forecast': 'sso-prophet-20200508' } ) # 28์ผ ๋™์•ˆ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์˜ˆ์ธก ๋ฐ์ดํ„ฐ ์ƒ์„ฑ inputDataFrame = io.read(days=28) outputDataFrame = forecast(inputDataFrame) # ์ƒ์„ฑ๋œ ์˜ˆ์ธก ๋ฐ์ดํ„ฐ๋ฅผ InfluxDB ์— ์ €์žฅ io.write(outputDataFrame)
Python
๋ณต์‚ฌ

์‹คํ–‰

[server ~]$ time sudo docker run -it --rm -v $(pwd):/app prophet-influxdb-phil:202005081129 ssoSessionForecast.py INFO:fbprophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this. Initial log joint probability = -460.372 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 99 38790.9 0.0447242 3752.01 0.8642 0.8642 111 ... ... Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 3720 41037.3 8.66095e-05 361.545 5.573e-07 0.001 4276 LS failed, Hessian reset 3778 41037.4 2.00873e-06 65.3976 0.8632 0.8632 4344 Optimization terminated normally: Convergence detected: relative gradient magnitude is below tolerance real 5m39.738s user 0m0.052s sys 0m0.071s
Bash
๋ณต์‚ฌ

๋ฐ์ดํ„ฐ ๊ฐ€๋ณ€

๊ณตํœด์ผ ๋ฐ ํšŒ์‚ฌ ํœด์ผ ์ ์šฉ

For instance, if you wanted to include Christmas Eve in addition to Christmas youโ€™d includeย lower_window=-1,upper_window=0.If you wanted to use Black Friday in addition to Thanksgiving, youโ€™d includeย lower_window=0,upper_window=1.You can also include a columnย prior_scaleย to set the prior scale separately for each holiday, as described below.
ํฌ๋ฆฌ์Šค๋งˆ์Šค ์ด๋ธŒ๋ฅผ ํฌ๋ฆฌ์Šค๋งˆ์Šค ๊ณตํœด์ผ๋กœ ์ธ์‹ํ•˜๊ฒŒ ํ•˜๊ณ  ์‹ถ์œผ๋ฉด, -1์„ ์ ์šฉํ•จ
๋ธ”๋ž™ ํ”„๋ผ์ด๋ฐ์ด๋ฅผ ์ถ”์ˆ˜๊ฐ์‚ฌ์ ˆ๋กœ ์ธ์‹ํ•˜๊ณ  ์‹ถ์œผ๋ฉด upper_window ๋ฅผ 1 ์ ์šฉํ•จ (์ถ”์ˆ˜๊ฐ์‚ฌ์ ˆ์€ ๋งค๋ฒˆ ๋ชฉ์š”์ผ์ด์—ฌ์„œ ๋ธ”๋ž™ ํ”„๋ ˆ์ด๋ฐ์ด๋Š” ๋‹ค์Œ๋‚  ๊ธˆ์š”์ผ)
๊ณตํœด์ผ ๋‹น์ผ๋งŒ ์ธ์‹ํ•˜๊ฒŒ ํ•˜๊ณ  ์‹ถ์œผ๋ฉด, lower_window, upper_window ๋ฅผ ์ž…๋ ฅํ•˜์ง€ ์•Š์•„๋„ ๋จ, ์•„๋‹ˆ๋ฉด ๋‘˜๋‹ค 0์œผ๋กœ ์ž…๋ ฅ(๋‹น์ผ)
์ถ”ํ›„ ํšŒ์‚ฌ ๊ณตํœด์ผ์„ ์ˆ˜๋™ ์ž…๋ ฅ์ด ์•„๋‹ˆ๋ผ ๋ณ„๋„ ํ…Œ์ด๋ธ”์— ์ €์žฅํ•˜์—ฌ ๊ฐ€์ ธ์˜ค๋„๋ก ์„ค์ •ํ•ด์•ผํ•จ
def get_holidays(): from workalendar.asia import SouthKorea cal = SouthKorea() y = pd.DataFrame(cal.holidays(2020), columns=['ds', 'holiday']) # y.insert(2, 'lower_window', 0) # y.insert(3, 'upper_window', 1) gsshop = pd.DataFrame({ 'ds': pd.to_datetime(['2020-05-01']), 'holiday': 'company', # 'lower_window': 0, # ์ž…๋ ฅํ•œ ๋‹น์ผ๋งŒ ๊ณตํœด์ผ๋กœ ์ œ์™ธํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ ํ•„์š” ์—†์Œ # 'upper_window': 1, # ์ž…๋ ฅํ•œ ๋‹น์ผ๋งŒ ๊ณตํœด์ผ๋กœ ์ œ์™ธํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ ํ•„์š” ์—†์Œ }) holiday = pd.concat((y, gsshop)) # ๊ณตํœด์ผ ๋ฐ์ดํ„ฐ์— ํฌํ•จ return holiday
Python
๋ณต์‚ฌ

๊ณตํœด์ผ ๋ฐ์ดํ„ฐ ๊ฐ€์ค‘์น˜ ๋†’์ž„

โ€ข
ํƒ€์ž„์‹œ๋ฆฌ์ฆˆ ๋‹จ์œ„:ย https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html
โ€ข
180๋ถ„ (3์‹œ๊ฐ„)์˜ ์˜ˆ์ธก ๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จํ•˜์—ฌ InfluxDB ์— ์ž…๋ ฅ
def forecast(df: pd.DataFrame): """ input: ์ •์ œ๋œ ์‹ค์ธก ๋ฐ์ดํ„ฐ Dataframe output: ์˜ˆ์ธก๋œ Dataframe """ m = Prophet( holidays=get_holidays(), holidays_prior_scale=100, # 0.99 ์ด์—ˆ๋Š”๋ฐ 100์œผ๋กœ ์˜ฌ๋ฆผ interval_width=0.99, daily_seasonality=10, # ์ฝ”๋ฉ˜ํŠธ ๋˜์–ด ์žˆ์—ˆ๋Š”๋ฐ ์ถ”๊ฐ€ํ•จ weekly_seasonality=20, # ์ฝ”๋ฉ˜ํŠธ ๋˜์–ด ์žˆ์—ˆ๋Š”๋ฐ ์ถ”๊ฐ€ํ•จ ) m.fit(df) future = m.make_future_dataframe( periods=180, freq='1T' ) forecast = m.predict(future) out = forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']] o = out.set_index('ds') return o
Python
๋ณต์‚ฌ

Grafana ์—ฐ๋™

๊ธฐ์กด ๋ฐ์ดํ„ฐ ๊ทธ๋ž˜ํ”„์— ์˜ˆ์ธก ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€

1.
yhat_lower (์˜ˆ์ธก ๋ฒ”์œ„ ํ•˜์œ„๊ฐ’), yhat_upper (์˜ˆ์ธก ๋ฒ”์œ„ ์ƒ์œ„๊ฐ’), yhat (์˜ˆ์ธก ์ค‘์œ„๊ฐ’)
2.
ํ™ˆ๋„ท ์„ธ์…˜์ˆ˜๊ฐ€ yhat_lower (์˜ˆ์ธก ๋ฒ”์œ„ ํ•˜์œ„๊ฐ’๋ณด๋‹ค ์ž‘์€ ๊ฒฝ์šฐ์—๋งŒ ๋ฐ์ดํ„ฐ ๋ณด์—ฌ์คŒ)ย โ†’ ์•Œ๋ฆผ์„ ์œ„ํ•œ ๊ทธ๋ž˜ํ”„
3.
InfluxDB 1.4 ์‚ฌ์šฉ์ค‘ (abs ๊ฐ™์€ function ์€ ์‚ฌ์šฉ ์•ˆ๋จ)

Visualization ์„ค์ •

โ€ข
์˜ˆ์ธก ์ค‘์œ„๊ฐ’์€ ์•ˆ๋ณด์—ฌ์คŒ
โ€ข
์˜ˆ์ธก ์ƒํ•˜์œ„ ๋ฒ”์œ„๋Š” ์„ ์ด ์•„๋‹Œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์— ๋‹ค๋ฅธ ์ƒ‰์œผ๋กœ ํ‘œํ˜„, ๋ ˆ์ „๋“œ์—์„œ๋Š” ๋ณด์—ฌ์ฃผ์ง€ ์•Š์Œ
โ€ข
์•Œ๋ฆผ์„ ์œ„ํ•œ ๊ทธ๋ž˜ํ”„๋Š” ์•ˆ๋ณด์—ฌ์คŒ

์˜ˆ์ธก ์ ์šฉ ํ™”๋ฉด

์•Œ๋ฆผ ์„ค์ •

โ€ข
๊ทธ๋ฃน์›จ์–ด ์„ธ์…˜์ˆ˜๊ฐ€ yhat_lower (์˜ˆ์ธก ๋ฒ”์œ„ ํ•˜์œ„๊ฐ’) ๋ณด๋‹ค ์ž‘์€ ๊ฒฝ์šฐ์— ์•Œ๋ฆผ
โ€ข
๊ทธ๋ฃน์›จ์–ด ์„ธ์…˜์ˆ˜๊ฐ€ 2000 ์ด์ƒ์ธ ๊ฒฝ์šฐ ์•Œ๋ฆผ (์‚ฌ์› 1000๋ช… ๊ธฐ์ค€)
โ€ข
ํ•ด๋‹น ์„ค์ •์ด 5๋ถ„๋™์•ˆ ์œ ์ง€๋˜๋Š” ๊ฒฝ์šฐ ์•Œ๋ฆผ

Facebook Prophet ์˜ ํ•œ๊ณ„

๋šœ๋ ทํ•œ ์‚ฌ์šฉ ๋ชฉ์  ๋ฐ ์ ์šฉ ๋Œ€์ƒ

โ€ข
์‹œ๊ณ„์—ด ๋ฐ์ดํ„ฐ (์‹œ๊ฐ„์˜ ํ๋ฆ„): ์‹œ๊ฐ„์˜ ํ๋ฆ„์— ๋”ฐ๋ผ ๊ฐ•ํ•œ ์‹œ์ฆŒ ํŠน์ง•์„ ๊ฐ–๊ณ  ์žˆ๋Š” ๊ณณ์— ์ ์šฉ ๊ฐ€๋Šฅ
โ€ข
๊ฐ•ํ•œ ์‹œ์ฆŒ ํŠน์ง•: ์‹œ๊ฐ„์— ๋”ฐ๋ฅธ ๋™์ผํ•œ ํŒจํ„ด์„ ๊ฐ–๋Š” ๋ฐ์ดํ„ฐย โ†’ย ์‚ฌ๋‚ด ๊ทธ๋ฃน์›จ์–ด ๋กœ๊ทธ์ธ ํŒจํ„ด, ์ถœ์ž…์ฆ ์ž…๋ ฅ ํŒจํ„ด, ์ถœ๊ทผ ์‹œ๊ฐ„๋Œ€ ํŒจํ„ด
โ€ข
๋„ค์ด๋ฒ„ D2 ๋„ค์ด๋ฒ„ํŽ˜์ด ๊ฒฐ์ œ์‹œ์Šคํ…œ ์˜ˆ์ธก -ย https://d2.naver.com/helloworld/0065813
๊ณผ๊ฑฐ์˜ ์žฅ๊ธฐ์ ์ธ ์ถ”์„ธ๋ฅผ ๋ณด๊ณ  ์ดํ›„์˜ ๋Œ€๋žต์ ์ธ ์ถ”์„ธ๋ฅผ ํ™•์ธํ•˜๋Š” ์šฉ๋„๋กœ ์‚ฌ์šฉํ•  ๋•Œ Prophet ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ํ›Œ๋ฅญํ•œ ๊ฒฐ๊ณผ๋ฌผ์„ ๋ณด์—ฌ ์ค€๋‹ค. ํŠนํžˆ ๊ฐ‘์ž‘์Šค๋Ÿฐ ๋ฐ์ดํ„ฐ์˜ ๋ณ€๋™์ด ์—†๋Š” ์ƒํ™ฉ์—์„œ๋Š” ์‹ค์ œ ๋ฐ์ดํ„ฐ์™€ ์•„์ฃผ ๊ทผ์ ‘ํ•œ ์˜ˆ์ธก ๊ฒฐ๊ณผ๋ฅผ ๋ณด์—ฌ ์ค€๋‹ค.ํ•˜์ง€๋งŒ ๋ช…์ ˆ์ด๋‚˜ ๊ธฐํƒ€ ์ƒํ™ฉ์— ์˜ํ•ด ๊ฑฐ๋ž˜๋Ÿ‰์ด ํ•œ์‹œ์ ์œผ๋กœ ํ•˜๋ฝํ•˜๊ฑฐ๋‚˜ ์ฆ๊ฐ€ํ•˜๋Š” ๊ฒฝ์šฐ, ์‚ฌ์—… ํ™•๋Œ€ ๋“ฑ์œผ๋กœ ์ธํ•ด ์ „๋ฐ˜์ ์ธ ๊ฑฐ๋ž˜๋Ÿ‰ ์ž์ฒด๊ฐ€ ๋ณ€๋™๋˜๋Š” ๊ฒฝ์šฐ ๋“ฑ์—์„œ๋Š” ์ ์ ˆํ•œ ์˜ˆ์ธก ๊ฒฐ๊ณผ๋ฅผ ๋ณด์—ฌ ์ฃผ์ง€ ๋ชปํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค. ๊ณผ๊ฑฐ์˜ ์ถ”์„ธ๋ฅผ ๋”ฐ๋ผ ์ฃผ๊ธฐ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค๊ณ  ์ดํ›„์˜ ๊ฑฐ๋ž˜๋ฅผ ์˜ˆ์ธกํ•˜๋Š” ๋ชจ๋ธ์˜ ํ•œ๊ณ„ ๋•Œ๋ฌธ์ด๋‹ค.

DB Active Session๊ณผ ํŠน์ • ์ƒํ’ˆ์˜ ํŒ๋งค ์ถ”์ด๋ฅผ ๊ด€๋ จ์‹œ์ผœ ์˜ˆ์ธกํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ๊ฐ€๋Šฅํ•œ๊ฐ€?

๊ณ ๋ ค๋˜์–ด์•ผ ํ•  Factor๊ฐ€ ๋„ˆ๋ฌด ๋งŽ๋‹ค!ย โ†’ย ๋ฐ์ดํ„ฐ ๊ณผํ•™์ž ๋ฐ AI ์˜ ์˜์—ญ
โ€ข
์š”์ผ: ์›”์š”์ผ์— ์ž˜ ํŒ”๋ฆฌ๋˜ ์ œํ’ˆ์ด ์ผ์š”์ผ์—๋„ ์ž˜ ํŒ”๋ฆฌ๋‚˜?
โ€ข
์‹œ๊ฐ„: ์ €๋… 8์‹œ์— ์ž˜ ํŒ”๋ฆฌ๋˜ ์ œํ’ˆ์ด ์˜ค์ „ 8์‹œ์—๋„ ์ž˜ ํŒ”๋ฆฌ๋‚˜?
โ€ข
ํœด์ผ: ํ‰์ผ์— ์ž˜ ํŒ”๋ฆฌ๋˜ ์ œํ’ˆ์ด ํœด์ผ์—๋„ ์ž˜ ํŒ”๋ฆฌ๋‚˜?
โ€ข
์™ธ๋ถ€ ์š”์ธ: ๋‚ ์”จ, ๊ณ„์ ˆ, ์‚ฌํšŒ์  ์ด์Šˆ(๋งˆ์Šคํฌ)์— ๋”ฐ๋ผ ์ž˜ ํŒ”๋ฆฌ๊ณ  ๋œ ํŒ”๋ฆฌ๋Š”๋ฐ ์–ด๋–ป๊ฒŒ ๊ฐ€์ค‘์น˜๋ฅผ ๋‘˜ ๊ฒƒ ์ธ๊ฐ€?

DB Active Session๊ณผ Plain ์ƒํ’ˆ ํŒ๋งค ์ถ”์ด๋งŒ ์กฐํ•ฉํ•˜์—ฌ ์˜ˆ์ธก ๋ชจ๋ธ์„ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ์˜๋ฏธ๊ฐ€ ์žˆ๋Š” ๊ฒƒ์ผ๊นŒ?

๊ณ ๋ ค๋˜๋Š” Factor (์‚ฌ์šฉ๊ฐ€๋Šฅ)
โ€ข
์š”์ผ
โ€ข
์‹œ๊ฐ„
โ€ข
ํœด์ผ
Active Session ์นด์šดํŠธ์™€ ์ฃผ๋ฌธ ์ˆ˜๋Ÿ‰์„ ์–ด๋–ป๊ฒŒ ์กฐํ•ฉํ•ด์„œ ์˜ˆ์ธก ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•  ์ง€ ์ƒ๊ฐํ•ด๋ณด์ž (Prophet ์œผ๋กœ ๊ฐ€๋Šฅํ•œ๊ฐ€?)

PoC

๊ทธ๋ ‡๋‹ค๋ฉด ์šฐ์„  DB Active Session ๋งŒ ๊ฐ–๊ณ  ์˜ˆ์ธก์„ ํ•ด ๋ดค์„๋•Œ ์–ด๋Š ์ •๋„ ๋งž๋Š”์ง€๋ฅผ ์‚ดํŽด๋ณด์ž

โ€ข
DB Active Session ์นด์šดํŠธ์ˆ˜๋งŒ์œผ๋กœ ์˜ˆ์ธก ๋ฐ์ดํ„ฐ๋ฅผ ๋Œ€์ž…ํ•ด๋ณด๊ณ  ์–ผ์ถ” ๋งž๋Š”์ง€ PoC ํ•ด๋ณด์žย 
โ€ข
DB Active ์‹œ๊ณ„์—ด ๋ฐ์ดํ„ฐ๋ฅผ InfluxDB ์— ์ˆ˜์ง‘ํ•œ๋‹ค
โ€ข
์™œ? ๊ทธ๋ž˜๋„ Static ํ•œ ์•Œ๋ฆผ ํ•œ๊ณ„์น˜๋ฅผ ์„ค์ •ํ•ด์„œ ํ•˜๋Š” ๊ฒƒ ๋ณด๋‹ค ์ข€ ๋” Flexible ํ•œ ์•Œ๋ฆผ ์ˆ˜์น˜๋ฅผ ์ œ์‹œํ•ด ์ค„ ์ˆ˜ ๋„ ์žˆ๋‹ค.

WebDB Active User Session Count ์ˆ˜์ง‘ ๋ฐ ์˜ˆ์ธก

์ค‘์œ„๊ฐ’์„ ๋ฐ”ํƒ•์œผ๋กœ ๊ณตํœด์ผ ๋ฐ์ดํ„ฐ ์ž…๋ ฅํ•˜์—ฌ ์ ์šฉํ•œ ๊ฒฐ๊ณผ

โ€ข
๊ธฐ์กด ์„ธ์…˜์ˆ˜๊ฐ€ ๋‚ฎ์€ ๊ฒฝ์šฐ ์•Œ๋ฆผ์„ ๋ฐ›๋Š” ์„ค์ •์€ ์—†๋‹ค๊ณ  ํ•จ
โ€ข
์„ธ์…˜์ˆ˜๋ฅผ 120์ •๋„๊ฐ€ ๋„˜๋Š” ๊ฒฝ์šฐ ์•Œ๋ฆผ์„ ๋ฐ›๊ณ  ์žˆ๋‹ค๊ณ  ํ•จ
โ€ข
๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚˜๋Š” ์ผ€์ด์Šค๋ฅผ ์‚ดํŽด๋ณด์ž
โ—ฆ
๋งค์ผ ์ƒˆ๋ฒฝ 2์‹œ ๋ฐฐ์น˜ ์ž‘์—…์‹œ ์นด์šดํŠธ ํŠ
โ–ช
Sub-Daily ์˜ต์…˜์„ ํ™œ์šฉํ•ด์„œ Fitting ํ•ด๋ณด์žย 
โ–ช
On/Off Seasonality ๋ฅผ ํ™œ์šฉํ•ด์„œ Fitting ํ•ด๋ณด์ž

๋งค์ผ 02:03~02:06 ์‚ฌ์ด ๋ฐฐ์น˜์ž‘์—…์— ๋ฐ์ดํ„ฐ๋Š” ๊ทธ๋ž˜ํ”„์—์„œ ์•ˆ๋ณด์ด๊ฒŒ ์ฒ˜๋ฆฌ

โ€ข
์ฟผ๋ฆฌ์— ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•œ ์ด์œ : ์˜ˆ์™ธ ์‹œ๊ฐ„์„ ๊ฐ–๊ณ  ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จํ•œ ์‹œ๋ฆฌ์ฆˆ์— ๋ฏธ๋ฆฌ ๋„ฃ๊ณ  Alert ์—์„œ ์กฐ๊ฑด์„ ๊ฑฐ๋Š” ์žฅ์‹์€ ๋‚˜์ค‘์— ์‚ฌ์šฉ ์˜ˆ์ • (๋ณผ ์ˆ˜๊ฐ€ ์—†์œผ๋ฉฐ, ํ•„์š”์‹œ ์ˆ˜์ •ํ•ด์•ผํ•˜๋ฏ€๋กœ)
โ€ข
์ถ”ํ›„์—๋Š” ์—ฌ๋Ÿฌ ์ž‘์—…๋“ค์ด ์žˆ๋Š” ์‹œ๊ฐ„์˜ ๊ฒฝ์šฐ ๋ณ„๋„ ์‹œ๋ฆฌ์ฆˆ๋กœ ๋‹ด๊ณ , ํ•ด๋‹น Alert ์กฐ๊ฑด์— ํ•ด๋‹น ์‹œ๊ฐ„์—๋Š” ์•Œ๋ฆผ ๋ณด๋‚ด์ง€ ์•Š๋„๋ก ์„ค์ • ํ•˜๋ฉด ๊ฐ€๋Šฅ
โ€ข
์• ์ดˆ์— ๋ฐฐ์น˜ ์ž‘์—…์‹œ ์‚ฌ์šฉ๋˜๋Š” ์„ธ์…˜์€ ์ œ์™ธํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด์„ ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธ ํ•„์š”ย โ†’ ์ž‘์—…์ค‘์— ์ž˜๋ชป๋˜๋Š” ๊ฒฝ์šฐ๋„ ๋ด์•ผํ•˜๋ฏ€๋กœ ๋ฐ์ดํ„ฐ ๋ณด์œ ํ•ด์•ผํ•จ
โ€ข
Grafana ์—์„œ๋Š” ํŠน์ • ์‹œ๊ฐ„๋Œ€์— ์•Œ๋ฆผ์„ ์•ˆ๋ณด๋‚ด๋Š” ๋ฐฉ์‹์€ ์š”์ฒญ์ด ๋งŽ์•˜์ง€๋งŒ ์•„์ง ๊ฐœ๋ฐœ๋˜์ง€ ์•Š์Œ
โ€ข
๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ, ํ•ด๋‹น ์‹œ๊ฐ„/๋ถ„์„ ๊ฐ™์€ ํ…Œ์ด๋ธ”์— ์ž…๋ ฅํ•˜๋„๋ก ํ•˜์—ฌ, query ์กฐ๊ฑด์— ์ž…๋ ฅํ•จ
SELECT max("qty") FROM "active_session_webdb" WHERE $timeFilter and (hour != 2 and minute < 6 and minute > 3) GROUP BY time($__interval) fill(linear)
SQL
๋ณต์‚ฌ

Redis ์ •๋ณด ์ˆ˜์ง‘ ๋ฐ ์˜ˆ์ธก ๋ฐ์ดํ„ฐ ์ƒ์„ฑ - Data ๊ธฐ๋ฐ˜ ์žฅ์•  ์ด์ƒ์ง•ํ›„ ๊ฐ์ง€ PoC

โ€ข
ํ˜„์žฌ: Prometheus ๋กœ ์ˆ˜์ง‘๋˜๊ณ  ์žˆ๋Š” ์ž๋ฃŒ๋ฅผ Grafana์— ์—ฐ๋™ํ•˜์—ฌ ๋ชจ๋‹ˆํ„ฐ๋ง ์ง„ํ–‰์ค‘
โ€ข
Redis์˜ Key ์นด์šดํŠธ๊ฐ€ ์ฆ๊ฐ€๋งŒ ํ•˜๊ณ  ์ค„์–ด๋“ค์ง€ ์•Š๋Š” ๊ฒฝ์šฐ ์žฅ์•  ๋ฐœ์ƒ ํ•  ์ˆ˜ ์žˆ์Œ (๋ฉ”๋ชจ๋ฆฌ ์†Œ์ง„)
โ€ข
Key ์นด์šดํŠธ๋ฅผ ๋ณด๋ฉด ํŒจํ„ด์ด ๋ณด์ด๊ธฐ๋Š” ํ•˜์—ฌ ์ ์šฉ ๊ฐ€๋Šฅ์„ฑ์ด ๋ณด์ž„
โ€ข
๊ฐ™์€ Category ์—์„œ๋Š” Load Balancing ์ด ๋˜์–ด ์žˆ๋Š”์ง€ ๊ฑฐ์˜ ์ผ์น˜ํ•˜๋Š” ํŒจํ„ด์„ ๋ณด์ž„ (์ž‘์—…์‹œ์ ์„ ์ œ์™ธํ•˜๋ฉด ๋‹ค๋ฅธ ํŒจํ„ด์„ ๋ณด์ด๋Š” ๊ฒฝ์šฐ ์•Œ๋ฆผ์„ ์ ์šฉ ํ•  ์ˆ˜ ์žˆ์„์ง€๋„...)
โ€ข
Redis ๋ฅผ ๋ฐ”๋ผ๋ณด๋Š” WAS์˜ ๋Œ“์ˆ˜๋ฅผ ์–ผ์ถ” ๋น„์Šทํ•˜๊ฒŒ ๋‚˜๋ˆ ๋†“๊ณ  ์‚ฌ์šฉํ•˜๋Š” ํ‚ค์—ฌ์„œ ๊ฐ™์€ ์นดํ…Œ๊ณ ๋ฆฌ์˜ ์ˆ˜๊ฐ€ ๋‹ฌ๋ผ๋„ ์ด์ƒํ•  ๊ฒƒ์ด ์—†์Œ
โ€ข
Key Count ๋Š” ์ œ์™ธ: MC ๊ฐœ์ธํ™”์˜ ๊ฒฝ์šฐ 0์ด์—ฌ๋„ ์ด์ƒํ•œ ๊ฒƒ์ด ์•„๋‹˜, ๊ทธ๋ƒฅ DB ์šฉ๋„๋กœ ์‚ฌ์šฉํ•˜์—ฌ ์ œ์™ธ

Data ์ €์žฅ ๊ณ„ํš: Prometheusย โ†’ InfluxDB

โ€ข
Redis ์ •๋ณด๋Š” Prometheus ๋กœ ์ €์žฅ๋˜์–ด ์žˆ์Œ
โ€ข
Prometheus ์— ์˜ˆ์ธก ๋ฐ์ดํ„ฐ๋ฅผ ์ž…ํžˆ๋Š” ์ž‘์—…์€ ์ถ”์ฒœ๋˜์ง€ ์•Š์Œ
โ—ฆ
์˜ˆ์ „ ๋ฐ์ดํ„ฐ, ๋ฏธ๋ž˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ์ž˜ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ๋“ค์Œ

Data ์ถ”์ถœ

โ€ข
Prometheus RestAPI ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”์ถœ
โ€ข
Python Pandas ๋ฅผ ํ†ตํ•ด DataFrame ์„ Merge ํ•˜์—ฌ ํ•œ๊ฐœ์˜ InfluxDB์— ์ฃผ์ž…
โ€ข
InfluxDB ์ฃผ์ž…์‹œ Facebook Prophet ์˜ˆ์ธก ๋ฐ์ดํ„ฐ๋„ ํ•จ๊ป˜ ์ถ”๊ฐ€ย โ†’ Prometheus ์™€ InfluxDB ์‹ฑํฌ ํ”„๋กœ์„ธ์Šค๋ฅผ ์†Œ๋Ÿ‰์œผ๋กœ ์ž์ฃผํ•˜๊ณ , ๋ณ„๋„ ํ”„๋กœ์„ธ์Šค๋กœ ์˜ˆ์ธก ๋ฐ์ดํ„ฐ ์ƒ์„ฑ ๋ฐฐ์น˜๊ฐ€ ํ•„์š”ํ•จ
โ€ข
Prometheus ์—์„œ ์ถ”์ถœ์‹œ ์‹œ๋ฆฌ์ฆˆ๋‹น 11,000 ํฌ์ธํŠธ ์ด์ƒ ์ถ”์ถœ์‹œ 400 ์—๋Ÿฌ ๋ฐœ์ƒย โ†’ ๋ฒ”์œ„๋ฅผ ์ข๊ฒŒ ๋˜๋Š” ๊ทธ๋ฃนํ•‘ ํ•ด์„œ ํ•ด์•ผํ•จ

Data ํ™•์ธ

Local ํ™˜๊ฒฝ์— InfluxDB ์‹คํ–‰

docker run -p 8086:8086 -v influxdb:/var/lib/influxdb influxdb

Local ํ™˜๊ฒฝ์— Chronograf ์‹คํ–‰

docker run -it --rm -p 18888:8888 chronograf --influxdb-url=http://*.*.*.*:8086

Data ์ฃผ์ž…

Crontab ์„ค์ •

โ€ข
Prometheusย โ†’ InfluxDB ๋กœ ์‹ฑํฌ๋Š” 1๋ถ„ ์ฃผ๊ธฐ๋กœ ๋งˆ์ง€๋ง‰ 2๋ถ„ ๊ฐ„์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ์ž…
โ€ข
์˜ˆ์ธก ๋ฐ์ดํ„ฐ ์ƒ์„ฑ์€ 30์ผ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ˜„์žฌ ์‹œ๊ฐ„ + 3์‹œ๊ฐ„ ์˜ˆ์ธก ๋ฐ์ดํ„ฐ๋ฅผ ์‹œ๊ฐ„๋‹จ์œ„๋กœ 10๋ถ„์— ์‹œ์ž‘ํ•˜์—ฌ 3์„ธํŠธ(๊ณตํ†ต, ๋งค์žฅ, ๊ณตํ†ต)์˜ ์˜ˆ์ธก ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ์ž… (๋ฐ์Šคํฌํƒ‘์—์„œ ์‹คํ–‰์‹œ 28๋ถ„์ •๋„ ์†Œ์š” ๋จ)
* * * * * /home/phil/Documents/prophet/redisSync.sh >> /home/phil/Documents/prophet/log_redisSync.log 2>&1 30 * * * * /home/phil/Documents/prophet/forecastRedisKeyCount.sh >> /home/phil/Documents/prophet/log_redisForecast.log 2>&1
Bash
๋ณต์‚ฌ

Data ์ ์šฉ ์˜ˆ์ • ๋ฒ”์œ„

โ€ข
Key Count: ์™„๋ฃŒ
โ€ข
Client

์•Œ๋ฆผ ์„ค์ • ์ž‘์šฉ ์˜ˆ์ • ๋ฒ”์œ„

โ€ข
์ƒํ•˜์œ„ ์˜ˆ์ธก ๊ฐ’ ๋ฒ—์–ด๋‚˜๋Š” ๊ฒƒ์— ๋Œ€ํ•ด ์•Œ๋ฆผ (๊ฒฝ๊ณ )
โ€ข
์„ธํŠธ๋กœ ๊ตฌ์„ฑ๋œ ์„œ๋น„์Šค์— ๋Œ€ํ•ด์„œ๋Š” ๋‘ ๊ทธ๋ž˜ํ”„์˜ ์ฐจ์ด๊ฐ€ ํŠน์ • ๊ฐ’์ด์ƒ ๋ฒŒ์–ด์ง€๋Š” ๊ฒฝ์šฐ ์•Œ๋ฆผ (๊ฒฝ๊ณ )
โ—ฆ
์˜ˆ) ๊ณตํ†ต 1 vs ๊ณตํ†ต 2 ๊ฐ’์ด ๊ฐ‘์ž๊ธฐ ๋งŽ์ด ์ฐจ์ด๋‚˜๋Š” ๊ฒฝ์šฐ (์˜ˆ์ธก๋ฒ”์œ„์—์„œ ๋ฒ—์–ด๋‚˜๋Š” ๊ฒƒ์œผ๋กœ๋„ ์ปค๋ฒ„๊ฐ€ ๋  ๋“ฏ)

Grafana ์ ์šฉ

Alternatives

References