Commit ac7b23b2 authored by Brett Wilson's avatar Brett Wilson

History init: add metrics and skip migration.

Removes the migration code to history DB version 34. This version did a
large migration of the URL table which can use several MB of disk storage. The
suspicion is that this is causing more initialization failures for users with
full devices.

The change in version 34 is to add an AUTOINCREMENT tag on the primary key.
This is required for some sync work that is in progress but is not complete,
so skipping this migration step won't break any users. We will need to fix
this before rolling out the new sync features.

The 34 change also removed the favicon_id column. Because some databases
may have the column and some won't, the code to copy to the in-memory
database (which previously was implicit) now lists the columns to copy
explicitly.

Adds UMA logging for history database initialization failures, as well as
versions for migration failures. Logging for the Sqlite error code for the
various failure states may have been nice but would have required more
plumbing and seemed impractical for now.

There is one behavior change the error logging resulted in: errors migrating
to version 22 were previously ignored but are now fatal. I believe continuing
in this case was a mistake. This will only affect users migrating from an
Android Chrome version prior to 2012, and failures here will likely be fatal
anyway.

BUG=734194, 736136
R=dtrainor@chromium.org, gangwu@chromium.org, mpearson@chromium.org, yzshen@chromium.org

Review-Url: https://codereview.chromium.org/2954593002 .
Cr-Commit-Position: refs/heads/master@{#481958}
parent f06b5d19
...@@ -651,6 +651,9 @@ void HistoryBackend::InitImpl( ...@@ -651,6 +651,9 @@ void HistoryBackend::InitImpl(
bool kill_db = scheduled_kill_db_; bool kill_db = scheduled_kill_db_;
if (kill_db) if (kill_db)
KillHistoryDatabase(); KillHistoryDatabase();
// The frequency of this UMA will indicate how often history
// initialization fails.
UMA_HISTOGRAM_BOOLEAN("History.AttemptedToFixProfileError", kill_db); UMA_HISTOGRAM_BOOLEAN("History.AttemptedToFixProfileError", kill_db);
} // Falls through. } // Falls through.
case sql::INIT_TOO_NEW: { case sql::INIT_TOO_NEW: {
......
...@@ -42,6 +42,37 @@ const int kCompatibleVersionNumber = 16; ...@@ -42,6 +42,37 @@ const int kCompatibleVersionNumber = 16;
const char kEarlyExpirationThresholdKey[] = "early_expiration_threshold"; const char kEarlyExpirationThresholdKey[] = "early_expiration_threshold";
const int kMaxHostsInMemory = 10000; const int kMaxHostsInMemory = 10000;
// Logs a migration failure to UMA and logging. The return value will be
// what to return from ::Init (to simplify the call sites). Migration failures
// are almost always fatal since the database can be in an inconsistent state.
sql::InitStatus LogMigrationFailure(int from_version) {
UMA_HISTOGRAM_SPARSE_SLOWLY("History.MigrateFailureFromVersion",
from_version);
LOG(ERROR) << "History failed to migrate from version " << from_version
<< ". History will be disabled.";
return sql::INIT_FAILURE;
}
// Reasons for initialization to fail. These are logged to UMA. It corresponds
// to the HistoryInitStep enum in enums.xml.
//
// DO NOT CHANGE THE VALUES. Leave holes if anything is removed and add only
// to the end.
enum class InitStep {
OPEN = 0,
TRANSACTION_BEGIN = 1,
META_TABLE_INIT = 2,
CREATE_TABLES = 3,
VERSION = 4,
COMMIT = 5,
};
sql::InitStatus LogInitFailure(InitStep what) {
UMA_HISTOGRAM_SPARSE_SLOWLY("History.InitializationFailureStep",
static_cast<int>(what));
return sql::INIT_FAILURE;
}
} // namespace } // namespace
HistoryDatabase::HistoryDatabase( HistoryDatabase::HistoryDatabase(
...@@ -74,13 +105,13 @@ sql::InitStatus HistoryDatabase::Init(const base::FilePath& history_name) { ...@@ -74,13 +105,13 @@ sql::InitStatus HistoryDatabase::Init(const base::FilePath& history_name) {
// mode to start out for the in-memory backend to read the data). // mode to start out for the in-memory backend to read the data).
if (!db_.Open(history_name)) if (!db_.Open(history_name))
return sql::INIT_FAILURE; return LogInitFailure(InitStep::OPEN);
// Wrap the rest of init in a tranaction. This will prevent the database from // Wrap the rest of init in a tranaction. This will prevent the database from
// getting corrupted if we crash in the middle of initialization or migration. // getting corrupted if we crash in the middle of initialization or migration.
sql::Transaction committer(&db_); sql::Transaction committer(&db_);
if (!committer.Begin()) if (!committer.Begin())
return sql::INIT_FAILURE; return LogInitFailure(InitStep::TRANSACTION_BEGIN);
#if defined(OS_MACOSX) && !defined(OS_IOS) #if defined(OS_MACOSX) && !defined(OS_IOS)
// Exclude the history file from backups. // Exclude the history file from backups.
...@@ -94,11 +125,11 @@ sql::InitStatus HistoryDatabase::Init(const base::FilePath& history_name) { ...@@ -94,11 +125,11 @@ sql::InitStatus HistoryDatabase::Init(const base::FilePath& history_name) {
// NOTE: If you add something here, also add it to // NOTE: If you add something here, also add it to
// RecreateAllButStarAndURLTables. // RecreateAllButStarAndURLTables.
if (!meta_table_.Init(&db_, GetCurrentVersion(), kCompatibleVersionNumber)) if (!meta_table_.Init(&db_, GetCurrentVersion(), kCompatibleVersionNumber))
return sql::INIT_FAILURE; return LogInitFailure(InitStep::META_TABLE_INIT);
if (!CreateURLTable(false) || !InitVisitTable() || if (!CreateURLTable(false) || !InitVisitTable() ||
!InitKeywordSearchTermsTable() || !InitDownloadTable() || !InitKeywordSearchTermsTable() || !InitDownloadTable() ||
!InitSegmentTables() || !InitSyncTable()) !InitSegmentTables() || !InitSyncTable())
return sql::INIT_FAILURE; return LogInitFailure(InitStep::CREATE_TABLES);
CreateMainURLIndex(); CreateMainURLIndex();
CreateKeywordSearchTermsIndices(); CreateKeywordSearchTermsIndices();
...@@ -107,10 +138,14 @@ sql::InitStatus HistoryDatabase::Init(const base::FilePath& history_name) { ...@@ -107,10 +138,14 @@ sql::InitStatus HistoryDatabase::Init(const base::FilePath& history_name) {
// Version check. // Version check.
sql::InitStatus version_status = EnsureCurrentVersion(); sql::InitStatus version_status = EnsureCurrentVersion();
if (version_status != sql::INIT_OK) if (version_status != sql::INIT_OK) {
LogInitFailure(InitStep::VERSION);
return version_status; return version_status;
}
return committer.Commit() ? sql::INIT_OK : sql::INIT_FAILURE; if (!committer.Commit())
return LogInitFailure(InitStep::COMMIT);
return sql::INIT_OK;
} }
void HistoryDatabase::ComputeDatabaseMetrics( void HistoryDatabase::ComputeDatabaseMetrics(
...@@ -383,10 +418,8 @@ sql::InitStatus HistoryDatabase::EnsureCurrentVersion() { ...@@ -383,10 +418,8 @@ sql::InitStatus HistoryDatabase::EnsureCurrentVersion() {
// Put migration code here // Put migration code here
if (cur_version == 15) { if (cur_version == 15) {
if (!db_.Execute("DROP TABLE starred") || !DropStarredIDFromURLs()) { if (!db_.Execute("DROP TABLE starred") || !DropStarredIDFromURLs())
LOG(WARNING) << "Unable to update history database to version 16."; return LogMigrationFailure(15);
return sql::INIT_FAILURE;
}
++cur_version; ++cur_version;
meta_table_.SetVersionNumber(cur_version); meta_table_.SetVersionNumber(cur_version);
meta_table_.SetCompatibleVersionNumber( meta_table_.SetCompatibleVersionNumber(
...@@ -430,10 +463,8 @@ sql::InitStatus HistoryDatabase::EnsureCurrentVersion() { ...@@ -430,10 +463,8 @@ sql::InitStatus HistoryDatabase::EnsureCurrentVersion() {
if (cur_version == 20) { if (cur_version == 20) {
// This is the version prior to adding the visit_duration field in visits // This is the version prior to adding the visit_duration field in visits
// database. We need to migrate the database. // database. We need to migrate the database.
if (!MigrateVisitsWithoutDuration()) { if (!MigrateVisitsWithoutDuration())
LOG(WARNING) << "Unable to update history database to version 21."; return LogMigrationFailure(20);
return sql::INIT_FAILURE;
}
++cur_version; ++cur_version;
meta_table_.SetVersionNumber(cur_version); meta_table_.SetVersionNumber(cur_version);
} }
...@@ -441,102 +472,79 @@ sql::InitStatus HistoryDatabase::EnsureCurrentVersion() { ...@@ -441,102 +472,79 @@ sql::InitStatus HistoryDatabase::EnsureCurrentVersion() {
if (cur_version == 21) { if (cur_version == 21) {
// The android_urls table's data schemal was changed in version 21. // The android_urls table's data schemal was changed in version 21.
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
if (!MigrateToVersion22()) { if (!MigrateToVersion22())
LOG(WARNING) << "Unable to migrate the android_urls table to version 22"; return LogMigrationFailure(21);
}
#endif #endif
++cur_version; ++cur_version;
meta_table_.SetVersionNumber(cur_version); meta_table_.SetVersionNumber(cur_version);
} }
if (cur_version == 22) { if (cur_version == 22) {
if (!MigrateDownloadsState()) { if (!MigrateDownloadsState())
LOG(WARNING) << "Unable to fix invalid downloads state values"; return LogMigrationFailure(22);
// Invalid state values may cause crashes.
return sql::INIT_FAILURE;
}
cur_version++; cur_version++;
meta_table_.SetVersionNumber(cur_version); meta_table_.SetVersionNumber(cur_version);
} }
if (cur_version == 23) { if (cur_version == 23) {
if (!MigrateDownloadsReasonPathsAndDangerType()) { if (!MigrateDownloadsReasonPathsAndDangerType())
LOG(WARNING) << "Unable to upgrade download interrupt reason and paths"; return LogMigrationFailure(23);
// Invalid state values may cause crashes.
return sql::INIT_FAILURE;
}
cur_version++; cur_version++;
meta_table_.SetVersionNumber(cur_version); meta_table_.SetVersionNumber(cur_version);
} }
if (cur_version == 24) { if (cur_version == 24) {
if (!MigratePresentationIndex()) { if (!MigratePresentationIndex())
LOG(WARNING) << "Unable to migrate history to version 25"; return LogMigrationFailure(24);
return sql::INIT_FAILURE;
}
cur_version++; cur_version++;
meta_table_.SetVersionNumber(cur_version); meta_table_.SetVersionNumber(cur_version);
} }
if (cur_version == 25) { if (cur_version == 25) {
if (!MigrateReferrer()) { if (!MigrateReferrer())
LOG(WARNING) << "Unable to migrate history to version 26"; return LogMigrationFailure(25);
return sql::INIT_FAILURE;
}
cur_version++; cur_version++;
meta_table_.SetVersionNumber(cur_version); meta_table_.SetVersionNumber(cur_version);
} }
if (cur_version == 26) { if (cur_version == 26) {
if (!MigrateDownloadedByExtension()) { if (!MigrateDownloadedByExtension())
LOG(WARNING) << "Unable to migrate history to version 27"; return LogMigrationFailure(26);
return sql::INIT_FAILURE;
}
cur_version++; cur_version++;
meta_table_.SetVersionNumber(cur_version); meta_table_.SetVersionNumber(cur_version);
} }
if (cur_version == 27) { if (cur_version == 27) {
if (!MigrateDownloadValidators()) { if (!MigrateDownloadValidators())
LOG(WARNING) << "Unable to migrate history to version 28"; return LogMigrationFailure(27);
return sql::INIT_FAILURE;
}
cur_version++; cur_version++;
meta_table_.SetVersionNumber(cur_version); meta_table_.SetVersionNumber(cur_version);
} }
if (cur_version == 28) { if (cur_version == 28) {
if (!MigrateMimeType()) { if (!MigrateMimeType())
LOG(WARNING) << "Unable to migrate history to version 29"; return LogMigrationFailure(28);
return sql::INIT_FAILURE;
}
cur_version++; cur_version++;
meta_table_.SetVersionNumber(cur_version); meta_table_.SetVersionNumber(cur_version);
} }
if (cur_version == 29) { if (cur_version == 29) {
if (!MigrateHashHttpMethodAndGenerateGuids()) { if (!MigrateHashHttpMethodAndGenerateGuids())
LOG(WARNING) << "Unable to migrate history to version 30"; return LogMigrationFailure(29);
return sql::INIT_FAILURE;
}
cur_version++; cur_version++;
meta_table_.SetVersionNumber(cur_version); meta_table_.SetVersionNumber(cur_version);
} }
if (cur_version == 30) { if (cur_version == 30) {
if (!MigrateDownloadTabUrl()) { if (!MigrateDownloadTabUrl())
LOG(WARNING) << "Unable to migrate history to version 31"; return LogMigrationFailure(30);
return sql::INIT_FAILURE;
}
cur_version++; cur_version++;
meta_table_.SetVersionNumber(cur_version); meta_table_.SetVersionNumber(cur_version);
} }
if (cur_version == 31) { if (cur_version == 31) {
if (!MigrateDownloadSiteInstanceUrl()) { if (!MigrateDownloadSiteInstanceUrl())
LOG(WARNING) << "Unable to migrate history to version 32"; return LogMigrationFailure(31);
return sql::INIT_FAILURE;
}
cur_version++; cur_version++;
meta_table_.SetVersionNumber(cur_version); meta_table_.SetVersionNumber(cur_version);
} }
...@@ -548,37 +556,57 @@ sql::InitStatus HistoryDatabase::EnsureCurrentVersion() { ...@@ -548,37 +556,57 @@ sql::InitStatus HistoryDatabase::EnsureCurrentVersion() {
} }
if (cur_version == 33) { if (cur_version == 33) {
if (!MigrateDownloadLastAccessTime()) { if (!MigrateDownloadLastAccessTime())
LOG(WARNING) << "Unable to migrate to version 34"; return LogMigrationFailure(33);
return sql::INIT_FAILURE;
}
cur_version++; cur_version++;
meta_table_.SetVersionNumber(cur_version); meta_table_.SetVersionNumber(cur_version);
} }
if (cur_version == 34) { if (cur_version == 34) {
/*
This code is commented out because we suspect the additional disk storage
requirements of duplicating the URL table to update the schema cause
some devices to run out of storage. Errors during initialization are
very disruptive to the user experience.
TODO(https://crbug.com/736136) figure out how to update users to use
AUTOINCREMENT.
// AUTOINCREMENT is added to urls table PRIMARY KEY(id), need to recreate a // AUTOINCREMENT is added to urls table PRIMARY KEY(id), need to recreate a
// new table and copy all contents over. favicon_id is removed from urls // new table and copy all contents over. favicon_id is removed from urls
// table since we never use it. Also typed_url_sync_metadata and // table since we never use it. Also typed_url_sync_metadata and
// autofill_model_type_state tables are introduced, no migration needed for // autofill_model_type_state tables are introduced, no migration needed for
// those two tables. // those two tables.
if (!RecreateURLTableWithAllContents()) { if (!RecreateURLTableWithAllContents())
LOG(WARNING) << "Unable to update history database to version 35."; return LogMigrationFailure(34);
return sql::INIT_FAILURE; */
}
cur_version++; cur_version++;
meta_table_.SetVersionNumber(cur_version); meta_table_.SetVersionNumber(cur_version);
} }
if (cur_version == 35) { if (cur_version == 35) {
if (!MigrateDownloadTransient()) { if (!MigrateDownloadTransient())
LOG(WARNING) << "Unable to migrate to version 36"; return LogMigrationFailure(35);
return sql::INIT_FAILURE;
}
cur_version++; cur_version++;
meta_table_.SetVersionNumber(cur_version); meta_table_.SetVersionNumber(cur_version);
} }
// ========================= ^^ new migration code goes here ^^
// ADDING NEW MIGRATION CODE
// =========================
//
// Add new migration code above here. It's important to use as little space
// as possible during migration. Many phones are very near their storage
// limit, so anything that recreates or duplicates large history tables can
// easily push them over that limit.
//
// When failures happen during initialization, history is not loaded. This
// causes all components related to the history database file to fail
// completely, including autocomplete and downloads. Devices near their
// storage limit are likely to fail doing some update later, but those
// operations will then just be skipped which is not nearly as disruptive.
// See https://crbug.com/734194.
// When the version is too old, we just try to continue anyway, there should // When the version is too old, we just try to continue anyway, there should
// not be a released product that makes a database too old for us to handle. // not be a released product that makes a database too old for us to handle.
LOG_IF(WARNING, cur_version < GetCurrentVersion()) << LOG_IF(WARNING, cur_version < GetCurrentVersion()) <<
......
...@@ -79,8 +79,23 @@ bool InMemoryDatabase::InitFromDisk(const base::FilePath& history_name) { ...@@ -79,8 +79,23 @@ bool InMemoryDatabase::InitFromDisk(const base::FilePath& history_name) {
// Copy URL data to memory. // Copy URL data to memory.
base::TimeTicks begin_load = base::TimeTicks::Now(); base::TimeTicks begin_load = base::TimeTicks::Now();
// Need to explicitly specify the column names here since databases on disk
// may or may not have a favicon_id column, but the in-memory one will never
// have it. Therefore, the columns aren't guaranteed to match.
//
// TODO(https://crbug.com/736136) Once we can guarantee that the favicon_id
// column doesn't exist with migration code, this can be replaced with the
// simpler:
// "INSERT INTO urls SELECT * FROM history.urls WHERE typed_count > 0"
// which does not require us to keep the list of columns in sync. However,
// we may still want to keep the explicit columns as a safety measure.
if (!db_.Execute( if (!db_.Execute(
"INSERT INTO urls SELECT * FROM history.urls WHERE typed_count > 0")) { "INSERT INTO urls "
"(id, url, title, visit_count, typed_count, last_visit_time, hidden) "
"SELECT "
"id, url, title, visit_count, typed_count, last_visit_time, hidden "
"FROM history.urls WHERE typed_count > 0")) {
// Unable to get data from the history database. This is OK, the file may // Unable to get data from the history database. This is OK, the file may
// just not exist yet. // just not exist yet.
} }
......
...@@ -586,19 +586,32 @@ bool URLDatabase::CreateURLTable(bool is_temporary) { ...@@ -586,19 +586,32 @@ bool URLDatabase::CreateURLTable(bool is_temporary) {
sql.append(name); sql.append(name);
sql.append( sql.append(
"(" "("
"id INTEGER PRIMARY KEY AUTOINCREMENT," // The id uses AUTOINCREMENT is for sync propose. Sync uses this |id| as
// Using AUTOINCREMENT is for sync propose. Sync uses this |id| as an // an unique key to identify the URLs. If here did not use AUTOINCREMENT,
// unique key to identify the URLs. If here did not use AUTOINCREMENT, and // and Sync was not working somehow, a ROWID could be deleted and re-used
// Sync was not working somehow, a ROWID could be deleted and re-used
// during this period. Once Sync come back, Sync would use ROWIDs and // during this period. Once Sync come back, Sync would use ROWIDs and
// timestamps to see if there are any updates need to be synced. And sync // timestamps to see if there are any updates need to be synced. And sync
// will only see the new URL, but missed the deleted URL. // will only see the new URL, but missed the deleted URL.
//
// IMPORTANT NOTE: Currently new tables are created with AUTOINCREMENT
// but the migration code is disabled. This means that you will not
// be able to count on AUTOINCREMENT behavior without adding
// additional migration steps.
//
// Along with this, an unused favicon_id column will exist for tables
// without AUTOINCREMENT. This should be removed everywhere.
//
// TODO(https://crbug.com/736136) figure out how to update users to use
// AUTOINCREMENT and remove the favicon_id column consistently.
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"url LONGVARCHAR," "url LONGVARCHAR,"
"title LONGVARCHAR," "title LONGVARCHAR,"
"visit_count INTEGER DEFAULT 0 NOT NULL," "visit_count INTEGER DEFAULT 0 NOT NULL,"
"typed_count INTEGER DEFAULT 0 NOT NULL," "typed_count INTEGER DEFAULT 0 NOT NULL,"
"last_visit_time INTEGER NOT NULL," "last_visit_time INTEGER NOT NULL,"
"hidden INTEGER DEFAULT 0 NOT NULL)"); "hidden INTEGER DEFAULT 0 NOT NULL)");
// IMPORTANT: If you change the colums, also update in_memory_database.cc
// where the values are copied (InitFromDisk).
return GetDB().Execute(sql.c_str()); return GetDB().Execute(sql.c_str());
} }
......
...@@ -18768,6 +18768,15 @@ uploading your change for review. These are checked by presubmit scripts. ...@@ -18768,6 +18768,15 @@ uploading your change for review. These are checked by presubmit scripts.
</int> </int>
</enum> </enum>
<enum name="HistoryInitStep">
<int value="0" label="Open"/>
<int value="1" label="Transaction begin"/>
<int value="2" label="Meta table init"/>
<int value="3" label="Create tables"/>
<int value="4" label="Version check and migration (check History.MigrateFailureToVersion)"/>
<int value="5" label="Commit"/>
</enum>
<enum name="HistoryPageView"> <enum name="HistoryPageView">
<int value="0" label="History"/> <int value="0" label="History"/>
<int value="1" label="Grouped Week (Obsolete Feb. 2017)"/> <int value="1" label="Grouped Week (Obsolete Feb. 2017)"/>
...@@ -24028,6 +24028,20 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries. ...@@ -24028,6 +24028,20 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries.
</summary> </summary>
</histogram> </histogram>
<histogram name="History.AttemptedToFixProfileError">
<owner>brettw@chromium.org</owner>
<summary>
Logged whenever history database initialization fails. The frequency of
logging will tell you the total failure rate. True indicate that we think
the database is non-recoverable and it will be deleted. It should be
re-created the next time Chrome is started. False indicates we think the
error is temporary (like out of disk space or file locked). Chrome will run
with no history database and nothing will be done to try to fix the error.
See History.InitializationFailureStep for which particular step of history
initialization failed.
</summary>
</histogram>
<histogram <histogram
name="History.ClearBrowsingData.HistoryNoticeShownInFooterWhenUpdated" name="History.ClearBrowsingData.HistoryNoticeShownInFooterWhenUpdated"
enum="BooleanShown"> enum="BooleanShown">
...@@ -24265,6 +24279,16 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries. ...@@ -24265,6 +24279,16 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries.
</summary> </summary>
</histogram> </histogram>
<histogram name="History.InitializationFailureStep" enum="HistoryInitStep">
<owner>brettw@chromium.org</owner>
<summary>
The phase of history initialization that failed. This histogram is only
logged on database initialization failure.
History.AttemptedToFixProfileError will tell how often initialization fails
overall.
</summary>
</histogram>
<histogram name="History.InMemoryDBItemCount"> <histogram name="History.InMemoryDBItemCount">
<owner>gab@chromium.org</owner> <owner>gab@chromium.org</owner>
<summary> <summary>
...@@ -24389,6 +24413,15 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries. ...@@ -24389,6 +24413,15 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries.
</summary> </summary>
</histogram> </histogram>
<histogram name="History.MigrateFailureFromVersion">
<owner>brettw@chromium.org</owner>
<summary>
History database version from which history migration failed. If there are
higher than normal migration failures, this histogram will indicate which
migration step failed.
</summary>
</histogram>
<histogram name="History.MonthlyHostCount"> <histogram name="History.MonthlyHostCount">
<owner>shess@chromium.org</owner> <owner>shess@chromium.org</owner>
<summary> <summary>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment