Caching Thumbnails in Mugshot

Currently, Mugshot automatically downloads at least two sizes for every single photo that a user try to view: a thumbnail, and a square version. I say “at least”, because although it does not currently download any larger sizes by default, it soon will. (I’m adding some features - that you can disable if you want - that will require larger sizes.) Because of the way Mugshot caches these image sizes to disk, attempting to download anything besides just Thumbnail and Square will bring your internet connection to a halt.

The current caching implementation in Mugshot is what I call the “shotgun approach”. When you click on an item, Mugshot asks Flickr for a list of photos in that collection - whether you click on a contact (”get me a list of all the photos in that contact’s photostream”), a photoset (”get me a list of all the photos in that set”), a group (”get me a list of all the photos in that group’s pool”), etc. Once Mugshot has that list of photos, it converts that photo data into CoreData objects. For every single “Photo” object in CoreData, if Mugshot doesn’t see cached files on disk, it creates a special handler and tells it to cache files for that photo in a new thread. Mugshot hands off control to that other thread, and then effectively forgets about it. While this is happening, Mugshot will show you the “Loading…” graphic for each photo. Here are the problems with this approach:

  • Mugshot kicks off these threads one at a time, without really caring or tracking how many it has kicked off. Although this has the benefit of requiring very little code, it also makes it difficult for Mugshot to provide status information - how many photos have been downloaded? How many left to go?
  • Thread storm! Although having multiple threads keeps things responsive, remember that Mugshot will kick off a thread for every photo. So if you click on your photostream, and you have 800 photos, Mugshot will eventually have 600 or 700 threads - all trying to download several small images. Although the main app remains responsive, trying to load thumbnails for this many photos is counterproductive - and in extreme cases, the downloads time out, resulting in empty files, which Mugshot isn’t smart enough to deal with.
  • Let’s say that you clicked on a photoset, took a quick look, realized the photo you want is in another set, so you click on another. Mugshot can’t stop the original 100 threads from loading, so it doesn’t - it just kicks off more in order to show you the new thumbnails. Although Mugshot is technically still responsive (i.e. no spinning beach ball), it will take a long time to start showing you the new thumbnails because the original set are still tying up your internet connection.
  • The bigger the files, the worse all of these problems become. For example, I am adding features that will require caching the “Small” and possibly the “Medium” photo sizes from Flickr. Each of these is, on average, about twice the size of the next smallest image. (i.e. “Medium” is, on average, twice as large as a “Small”, which is about twice the size of a “Thumbnail”). The code that caches these files plays a few tricks to try and load these larger sizes later - trying to make sure that thumbnails get loaded ASAP so the user can see them. But, given the other drawbacks above, these tricks fall apart pretty quickly.

So what does this all mean? Well, first it means that the simple, “shotgun approach” code made it easy for me to get Mugshot up and running quickly. It also means that under small loads and fast internet connections (less than 300 photos in a set, and using a cable modem), Mugshot usually responds very well - I could put off writing a better caching system until another day. It also means that “another day” is here.

Based on some of the drawbacks above, and based on how I’m seeing people use Mugshot, I started designing the new caching system by writing down the rules and guidelines it should follow:

  1. Mugshot should always know - and be able to tell the user - exactly how many photos it has cached from the current view, and how many photos it still has left to go. As a corollary, it should attempt to cache photos that are directly in view (as opposed to outside the scroll area) first.
  2. Mugshot should always try to show users thumbnail and square versions first. These are the first images used in the UI and are most critical to a smooth, responsive look and feel.
  3. When the user navigates to another set of photos before the current set has been cached, the new set should take priority. Again, following the above rule that Thumbnails and Squares are the most important sizes.
  4. Larger sizes are always lowest priority. (Which larger sizes to download is controlled by other features in Mugshot and whether they’re in use - this will continue to be the case.)
  5. The new system should never start caching a lower priority photo (determined by the above rules) if a higher priority item exists. (i.e. Mugshot should never start caching a thumbnail for a photo the user isn’t currently viewing while a thumbnail needs to be downloaded for a photo that’s currently in view).

The theme of all these rules is that Mugshot should put responsiveness and usability FIRST. I’ve spent most of today re-designing the caching system to follow these rules. It uses NSURLDownload and preferences control how many concurrent downloads it will attempt. I use this same code for caching and downloading in several areas of the app, so these updates should provide a lot of useful speedups. Also, using NSURLDownload means I can probably have a more responsive system without creating (and trying to coordinate) multiple threads.

What do you think? Are the “rules” reasonable? Have you tried Mugshot? Is there something I’m missing?

6 Responses to “Caching Thumbnails in Mugshot”


  1. 1 heipei Jan 12th, 2006 at 4:57 pm

    sounds great to me. i also think that responsiveness is your number one priority. and app can be really good, if its slow, i for myself quickly kick it. i dont mind that it takes a while to load pictures, but if the whole app comes to a slow down during the process, i get impatient and try to close it ;)

    anywho, id still like to see the source some time, or are you planning on making this commercial? (hope not!)

  2. 2 Pete Jan 12th, 2006 at 6:06 pm

    Those rules sound reasonable to me… Seems like it would be straighforward to create what would essentially be a cache manager class which would manage the downloads for you as well as providing priority scemantics.. That way you could submit jobs to your cache manager with a priority, it would track all of the jobs in the queue and ensure that the highest priority downloads happen first… Fire and forget threading works up to a point but without some way of throttling the threads you spawn you’ll quickly bring your system to its knees and impact responsivenes…

    Have fun! :)

    p

  3. 3 blake Jan 12th, 2006 at 6:15 pm

    :) That’s pretty much exactly wat I’ve got, Pete. I already had a CacheManager class where I encapsulated the knowledge of how to take some photo data and figure out what the cache filename was and where it was on disk. It works both ways - it will fetch data from the cache for you, and save data to the cache for you. I’m just adding to that functionality - adding priorities, etc.

  4. 4 blake Jan 12th, 2006 at 6:16 pm

    heipei,
    This will not be a commercial app. And, I will release the source at some point. Right now I just have no bandwidth to document how my internal flickr kit works - and I’m hesitant to just put it out there without any documentation whatsoever.

  5. 5 Maxim Blinder Jan 12th, 2006 at 9:09 pm

    your approach sounds good to me.

    while this is a bit off topic, have you get a roadmap or a proposed feature set/milestones for the app?

    thanks for developing it, it’s got enormous potential!

  6. 6 blake Jan 12th, 2006 at 11:45 pm

    Good question. The answer is “sort of”. I’ve got some general ideas. Since I’m still getting my feet wet with Cocoa, I still have a hard time gauging how long some things will take. Because of that, it’s probably not wise to commit to anything yet. Let me get to a point that I feel comfortable with - I’lll stamp it 1.0 and then I’ll probably start talking about future features.

Comments are currently closed.