1. 배경
grafana 는 Source DB (ex. prometheus, influx, etc.. ) 들에 쌓인 데이터를 파싱하여 Visualizaion 해주는 tool 이다.
모니터링 시에 Time Range 에 따라 원하는 Range 의 지표 값들을 볼 수 있다.
data 를 가져오는 것은 grafana 를 통해 각 Source DB 에 요청을 하지만,
가져온 data 지표들을 grafana page 에 visualization 을 해주는 것은 웹 브라우저가 하게 된다.
따라서 step 을 짧게 가져가서, time slot 이 너무 많게 되면 웹 브라우저 Client Side 에 과부하를 유발할 수 있다.
그래서 grafana 는 prometheus 등에 data 지표들을 요청할 때 Time Range 에 따라 자동으로 step 값을 증가시킨다.
이는 Time Ragne 를 조절하면서 Query Inspector 를 통해 url 을 보면, Range 에 따라 url 마지막에 있는 step 값이 자동으로 변하는 것을 볼 수 있다.
exmaple.
api/datasources/proxy/1/api/v1/query_range?query=node_memory_MemFree_bytes%7Binstance%3D~%2210.1.1.1%22%7D&start=1589166840&end=1589253240&step=120
2. 문제점
위와 같이 step 이 time range 에 종속적으로 설정되면, time range 가 길어져도 일정한 개수의 time slot 을 보장할 수 있으므로 웹 브라우저의 과부하는 막을 수 있겠지만, step 값이 증가하면 step 만큼의 평균 수치 밖에 보여주지못한다.
이 때 발생하는 문제는, 특정 시점에 튀는 수치를 잡아낼 수가 없다는 것이다.
time ragne 에 depend on 한 지표는 웹 브라우저의 부하를 막을 수는 있지만, 튀는 부분들을 잡아낼 수 없기때문에,
DB Monitoring 지표 상 안맞는 부분이 있는 것이다.
그래서 웹 브라우저의 부하는 감안하더라도 (지표가 조금 늦게 로딩이 되더라도) 일정한 step 값을 지닌 지표를 보는 것을 고려해보았다.
3. 고민..
step 값을 fix 시키는 방법이 없을까..?
grafana 는 단순히 Source DB 에 data 를 API Call 형태로 가져오고, 가져온 데이터를 웹 브라우저에 뿌려주는 형태이다.
그러면 단순히 API Call 을 할 때 url 의 step 을 catch 해서 변경해주면 되지 않을까?
라는 생각으로 step 을 fix 시켰다.
prometheus 의 nginx 를 두고 nginx 단에서 강제로 변경해주어도 되지만,
grafana 를 통해서 변경하기로 했다.
또한 grafana 와 prometheus 소스에도 time slot limit 이 걸려있는데, (11000)
이 limit 을 늘려준다.
4. Customizing
1) step fix
./pkg/api/pluginproxy/ds_proxy.go
* 원본 코드
132 if err := opentracing.GlobalTracer().Inject(
133 span.Context(),
134 opentracing.HTTPHeaders,
135 opentracing.HTTPHeadersCarrier(proxy.ctx.Req.Request.Header)); err != nil {
136 logger.Error("Failed to inject span context instance", "err", err)
137 }
138
139 reverseProxy.ServeHTTP(proxy.ctx.Resp, proxy.ctx.Req.Request)
* step 을 강제로 30초로 변경하도록 url catch 및 30초로 변경
132 if err := opentracing.GlobalTracer().Inject(
133 span.Context(),
134 opentracing.HTTPHeaders,
135 opentracing.HTTPHeadersCarrier(proxy.ctx.Req.Request.Header)); err != nil {
136 logger.Error("Failed to inject span context instance", "err", err)
137 }
138
/* 추가된 부분 * /
139 if strings.Contains(proxy.ctx.Req.Request.URL.String(), "step=") == true {
140 m := regexp.MustCompile(`step=(0|[1-9][0-9]*)`)
141 s := m.ReplaceAllString(proxy.ctx.Req.Request.URL.String(), "step=30")
142
143 proxy.ctx.Req.Request.URL, _ =url.Parse(s)
144 }
/* */
145
146 reverseProxy.ServeHTTP(proxy.ctx.Resp, proxy.ctx.Req.Request)
위와 같이 하면 step 값을 고정 시킬 수 있다.
하지만 grafana 의 prometheus api 소스 상 type script 단에서 time slot 을 제한(11000개) 하는 부분이 있어서,
time ragne 가 길어지면 call 을 하지 않는다.
2) grafana time slot limit 해제
.public/app/plugins/datasource/prometheus/datasource.ts
* 원본 소스
396 adjustInterval(interval: number, minInterval: number, range: number, intervalFactor: number) {
397 // Prometheus will drop queries that might return more than 11000 data points.
398 // Calculate a safe interval as an additional minimum to take into account.
399 // Fractional safeIntervals are allowed, however serve little purpose if the interval is greater than 1
400 // If this is the case take the ceil of the value.
401 let safeInterval = range / 11000;
402 if (safeInterval > 1) {
403 safeInterval = Math.ceil(safeInterval);
404 }
405 return Math.max(interval * intervalFactor, minInterval, safeInterval);
406 }
* safeInterval 값을 변경
396 adjustInterval(interval: number, minInterval: number, range: number, intervalFactor: number) {
397 // Prometheus will drop queries that might return more than 11000 data points.
398 // Calculate a safe interval as an additional minimum to take into account.
399 // Fractional safeIntervals are allowed, however serve little purpose if the interval is greater than 1
400 // If this is the case take the ceil of the value.
// 수정된 라인
401 let safeInterval = range / 20000000;
402 if (safeInterval > 1) {
403 safeInterval = Math.ceil(safeInterval);
404 }
405 return Math.max(interval * intervalFactor, minInterval, safeInterval);
406 }
3) prometheus time slot limit 해제
./web/api/v1/api.go
* 원본 소스
// For safety, limit the number of returned points per timeseries.
// This is sufficient for 60s resolution for a week or 1h resolution for a year.
if end.Sub(start)/step > 11000 {
err := errors.New("exceeded maximum resolution of 11,000 points per timeseries. Try decreasing the query resolution (?step=XX)")
return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil}
}
* 수정
// For safety, limit the number of returned points per timeseries.
// This is sufficient for 60s resolution for a week or 1h resolution for a year.
if end.Sub(start)/step > 2000000 {
err := errors.New("exceeded maximum resolution of 11,000 points per timeseries. Try decreasing the query resolution (?step=XX)")
return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil}
}
5. Conclusion (+ Result)
위와 같이 일주일 치의 그래프를 보아도 값이 뭉개지지는 않는다. (time range 를 좁혀도 동일한 지표 값이다)
만약 time range 에 의해 step 값이 증가했으면 뾰족한 부분들은 다 사라지고 다 뭉개졌을 것이다.
step=30 기준으로 운영을 하고 있지만, time range 가 길어져도 크게 응답 속도에 불편한 점은 없다.
(물론 느려지긴했지만.. 그것보다 얻는 장점이 훨씬 크기에 체감은 적다)
DB Monitoring 특성 상 time range 를 길게 볼 일이 많은데, 어느정도의 웹 브라우저의 부하를 포기하여 위와 같이 적용하는 것도 괜찮은 방법이라고 생각한다.
'Monitoring > R&D' 카테고리의 다른 글
percona rds-exporter AWS API throttling issue (2) | 2023.02.23 |
---|---|
rds_exporter API Call 비용 이슈(+ Customizing) (1) | 2020.05.12 |