Efficient Evenly Distributed Sampling of Time Series Records in PostgreSQL
blog.joshsoftware.com
blog.joshsoftware.com
If you want to display a line chart that is 500px wide, for example, you know you really only need 500 data points. You can treat this chart as a histogram, and compute a min/max/avg for each value. The chart could display the min & max as a high-low band, and if you zoom in further, adjust the histogram interval and requery.
This is what a query would look like with this strategy:
start_timestamp: The start of the histogram range
end_timestamp: The end of the histogram range
num_divisions: The number of histogram divisions
select
start_timestamp + ((bin_id - 1) * ((end_timestamp - start_timestamp) / num_divisions)) as bin_start_at,
start_timestamp + ((bin_id) * ((end_timestamp - start_timestamp) / num_divisions)) as bin_end_at,
average_value,
min_value,
max_value,
num_values
from (
select
avg(value) as average_value,
min(value) as min_value,
max(value) as max_value,
count(*) as num_values,
width_bucket(data_points.timestamp, start_timestamp, end_timestamp, num_divisions) as bin_id
from data_points
where
timestamp > start_timestamp and timestamp < end_timestamp
group by bin_id
order by bin_id asc
) t;
I'm interested to see if anyone else has taken this approach, and if there are any performance considerations I haven't considered!I'm curious why CTEs don't seem to be getting used here?
SELECT
(
SELECT
(
t.data,
t.created_at
)::jsonb_x_timestamp
FROM
telemetries t
WHERE
server_id = 'a0dcc312-0623-af60-4dc0-238301cc9bf8'
AND data ? 'load_avg'
AND t.created_at >= series.target
AND t.created_at <= (series.target + INTERVAL '2592 seconds')
LIMIT 1
)
FROM
(
SELECT
target
FROM
GENERATE_SERIES(
'2020-09-01 00:00:00'::timestamp,
'2020-09-15 23:59:59'::timestamp,
INTERVAL '2592 seconds'
) target
) series
I'm curious on the performance of: WITH series AS SELECT
target
FROM
GENERATE_SERIES(
'2020-09-01 00:00:00'::timestamp,
'2020-09-15 23:59:59'::timestamp,
INTERVAL '2592 seconds'
) target
SELECT
(
t.data,
t.created_at
)::jsonb_x_timestamp
FROM
telemetries t
WHERE
server_id = 'a0dcc312-0623-af60-4dc0-238301cc9bf8'
AND data ? 'load_avg'
AND t.created_at >= series.target
AND t.created_at <= (series.target + INTERVAL '2592 seconds')
LIMIT 1
or even shuffling some of the other selects and chaining CTEs together. I'm even curious if you could somehow use a combination of CTEs to generate something more suitable for use with SYSTEM_ROWS (TIL!! will keep this in my pocket for the future!).CTEs are never a guarantee for performance improvements (they make things worse at times) but I see them getting ignored a lot when at times they are a huge boost.
Edit: wow the formatting is really bad, hopefully the code stuff doesn't kill it for mobile users
So while the UUID could be leveraged to provide a distribution space for random sampling, it doesn't help much with the requirement that the records be time distributed.
In the article I used a 2 week span as an example, but in actual use this could be a 1 hour span or a 4 month span, too, with an arbitrary number of data points being collected, and I need a technique that works efficiently for all of those things.
To the core of your thought, though, about using the UUID as a source of randomness for gathering a random sample, I think the challenge there is going to be indexing the UUID in a way that is amenable to that purpose.