Skip to content

Per-package daily download history via the REST API #661

Description

@coatless

stat_download returns rolling windows (cnt_1d/7d/30d/total) but not a series, so to build a daily history each consumer has to poll once a day and store the result. I do that for https://github.com/r-observatory/autoobs-downloads.

The daily data is already collected, though. agg_download_pkg holds one row per (period, dt, metapkg_id, folder_id, country):

create table if not exists agg_download_pkg (
period stat_period_t NOT NULL,
dt timestamp NOT NULL,
metapkg_id bigint NOT NULL,
folder_id bigint NOT NULL,
country varchar(2),
cnt bigint,
primary key(period, dt, metapkg_id, folder_id, country)
);

StatAggPkg already writes period='day' rows on a schedule:

if ($minion->lock('stat_agg_pkg_day', 2*60*60)) {
_agg($app, $job, 'day');

Cleanup keeps them for 6 months:

where period = 'day' and dt < (current_timestamp - interval '6 month')
LIMIT 100000
)
END_SQL
} else {
$sql = "delete from agg_download_pkg where period = 'day' and dt < date_sub(current_timestamp, interval 6 month) LIMIT 100000";

And stat_download already reads period='day' (last 30 days) before collapsing it into windows:

sub stat_download {
my $self = shift;
my $id = $self->param('id');
my $sql;
$sql = <<'END_SQL';
select
extract(epoch from ( select min(dt) from agg_download_pkg where metapkg_id = ? and period = 'day' ))::int as first_seen,
coalesce( (select sum(cnt) as cnt from agg_download_pkg where metapkg_id = ? and period = 'total' and dt = (select max(dt) from agg_download_pkg where period = 'total')), 0 ) as cnt_total,
coalesce( (select sum(cnt) as cnt from agg_download_pkg where metapkg_id = ? and period = 'hour' and dt > (select max(dt) from agg_download_pkg where period = 'total')), 0 ) as cnt_today,
sum(cnt) as cnt_30d,
coalesce( sum(case when dt > now() - interval '7 day' then cnt else 0 end), 0 ) as cnt_7d,
coalesce( sum(case when dt > now() - interval '1 day' then cnt else 0 end), 0) as cnt_1d
from agg_download_pkg
where metapkg_id = ? and period = 'day' and dt > now() - interval '30 day'
END_SQL
unless ($self->schema->pg) {
$sql =~ s/::int//g;
$sql =~ s/interval '(\d+) day'/interval $1 day/g;
$sql =~ s/extract\(epoch from/unix_timestamp(/g;
}
my $res = $self->schema->storage->dbh->selectall_arrayref($sql, {Columns => {}}, $id, $id, $id, $id);
return $self->render(json => { data => $res });
}

Could you expose those rows directly, e.g. GET /rest/package/<id>/stat_download_daily returning [{date, count}, ...]? It would be a near-copy of stat_download:

select to_char(dt,'YYYY-MM-DD') as date, sum(cnt) as count
from agg_download_pkg
where metapkg_id = ? and period = 'day'
group by dt order by dt;

plus one route line next to the existing stat_download route:

$rest_r->get('/package/#id/stat_download')->to('metapkg#stat_download');

That saves every downstream from running its own snapshotter, and gives up to 6 months of backfill.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions