본문 바로가기

Monitoring/R&D

Grafana 지표 값이 뭉개지는 문제 (+ Customizing)

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 를 길게 볼 일이 많은데, 어느정도의 웹 브라우저의 부하를 포기하여 위와 같이 적용하는 것도 괜찮은 방법이라고 생각한다.