@@ -144,6 +144,24 @@ func getTimeSeriesKey(projectId, metricType, granularity string, isSystemMetric
144144 return fmt .Sprintf ("ts:project-%s:%s:%s" , metricType , projectId , granularity )
145145}
146146
147+ // bucketTimestampMs returns the current UTC time truncated to the start of the
148+ // given granularity bucket, in milliseconds. Truncating ensures that all events
149+ // within the same bucket share one timestamp so ON_DUPLICATE SUM accumulates
150+ // them into a single sample instead of creating a separate sample per event.
151+ func bucketTimestampMs (granularity string ) int64 {
152+ now := time .Now ().UTC ()
153+ var t time.Time
154+ switch granularity {
155+ case "hourly" :
156+ t = now .Truncate (time .Hour )
157+ case "daily" :
158+ t = now .Truncate (24 * time .Hour )
159+ default : // minutely
160+ t = now .Truncate (time .Minute )
161+ }
162+ return t .UnixNano () / int64 (time .Millisecond )
163+ }
164+
147165// recordProjectMetrics records project metrics to Redis TimeSeries
148166// metricType can be: "events-accepted", "events-rate-limited", etc.
149167func (handler * Handler ) recordProjectMetrics (projectId , metricType string , isSystemMetric bool ) {
@@ -158,18 +176,17 @@ func (handler *Handler) recordProjectMetrics(projectId, metricType string, isSys
158176 }
159177
160178 // minutely: store for 24 hours
161- // Use TS.ADD with ON_DUPLICATE SUM to accumulate events within the same timestamp
162- if err := handler .RedisClient .SafeTSAdd (minutelyKey , 1 , labels , 24 * time .Hour ); err != nil {
179+ if err := handler .RedisClient .SafeTSAdd (minutelyKey , 1 , labels , 24 * time .Hour , bucketTimestampMs ("minutely" )); err != nil {
163180 log .Errorf ("failed to add minutely TS for %s: %v" , metricType , err )
164181 }
165182
166183 // hourly: store for 7 days
167- if err := handler .RedisClient .SafeTSAdd (hourlyKey , 1 , labels , 7 * 24 * time .Hour ); err != nil {
184+ if err := handler .RedisClient .SafeTSAdd (hourlyKey , 1 , labels , 7 * 24 * time .Hour , bucketTimestampMs ( "hourly" ) ); err != nil {
168185 log .Errorf ("failed to add hourly TS for %s: %v" , metricType , err )
169186 }
170187
171188 // daily: store for 90 days
172- if err := handler .RedisClient .SafeTSAdd (dailyKey , 1 , labels , 90 * 24 * time .Hour ); err != nil {
189+ if err := handler .RedisClient .SafeTSAdd (dailyKey , 1 , labels , 90 * 24 * time .Hour , bucketTimestampMs ( "daily" ) ); err != nil {
173190 log .Errorf ("failed to add daily TS for %s: %v" , metricType , err )
174191 }
175192}
0 commit comments