Skip to content

Commit daf0751

Browse files
committed
Define recent_crate_downloads in terms of version_downloads
The only place we're ever using `crate_downloads` directly is in a place that we should have been using `recent_crate_downloads`. Once we make that change, `crate_downloads` is only ever used to populate `recent_crate_downloads`, which can just as easily be populated from `version_downloads` instead. Populating it from `version_downloads` is a bit slower since the table is so much larger, and we don't have an index on it which we can use for this query. However, this doesn't really matter since we run this asynchronously. Now that we're on PG 11, I also intend to partition `version_downloads` on date which will make populating the view much faster once again. Note that I have not removed `crate_downloads` yet. That needs to be deployed separately, as it's a destructive change which would break running code, so we can't run that migration until the code which is no longer relying on `crate_downloads` is deployed
1 parent 44060f3 commit daf0751

File tree

7 files changed

+50
-75
lines changed

7 files changed

+50
-75
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
DROP MATERIALIZED VIEW recent_crate_downloads;
2+
CREATE MATERIALIZED VIEW recent_crate_downloads (crate_id, downloads) AS
3+
SELECT crate_id, SUM(downloads) FROM crate_downloads
4+
WHERE date > date(CURRENT_TIMESTAMP - INTERVAL '90 days')
5+
GROUP BY crate_id;
6+
CREATE UNIQUE INDEX recent_crate_downloads_crate_id ON recent_crate_downloads (crate_id);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
DROP MATERIALIZED VIEW recent_crate_downloads;
2+
CREATE MATERIALIZED VIEW recent_crate_downloads (crate_id, downloads) AS
3+
SELECT crate_id, SUM(version_downloads.downloads) FROM version_downloads
4+
INNER JOIN versions
5+
ON version_downloads.version_id = versions.id
6+
WHERE version_downloads.date > date(CURRENT_TIMESTAMP - INTERVAL '90 days')
7+
GROUP BY crate_id;
8+
CREATE UNIQUE INDEX recent_crate_downloads_crate_id ON recent_crate_downloads (crate_id);

src/bin/update-downloads.rs

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ extern crate diesel;
66
use cargo_registry::{
77
db,
88
models::VersionDownload,
9-
schema::{crate_downloads, crates, metadata, version_downloads, versions},
9+
schema::{crates, metadata, version_downloads, versions},
1010
util::CargoResult,
1111
};
1212

@@ -53,7 +53,7 @@ fn update(conn: &PgConnection) -> QueryResult<()> {
5353
}
5454

5555
fn collect(conn: &PgConnection, rows: &[VersionDownload]) -> QueryResult<()> {
56-
use diesel::{insert_into, update};
56+
use diesel::update;
5757

5858
println!("updating {} versions", rows.len());
5959

@@ -77,18 +77,6 @@ fn collect(conn: &PgConnection, rows: &[VersionDownload]) -> QueryResult<()> {
7777
.set(crates::downloads.eq(crates::downloads + amt))
7878
.execute(conn)?;
7979

80-
// Update the total number of crate downloads for today
81-
insert_into(crate_downloads::table)
82-
.values((
83-
crate_downloads::crate_id.eq(crate_id),
84-
crate_downloads::downloads.eq(amt),
85-
crate_downloads::date.eq(download.date),
86-
))
87-
.on_conflict(crate_downloads::table.primary_key())
88-
.do_update()
89-
.set(crate_downloads::downloads.eq(crate_downloads::downloads + amt))
90-
.execute(conn)?;
91-
9280
// Now that everything else for this crate is done, update the global counter of total
9381
// downloads
9482
update(metadata::table)
@@ -144,16 +132,6 @@ mod test {
144132
(krate, version)
145133
}
146134

147-
macro_rules! crate_downloads {
148-
($conn:expr, $id:expr, $expected:expr) => {
149-
let dl = crate_downloads::table
150-
.filter(crate_downloads::crate_id.eq($id))
151-
.select(crate_downloads::downloads)
152-
.first($conn);
153-
assert_eq!(Ok($expected as i32), dl);
154-
};
155-
}
156-
157135
#[test]
158136
fn increment() {
159137
use diesel::dsl::*;
@@ -185,7 +163,6 @@ mod test {
185163
.select(crates::downloads)
186164
.first(&conn);
187165
assert_eq!(Ok(1), crate_downloads);
188-
crate_downloads!(&conn, krate.id, 1);
189166
crate::update(&conn).unwrap();
190167
let version_downloads = versions::table
191168
.find(version.id)
@@ -298,7 +275,6 @@ mod test {
298275
.unwrap();
299276
assert_eq!(krate2.downloads, 2);
300277
assert_eq!(krate2.updated_at, krate_before.updated_at);
301-
crate_downloads!(&conn, krate.id, 1);
302278
crate::update(&conn).unwrap();
303279
let version3 = versions::table
304280
.find(version.id)

src/controllers/krate/metadata.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
77
use crate::controllers::prelude::*;
88
use crate::models::{
9-
Category, Crate, CrateCategory, CrateDownload, CrateKeyword, CrateVersions, Keyword, User,
10-
Version,
9+
Category, Crate, CrateCategory, CrateKeyword, CrateVersions, Keyword, RecentCrateDownloads,
10+
User, Version,
1111
};
1212
use crate::schema::*;
1313
use crate::views::{
@@ -101,8 +101,6 @@ pub fn summary(req: &mut dyn Request) -> CargoResult<Response> {
101101

102102
/// Handles the `GET /crates/:crate_id` route.
103103
pub fn show(req: &mut dyn Request) -> CargoResult<Response> {
104-
use diesel::dsl::*;
105-
106104
let name = &req.params()["crate_id"];
107105
let conn = req.db_conn()?;
108106
let krate = Crate::by_name(name).first::<Crate>(&*conn)?;
@@ -123,10 +121,10 @@ pub fn show(req: &mut dyn Request) -> CargoResult<Response> {
123121
.inner_join(categories::table)
124122
.select(categories::all_columns)
125123
.load(&*conn)?;
126-
let recent_downloads = CrateDownload::belonging_to(&krate)
127-
.filter(crate_downloads::date.gt(date(now - 90.days())))
128-
.select(sum(crate_downloads::downloads))
129-
.get_result(&*conn)?;
124+
let recent_downloads = RecentCrateDownloads::belonging_to(&krate)
125+
.select(recent_crate_downloads::downloads)
126+
.get_result(&*conn)
127+
.optional()?;
130128

131129
let badges = badges::table
132130
.filter(badges::crate_id.eq(krate.id))

src/models.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub use self::download::VersionDownload;
66
pub use self::email::{Email, NewEmail};
77
pub use self::follow::Follow;
88
pub use self::keyword::{CrateKeyword, Keyword};
9-
pub use self::krate::{Crate, CrateDownload, CrateVersions, NewCrate};
9+
pub use self::krate::{Crate, CrateVersions, NewCrate, RecentCrateDownloads};
1010
pub use self::owner::{CrateOwner, Owner, OwnerKind};
1111
pub use self::rights::Rights;
1212
pub use self::team::{NewTeam, Team};

src/models/krate.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use chrono::{NaiveDate, NaiveDateTime};
1+
use chrono::NaiveDateTime;
22
use diesel::associations::Identifiable;
33
use diesel::pg::Pg;
44
use diesel::prelude::*;
@@ -20,14 +20,13 @@ use crate::schema::*;
2020
/// and are possibly of malicious intent e.g. ad tracking networks, etc.
2121
const DOCUMENTATION_BLOCKLIST: [&str; 1] = ["rust-ci.org"];
2222

23-
#[derive(Debug, Insertable, Queryable, Identifiable, Associations, AsChangeset, Clone, Copy)]
23+
#[derive(Debug, Queryable, Identifiable, Associations, Clone, Copy)]
2424
#[belongs_to(Crate)]
25-
#[primary_key(crate_id, date)]
26-
#[table_name = "crate_downloads"]
27-
pub struct CrateDownload {
25+
#[primary_key(crate_id)]
26+
#[table_name = "recent_crate_downloads"]
27+
pub struct RecentCrateDownloads {
2828
pub crate_id: i32,
2929
pub downloads: i32,
30-
pub date: NaiveDate,
3130
}
3231

3332
#[derive(Debug, Clone, Queryable, Identifiable, Associations, AsChangeset, QueryableByName)]

src/tests/builders.rs

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
//! Structs using the builder pattern that make it easier to create records in tests.
22
33
use cargo_registry::{
4-
models::{Crate, CrateDownload, Keyword, NewCrate, NewVersion, Version},
5-
schema::{crate_downloads, dependencies, versions},
4+
models::{Crate, Keyword, NewCrate, NewVersion, Version},
5+
schema::{crates, dependencies, version_downloads, versions},
66
util::CargoResult,
77
views::krate_publish as u,
88
};
99
use std::{collections::HashMap, io::Read};
1010

11-
use chrono::Utc;
1211
use diesel::prelude::*;
1312
use flate2::{write::GzEncoder, Compression};
1413

@@ -240,45 +239,34 @@ impl<'a> CrateBuilder<'a> {
240239
// Since we are using `NewCrate`, we can't set all the
241240
// crate properties in a single DB call.
242241

243-
let old_downloads = self.downloads.unwrap_or(0) - self.recent_downloads.unwrap_or(0);
244-
let now = Utc::now();
245-
let old_date = now.naive_utc().date() - chrono::Duration::days(91);
246-
247242
if let Some(downloads) = self.downloads {
248-
let crate_download = CrateDownload {
249-
crate_id: krate.id,
250-
downloads: old_downloads,
251-
date: old_date,
252-
};
253-
254-
insert_into(crate_downloads::table)
255-
.values(&crate_download)
256-
.execute(connection)?;
257-
krate.downloads = downloads;
258-
update(&krate).set(&krate).execute(connection)?;
259-
}
260-
261-
if self.recent_downloads.is_some() {
262-
let crate_download = CrateDownload {
263-
crate_id: krate.id,
264-
downloads: self.recent_downloads.unwrap(),
265-
date: now.naive_utc().date(),
266-
};
267-
268-
insert_into(crate_downloads::table)
269-
.values(&crate_download)
270-
.execute(connection)?;
271-
272-
no_arg_sql_function!(refresh_recent_crate_downloads, ());
273-
select(refresh_recent_crate_downloads).execute(connection)?;
243+
krate = update(&krate)
244+
.set(crates::downloads.eq(downloads))
245+
.returning(cargo_registry::models::krate::ALL_COLUMNS)
246+
.get_result(connection)?;
274247
}
275248

276249
if self.versions.is_empty() {
277250
self.versions.push(VersionBuilder::new("0.99.0"));
278251
}
279252

253+
let mut last_version_id = 0;
280254
for version_builder in self.versions {
281-
version_builder.build(krate.id, self.owner_id, connection)?;
255+
last_version_id = version_builder
256+
.build(krate.id, self.owner_id, connection)?
257+
.id;
258+
}
259+
260+
if let Some(downloads) = self.recent_downloads {
261+
insert_into(version_downloads::table)
262+
.values((
263+
version_downloads::version_id.eq(last_version_id),
264+
version_downloads::downloads.eq(downloads),
265+
))
266+
.execute(connection)?;
267+
268+
no_arg_sql_function!(refresh_recent_crate_downloads, ());
269+
select(refresh_recent_crate_downloads).execute(connection)?;
282270
}
283271

284272
if !self.keywords.is_empty() {

0 commit comments

Comments
 (0)