Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion components/places/sql/create_shared_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ CREATE INDEX IF NOT EXISTS frecencyindex ON moz_places(frecency);
CREATE INDEX IF NOT EXISTS lastvisitdatelocalindex ON moz_places(last_visit_date_local);
CREATE INDEX IF NOT EXISTS lastvisitdateremoteindex ON moz_places(last_visit_date_remote);
CREATE UNIQUE INDEX IF NOT EXISTS guid_uniqueindex ON moz_places(guid);
CREATE INDEX IF NOT EXISTS originidindex ON moz_places(origin_id);

-- speeds up the per-origin ORDER BY frecency DESC in api/matcher.rs URL_SQL
CREATE INDEX IF NOT EXISTS idx_places_origin_frecency ON moz_places(origin_id, frecency);

-- partial index to help speed up the fetch_outgoing query in history.rs
CREATE INDEX IF NOT EXISTS idx_places_outgoing_by_frecency
Expand Down Expand Up @@ -157,6 +159,9 @@ CREATE TABLE IF NOT EXISTS moz_origins (

CREATE INDEX IF NOT EXISTS hostindex ON moz_origins(rev_host);

-- speeds up the host range scan and TOTAL(frecency) in api/matcher.rs ORIGIN_SQL
CREATE INDEX IF NOT EXISTS idx_origins_host_frecency ON moz_origins(host, frecency);


-- This table holds key-value metadata for Places and its consumers. Sync stores
-- the sync IDs for the bookmarks and history collections in this table, and the
Expand Down
13 changes: 12 additions & 1 deletion components/places/src/db/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use sql_support::ConnExt;

use super::db::{Pragma, PragmaGuard};

pub const VERSION: u32 = 20;
pub const VERSION: u32 = 21;

// Shared schema and temp tables for the read-write and Sync connections.
const CREATE_SHARED_SCHEMA_SQL: &str = include_str!("../../sql/create_shared_schema.sql");
Expand Down Expand Up @@ -341,6 +341,17 @@ pub fn upgrade_from(db: &Connection, from: u32) -> rusqlite::Result<()> {
db.execute("ANALYZE moz_places", [])?;
db.execute("ANALYZE moz_historyvisits", [])?;
}
20 => {
// Drop the old originidindex, then create its replacement
// (idx_places_origin_frecency) and idx_origins_host_frecency by
// calling the shared schema file
db.execute("DROP INDEX IF EXISTS originidindex", [])?;
db.execute_batch(CREATE_SHARED_SCHEMA_SQL)?;
// Manually call analyze so the planner can start using the indexes
// immediately, and to refresh stale stats on existing profiles
db.execute("ANALYZE moz_origins", [])?;
db.execute("ANALYZE moz_places", [])?;
}
// Add more migrations here...

// Any other from value indicates that something very wrong happened
Expand Down
7 changes: 5 additions & 2 deletions testing/separated/places-bench/src/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ mod matching;

use criterion::{criterion_group, criterion_main};
use database::{
bench_match_url, bench_outgoing_candidates, bench_search_frecent, bench_top_frecent,
bench_match_url, bench_origin_autocomplete, bench_origin_frecency_index,
bench_outgoing_candidates, bench_search_frecent, bench_top_frecent,
};
use matching::bench_match_anywhere;

Expand All @@ -18,7 +19,9 @@ criterion_group!(
bench_search_frecent,
bench_match_url,
bench_outgoing_candidates,
bench_top_frecent
bench_top_frecent,
bench_origin_autocomplete,
bench_origin_frecency_index
);
criterion_group!(bench_mem, bench_match_anywhere);
criterion_main!(bench_db, bench_mem);
149 changes: 149 additions & 0 deletions testing/separated/places-bench/src/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -529,3 +529,152 @@ pub fn bench_top_frecent(c: &mut Criterion) {
});
}
}

// -----------------------------------------------------------------------------------------
// origin autocomplete benchmark, speeding up the moz_origins host scan via an index
// -----------------------------------------------------------------------------------------

// Insert many origins, spread over leading letters and frecencies, so the host scan and
// GROUP BY in ORIGIN_SQL have real work to do. moz_origins has no insert triggers.
fn seed_db_for_origin_autocomplete(db: &PlacesDb) -> places::Result<()> {
db.execute_batch(
r#"
WITH RECURSIVE seq(i) AS (
SELECT 1 UNION ALL SELECT i + 1 FROM seq WHERE i < 6000
)
INSERT OR IGNORE INTO moz_origins (prefix, host, rev_host, frecency)
SELECT
'https',
char(97 + (i % 26)) || 'host' || i || '.example.com',
reverse_host(char(97 + (i % 26)) || 'host' || i || '.example.com'),
(i * 7) % 100000
FROM seq;
"#,
)?;
db.execute_batch("ANALYZE; PRAGMA optimize;")?;
Ok(())
}

fn create_origins_host_frecency_index(db: &PlacesDb) -> places::Result<()> {
db.execute_batch(
"CREATE INDEX IF NOT EXISTS idx_origins_host_frecency ON moz_origins(host, frecency);",
)?;
db.execute_batch("ANALYZE; PRAGMA optimize;")?;
Ok(())
}

fn drop_origins_host_frecency_index(db: &PlacesDb) -> places::Result<()> {
db.execute_batch("DROP INDEX IF EXISTS idx_origins_host_frecency;")?;
db.execute_batch("ANALYZE; PRAGMA optimize;")?;
Ok(())
}

pub fn bench_origin_autocomplete(c: &mut Criterion) {
// A single-letter query routes through looks_like_origin to ORIGIN_SQL.
const QUERY: &str = "a";

// Create two independent DBs so index state changes do not leak between benches.
let db_no_index = TestDb::new();
seed_db_for_origin_autocomplete(&db_no_index.db).unwrap();
drop_origins_host_frecency_index(&db_no_index.db).unwrap();

let db_with_index = TestDb::new();
seed_db_for_origin_autocomplete(&db_with_index.db).unwrap();
create_origins_host_frecency_index(&db_with_index.db).unwrap();

// 1) Without idx_origins_host_frecency
{
let tdb = db_no_index.clone();
c.bench_function("origin_autocomplete: NO idx_origins_host_frecency", move |b| {
let db = &tdb.db;
b.iter(|| match_url(db, QUERY).unwrap())
});
}

// 2) With idx_origins_host_frecency
{
let tdb = db_with_index.clone();
c.bench_function("origin_autocomplete: idx_origins_host_frecency", move |b| {
let db = &tdb.db;
b.iter(|| match_url(db, QUERY).unwrap())
});
}
}

// -----------------------------------------------------------------------------------------
// per-origin url benchmark, replacing originidindex with an (origin_id, frecency) index
// -----------------------------------------------------------------------------------------

// Create many pages that all share one origin, so URL_SQL's per-origin ORDER BY frecency has
// a large set to walk. Real URLs let the afterinsert trigger wire up origin_id, as init_db does.
fn seed_db_for_origin_frecency(db: &PlacesDb) -> places::Result<()> {
db.execute_batch(
r#"
WITH RECURSIVE seq(i) AS (
SELECT 1 UNION ALL SELECT i + 1 FROM seq WHERE i < 12000
)
INSERT INTO moz_places (url, guid, frecency, hidden, url_hash)
SELECT
'https://example.com/page/' || i,
'bench_of_' || i,
(i * 7919) % 100000,
0,
hash('https://example.com/page/' || i)
FROM seq;
"#,
)?;
places::storage::delete_pending_temp_tables(db)?;
db.execute_batch("ANALYZE; PRAGMA optimize;")?;
Ok(())
}

// Baseline: the single-column index that idx_places_origin_frecency replaces.
fn create_origin_id_index(db: &PlacesDb) -> places::Result<()> {
db.execute_batch(
"DROP INDEX IF EXISTS idx_places_origin_frecency;
CREATE INDEX IF NOT EXISTS originidindex ON moz_places(origin_id);",
)?;
db.execute_batch("ANALYZE; PRAGMA optimize;")?;
Ok(())
}

fn create_origin_frecency_index(db: &PlacesDb) -> places::Result<()> {
db.execute_batch(
"DROP INDEX IF EXISTS originidindex;
CREATE INDEX IF NOT EXISTS idx_places_origin_frecency ON moz_places(origin_id, frecency);",
)?;
db.execute_batch("ANALYZE; PRAGMA optimize;")?;
Ok(())
}

pub fn bench_origin_frecency_index(c: &mut Criterion) {
// A query containing '/' routes to URL_SQL, scoped to the heavy origin.
const QUERY: &str = "example.com/";

// Create two independent DBs so index state changes do not leak between benches.
let db_origin_id = TestDb::new();
seed_db_for_origin_frecency(&db_origin_id.db).unwrap();
create_origin_id_index(&db_origin_id.db).unwrap();

let db_origin_frecency = TestDb::new();
seed_db_for_origin_frecency(&db_origin_frecency.db).unwrap();
create_origin_frecency_index(&db_origin_frecency.db).unwrap();

// 1) Old single-column originidindex
{
let tdb = db_origin_id.clone();
c.bench_function("origin_url: originidindex", move |b| {
let db = &tdb.db;
b.iter(|| match_url(db, QUERY).unwrap())
});
}

// 2) New idx_places_origin_frecency
{
let tdb = db_origin_frecency.clone();
c.bench_function("origin_url: idx_places_origin_frecency", move |b| {
let db = &tdb.db;
b.iter(|| match_url(db, QUERY).unwrap())
});
}
}