Categories
Mozilla

Changes to how Places stores data incoming

Sometime soon after the beta 8 code freeze, the Places team will be merging the Places branch into mozilla-central. There are a lot of changes we’ve been working on, the most important of which is some major re-architecting how we store data.

The Benefits

The work on the Places branch brings us a number of benefits. In general, we’ve parallelized work, and made it substantially less likely that we’ll block on the GUI thread. Some of the important fixes we have landed are:

  • Faster Location Bar
    The location bar is faster because other database work no longer blocks us from searching, and the queries are much simpler.
  • Asynchronous Bookmark Notifications
    Indicating if the current page is bookmarked in the location bar (with the star) is now an asynchronous operation that does not block the page load.
  • Faster Bookmarks & History Management/Searches
    Simpler queries and other improvements should make this all work faster.
  • Faster Link Coloring
    Link coloring is now executed on a separate database connection so it cannot block other database work.
  • Expiration Work
    Less work at startup, less work at shutdown, and less work when we run expiration.
  • Less Data Stored
    Embedded pages are now tracked only in memory and never hit the disk.
  • Better Battery Management
    Much less work during idle time, which will improve our power consumption behaviors.
  • Fixes 29 blockers and 18 other issues

A bit of History

Way back in the days leading up to Firefox 3.5, we moved from storing all of our history and location data in disk tables to in-memory tables that we’d flush out to disk every two minutes off of the GUI thread. The benefit of this was two-fold:

  1. No longer performing the vast majority of our disk writes on the GUI thread
  2. No longer performing the vast majority of our fsyncs/Flushes on the GUI thread

More details about how we came up with this solution can be found in a series of blog posts.

The Problem

This solution has worked out pretty well for us for a while, but recently, especially on OS X, it has not been. The short story is that our architecture did not scale well due to lock contention between our GUI thread and our background I/O thread. While the common case access case may be fine, the failure case (when we hit lock contention) is pretty terrible. The problem is so terrible that I once described it like this:

the failure case makes us fall on our faces, skid about 100 feet, and then fall off a cliff without a parachute.

Ultimately, the only way we can avoid this situation is to not do any database work on separate threads with the same database connection. It was not an issue in the past because we just did not do enough work on the I/O thread, but as we have added to the workload of that thread, we increase the likelihood of it holding the lock, which means there is a higher probability that the GUI thread will not be able to instantly acquire the lock and do whatever it needs to do. This essentially leaves us with two options:

  1. Move the rest of our database work off of the GUI thread.
  2. Move database work from the I/O thread back to the GUI thread.

The Solution

The second choice is not actually a viable option. Disk I/O completes in a non-deterministic amount of time, which is why we have been moving it from the GUI thread to an I/O thread since Firefox 3.5. The first choice is not entirely viable either due to schedule constraints either (we have tons of API calls that are not used heavily but still synchronous). A hybrid solution exists, however. We can reduce the amount of work we do on the I/O thread by using additional I/O threads. Additionally, we can move the remaining synchronous operations during browsing to an I/O thread. In the end, Places ends up with one read/write thread, and multiple read-only threads.

This wasn’t really an option back in the Firefox 3.5 days because in SQLite readers and writers blocked each other. However, the SQLite developers recently devised a new journaling method called WAL that lets readers not block writers, and writers not block readers. When the Places branch merges into mozilla-central, we will end up with three read-only I/O threads and our original read-write I/O thread. The three read-only threads are used for location bar searches, visited checks (is a given hyperlink visited), and some bookmark operations. Each I/O thread has its own connection to the database, allowing operations to happen in parallel (SQLite is only threadsafe because it serializes all access on each connection object, which is why we had the lock contention in the first place).

Performance Test Issues

One of the things that made this work especially difficult is seemingly random changes in performance numbers. We often had regressions suddenly appear (according to talos) on changesets that would have zero impact on performance, and then backing out the change would cause an additional regression. Other times, when we would merge mozilla-central into Places, we would suddenly get new regressions when comparing to mozilla-central. This could be indicative of a bad interaction with our code and the changes on mozilla-central, however after looking at the changes on mozilla-central that landed with the merge, that appeared to be highly unlikely.

I’m also quite certain that some of our performance tests do not actually test/measure what we actually want to test/measure. I’ll leave that discussion to a future blog posts, however.

Categories
Mozilla

Startup Time in the Wild

Over the weekend I spent some serious time with my computer running a bunch of tests with standalone talos in 11 different situations. First, a disclaimer: these tests were only designed to give some insight on the areas we should focus on for the goal. Each of these tests was reproduced at least once before I moved onto the next one in order to make sure the numbers were stable.

The Tests

  • Clean profile. This is just the standard profile that we normally run Ts with on tinderbox. This is basically used a baseline for best possible performance.
  • Dirty profile. This is actually my daily profile, with eight tabs that will open through session restore during startup. Because of how talos works, these tabs don’t all have to load for the number to be generated. Even so, you’ll notice a substantial slowdown. Sadly, I fear I modified the profile I was using in a bad way because I can no longer reproduce the numbers I got (but the numbers recorded were reproduced four times before I moved on to the rest of the tests initially).
  • Bookmarks toolbar disabled. This is a variation on the dirty profile test that just disables the bookmarks toolbar.
  • No places. This is a variation on the dirty profile test that removes places files from the profile.
  • No sessionstore.js. This is a variation on the dirty profile test that removes sessionstore.js from the profile. This has the side effect of also not making the eight tabs load at startup.
  • No urlclassifier. This is a variation on the dirty profile test that removes the urlclassifier related files from the profile.
  • No cookies.sqlite. This is a variation on the dirty profile test that removes cookies.sqlite from the profile.
  • No extensions. This is a variation on the dirty profile test that removes all add-on manager bits in the profile.
  • No formhistory.sqlite. This is a variation on the dirty profile test that removes formhistory.sqlite from the profile.
  • No downloads.sqlite. This is a variation on the dirty profile test that removes downloads.sqlite from the profile.
  • No content-prefs.sqlite. This is a variation on the ditry profile test that removes content-prefs.sqlite from the profile.

Results

I’m going to let some graphs do the talking here. The first shows the raw test run data (which isn’t terribly interesting). The second compares the reported startup time for each test. You will probably want to click to zoom in.

Data of the startup time runs

Startup time of the various tests

Conclusions

It looks like the best wins that we can get are related to fixing session restore to not scale linearly with the number of tabs it is restoring, and reduce the startup time costs of loading places files and cookies.sqlite. It should be noted that this test was not measuring the load time for each tab, so something like BarTab would not help in this case. The other good news is that we already have work underway to make cookies.sqlite load time not hurt us so much during startup.

Categories
Mozilla

Asynchronous Location Bar has Landed

About two weeks ago the asynchronous location bar work landed in mozilla-central without much issue. It’s also in the Firefox 3.6 alpha we just recently released. This has the potential to impact all of our users, but those on slower hard drives will notice this the most. Your location bar searches may not complete any faster than before, but they certainly won’t be hanging your browser and locking up the UI.

Background

We’ve been getting reports for some time about the location bar hanging the application for some users when they are typing in it. This wasn’t a problem that was reproducible on every machine, and even on machines that saw it, it wasn’t always 100% reproducible. Clearly, this behavior is not desirable, so we set out to fix it.

I had a theory to the cause almost a year ago and filed a bug that I was hoping we could work on and fix for Firefox 3.5. We knew that reading data off a disk can be slow (and certainly would complete in a non-deterministic amount of time). Since SQLite uses blocking read calls (no more code can execute until the data is read from disk), this could certainly be the cause of the slowdown our users were seeing. Some simple profiling showed that this was largely the cause of the hanging. Work began on the project, but it was clear that enough issues were cropping up that we were not going to be able to safely take this change for Firefox 3.5, and resources were diverted elsewhere.

Process and Solution

This section is a bit technical, so feel free to skip it. The short answer is “do not block the main thread while reading from the hard drive.”

In order to not block the main thread while reading from disk we either need to make SQLite use non-blocking read system calls, or call into SQLite off of the main thread. Changing the SQLite code isn’t something we want to do, so that solution was out of the question. Luckily, we had solved a similar problem with writes and fsyncs earlier in the Firefox 3.5 development with the asynchronous Storage API.

The first implementation that we tried essentially did the same thing that the old code did. We would execute a query, but this time asynchronously, and then process the results and see if they match. There were two issues with this approach, however. The first issue was that we were filtering every history and bookmark entry on the main thread for a given search. That could be a lot of work we end up doing, and with the additional overhead of moving data across threads, the common case would see no win. The second issue was that once we selected a result in the location bar, and a search was not yet complete, there would be a hang as the main thread processed a bunch of events that Storage had posted to it containing results.

At this point, we realized we needed to do the filtering on a thread other than the main thread. After some thought, we was figured that the easiest way to do that would be to use a SQL function that we define in the WHERE clause of our autocomplete queries. This way, all the filtering is done on a background thread, and the code that runs on the main thread only deals with results we will actually use. This solution exposed some things in the Storage backend like lock contention and a few other subtle issues, but nothing major came up.

For more details on how the location bar search results are generated, see my explanation here.

If you weren’t having a problem before, chances are you won’t notice any difference at all.

Categories
Mozilla

Test Build: Asynchronous Location Bar (Take >2)

I have another test build for folks to try out. This fixes a possible error condition that could happen in certain circumstances. This build has two known issues:

  1. There is a lot of flickering when new results show up. This is being tracked in bug 393902.
  2. Your computer will hang for a period of time (it will become responsive again) if you continue to type once no results are found. This is being tracked in bug 503701.

This is built off of a “stable” point of mozilla-central, so it’s like using a 3.6 nightly. All the normal warnings apply about using it. I’m told this greatly increases the speed at which results obtained by many people. If you experience any issues (other than the two listed here), please let me know! The builds can be found here.

Categories
Mozilla

Test Build: Asynchronous Location Bar

It’s been a while, but last week I started working on the asynchronous location bar patch again. The add-on from past posts will not be updated as there are binary changes that need to be made. I do, however, have some test builds that you can try out. These are built of off mozilla-central, so it’s like using a 3.6 nightly. That means it may not be stable, and you shouldn’t use this with your default profile (but feel free to copy your places.sqlite file into a new one to really hammer on it). If you are interested in trying it out, the builds can be found here.

Your feedback is appreciated!