Flickr Engineers Do It Offline

It seems that using queuing systems in web apps is the new hottness . While the basic idea itself certainly isn’t new, its application to modern, large, scalable sites seems to be. At the very least, it’s something that deserves talking about — so here’s how Flickr does it, to the tune of 11 million tasks a day.

But first, a use case! Every time you upload a photo to Flickr, we need to tell three different classes of people about it: 1) You, 2) Your contacts, 3) Everyone else. In a basic application, we’d have a table of your photos and a table containing people who call you a contact. A simple join of these tables, every time someone checks the latest photos from their contacts, works fine. If you’re browsing around everyone’s photos or doing a search, you can just check the single photos table.

Obviously not going to fly here.

For scale, Flickr separates these three lookups into three different places. When you upload that photo, we immediately tell you about it, and get out of your way so you can go off and marvel at it. We don’t make you wait while we tell your contacts about it, insert it into the search system, notify third-party partners, etc. Instead, we insert several jobs into our queueing system to do these steps "later". In practice, every single one of these actions subsequently takes place within 15 seconds of your upload while you’re free to do something else.

Currently, we have ten job queues running in parallel. At creation, each job is randomly assigned a queue and inserted — usually at the end of the queue, but it is possible for certain high-priority tasks to jump to the front. The queues each have a master process running on dedicated hardware. The master is responsible for fetching jobs from the queue, marking them as in-process, giving them to local children to do the work, and marking them as complete when the child is finished. Each task has an ID, task type, arguments, status, create/update date, retry count, and priority. Tasks detect their own errors and either halt or put themselves back in the queue for retry later, when hopefully things have gotten better. Tasks also lock against the objects they’re running on (like accounts or groups) to make sure that other tasks don’t stomp on their data. Masters have an in-memory copy of the queue for speed, but it’s backed by mysql for persistent storage, so we never lose a task.

There are plenty of off-the-shelf popular messaging servers out there . We wrote our own, of course. This isn’t just because we think we’re hot shit, there are also practical reasons. The primary one was that we didn’t need yet another piece of complicated architecture to maintain. The other was that for consistency and maintainability, the queue needed to run on the exact same code as the rest of the site (yes, that means our queuing system is written in php). This makes bug fixes and hacking easier, since we only need to do them in one place. So every time we deploy , the masters detect that, tell all their children to cleanly finish up what they’re doing, and restart themselves to take advantage of the latest and greatest.

It’s not just boring-old notifications, privacy changes, deletes, and denormalization calculations for our queue, no. The system also makes it easy for us to write backfills that automatically parallelize, detect errors, retry, and backoff from overloaded servers. As an essential part of the move-data-to-another-cluster pattern and the omg-we’ve-been-writing-the-wrong-value-there bug fix, this is quickly becoming the most common use of our queue.

The Flickr engineering team is obsessed with making pages load as quickly as possible. To that end, we’re refactoring large amounts of our code to do only the essential work up front, and rely on our queueing system to do the rest.

Cal Henderson Tuesday

We’re taking a break from Kittens this week. Mainly because I’m on Paternity Leave (see Kitten Tuesday for details) and therefore don’t have all week at work to select a suitable kitten, and no-one else on the engineering team is kitten qualified enough.

So instead I give you our very own Cal Henderson (the fellow who has kept Flickr from crashing all these years) presenting at djangocon, on the subject of “Why I Hate Django”.

We don’t use Django here at Flickr (because Cal hates it of course) but we do do a lot of scaling, and not only does Cal give a bit of insight into scaling and how things work here at Flickr, but he also talks about kittens a bit, which is nice.

So if you have an hour to spare (and frankly if you work with the internets you probably do) this is worth a watch…

[Link for RSS readers:]

Kitten Tuesday – Office Hygiene Edition

Our very own frontend engineering, scrumjaxing Scott (tagged in our backend system as; "dj, flickr, javascript, super star") sez …

And for my next trick, a keyboard in the dishwasher!

"Correcting many months of breakfast bagel poppyseeds, crumbs and the occasional coffee drip; these things were sustainable events, but a beer spill was the thing that finally put it out of commission."

Setting aside the whole how do you get beer spilt into your office keyboard question* , Scott, if you’re going to clean your keyboard perhaps we should get one of these for the office instead …

Keyboard cleaning

… for a far more thorough solution?

Photos by .schill and Mrs eNil

* Answer, because that’s how we roll at flickrhq.

Who’s On First?

Untitled World View #1215585993

Normally we don’t talk about upcoming features but in recent weeks we’ve been hard-pressed to keep a lid on our renewed excitement for kittens. We’re betting 2009 will be a big year for kittens and it is why Dan and Heather are taking so much time to hash out the details for the new flickr.kittens API framework which we’re confident will re-embiggen-ize the how, the what and the why of photo-sharing!

To pass the time, until then, I’m going to talk about some of the geo-related API methods that have been released in the last few months, but perhaps not properly explained in detail and introduce a new minty-fresh method which hasn’t been discussed at all!

Cake for breakfast

First, the new stuff.

Places for a user

We’ve added a new authenticated method to the flickr.places namespace called flickr.places.placesForUser which returns the top 100 unique places, scoped by place type, where a user has geotagged photos. For example, I’ve geotagged photos in the following countries:

# ?method=flickr.places.placesForUser&place_type=country

<places total="7">
	<place place_id="4KO02SibApitvSBieQ" woeid="23424977"
		latitude="48.890" longitude="-116.982"
		place_url="/United+States" place_type="country"
		photo_count="1264">United States</place>
	<place place_id="EESRy8qbApgaeIkbsA" woeid="23424775"
		latitude="62.358" longitude="-96.582"
		place_url="/Canada" place_type="country"

	<place place_id="3s63vaibApjQipWazQ" woeid="23424950"
		latitude="39.895" longitude="-2.988"
		place_url="/Spain" place_type="country"
	<place place_id="6immEPubAphfvM5R0g" woeid="23424819"
		latitude="46.712" longitude="1.718"
		place_url="/France" place_type="country"
	<place place_id="DevLebebApj4RVbtaQ" woeid="23424975"
		latitude="54.313" longitude="-2.232"
		place_url="/United+Kingdom" place_type="country"
		photo_count="34">United Kingdom</place>
	<place place_id="mSCQNWWbAphdLH6WDQ" woeid="23424812"
		latitude="64.950" longitude="26.064"
		place_url="/Finland" place_type="country"

	<place place_id="mpa01jWbAphICsyCsA" woeid="23424853"
		latitude="42.502" longitude="12.573"
		place_url="/Italy" place_type="country"

The response format is (almost) like all the other places methods, which I guess makes it a standard places response though we haven’t gotten around to standardizing it like we have with photos. Places responses will always contain a “place ID” and a “WOE ID”, a “latitude” and a “longitude”, a “place type” and a “place URL” attribute. They usually contain an “accuracy” attribute but it doesn’t make any sense in the a list of places for a user since the photos, clustered by place type, may have been geotagged at multiple zoom levels. In this example, we’ve also added a “photo count” attribute since that’s an interesting bit of information.

The list of place types with which to scope a query by is limited to a subset of the place types in the Flickr location hierarchy, specifically: neighbourhoods, localities (cities or towns), regions (states) and countries. While place_type is a required argument for the method, there are two other optional parameters you can use to filter your results.

Places for a user (and a place)

The first is woe_id and ensures that places of a given type also have a relationship with that WOE ID. For example, these are all the localities for my geotagged photos taken in Canada (WOE ID 23424775):

# ?method=flickr.places.placesForUser&place_type=locality&woe_id=23424775

<places total="5">
	<place place_id="4hLQygSaBJ92" woeid="3534"
		latitude="45.512" longitude="-73.554"
		place_url="/Canada/Quebec/Montreal" place_type="locality"
		photo_count="221">Montreal, Quebec</place>
	<place place_id="63v7zaqQCZxX" woeid="9807"
		latitude="49.260" longitude="-123.113"
		place_url="/Canada/British+Columbia/Vancouver" place_type="locality"
		photo_count="59">Vancouver, British Columbia</place>
	<place place_id="zrCws.mQCZj_" woeid="9848"
		latitude="48.428" longitude="-123.364"
		place_url="/Canada/British+Columbia/Victoria" place_type="locality"
		photo_count="9">Victoria, British Columbia</place>

	<place place_id="WzwcDQCdAJsL" woeid="4177"
		latitude="44.646" longitude="-63.573"
		place_url="/Canada/Nova+Scotia/Halifax" place_type="locality"
		photo_count="3">Halifax, Nova Scotia</place>
	<place place_id="XlQb2xedAJvr" woeid="4176"
		latitude="44.673" longitude="-63.575"
		place_url="/Canada/Nova+Scotia/Dartmouth" place_type="locality"
		photo_count="1">Dartmouth, Nova Scotia</place>

(Most) places for a user (and a place)

The second optional parameter is threshold which requires a place have a minimum number of photos associated with it in order to be included in the result set. Any place that falls below the threshold will be rolled-up in to its parent location. For example, if we search for localities in Canada but also a minimum threshold of 5 photos per location the towns of Halifax and Dartmouth are rolled up in to the province of Nova Scotia:

# ?method=flickr.places.placesForUser&place_type=locality&woe_id=23424775&threshold=5

<places total="4">
	<place place_id="4hLQygSaBJ92" woeid="3534"
		latitude="45.512" longitude="-73.554"
		place_url="/Canada/Quebec/Montreal" place_type="locality"
		photo_count="221">Montreal, Quebec</place>

	<place place_id="63v7zaqQCZxX" woeid="9807"
		latitude="49.260" longitude="-123.113"
		place_url="/Canada/British+Columbia/Vancouver" place_type="locality"
		photo_count="59">Vancouver, British Columbia</place>
	<place place_id="zrCws.mQCZj_" woeid="9848"
		latitude="48.428" longitude="-123.364"
		place_url="/Canada/British+Columbia/Victoria" place_type="locality"
		photo_count="9">Victoria, British Columbia</place>
	<place place_id="QpsBIhybAphCEFAm" woeid="2344921"
		latitude="44.727" longitude="-63.587"
		place_url="/Canada/Nova+Scotia" place_type="region"
		photo_count="4">Nova Scotia, CA</place>

Note that we only roll up a single level so if, like the Halifax and Dartmouth, a locality gets rolled up in to its parent region it may still have fewer photos associated with it than the threshold you passed with the method call. If you think this is crazy talk and/or have some other use case we haven’t considered with this model tell us why and we can revisit the decision.

Untitled Relationship #1219894402

Finally, as mentioned above the flickr.places.placesForUser method requires that you include an auth token with minimum read permissions. As always, please take extra care to respect people’s senstitivies when it comes to location data and, above all, don’t be creepy.

Meanwhile, back at the Ranch

About a week ago, I was asked whether it was possible to use the Flickr API to get a feed of geotagged photos (for a particular place/radius) sorted by interestingness, filtered by CC license to which I quickly replied: WOE IDs, the flickr.places APIs and the “radius” parameter in the method should get you what you need! (Also the API responses as syndication feeds support, if you’re being finnicky about the question.)

Which got me thinking that while we’ve told people about WOE IDs and the Places API we haven’t really made a lot of noise about the addition of radial queries and the has_geo flag to the method. We’ve mentioned it in passing, here and there, but never really tied it all together. So, let’s start with WOE IDs:

WOE (short for Where On Earth) IDs are the unique identifiers in the giant database of places that we (and FireEagle and the rest of Yahoo!) use to keep track of where stuff is. They are also available to you and the rest of the Internets via the public GeoPlanet API. In Flickr, every geotagged photo has up to (6) associated WOE IDs representing a neighbourhood, locality, county (if present), region, country and continent.


Using the example above, you could start with an API call to the flickr.places.find method which is like an extra-magic geocoder: It not only resolves places names to latitude and longitude coordinates but also returns the WOE ID for the place that contains it. Searching for “San Francisco CA” returns WOE ID 2487956 which you can use to call the method asking for all the photos geotagged in the city of San Francisco sorted by interestingness with a Creative Commons Attribution license. Something like this:

# ?method=flickr.places.find&query=San+Francisco+CA

<places query="San Francisco CA" total="1">
	<place place_id="kH8dLOubBZRvX_YZ" woeid="2487956" latitude="37.779"
       	longitude="-122.420" place_url="/United+States/California/San+Francisco"
       	place_type="locality">San Francisco, California, United States</place>

# ? 
# 	&woe_id=2487956&extras=tags,geo,license

<photos page="1" pages="289" perpage="100" total="28809">
	<photo id="145874931" owner="37996593020@N01" secret="b695138626" server="52"
       	farm="1" title="bridge" ispublic="1" isfriend="0" isfamily="0"
       	tags="sanfrancisco bridge water night reflections geotagged lights
       	baybridge geolat3779274 geolon12239096 sfchronicle96hours
       	sanfranciscochronicle96hours" latitude="37.79274" longitude="-122.39096"
       	accuracy="16" license="4"
		place_id="kH8dLOubBZRvX_YZ" woeid="2487956"/>

	<!-- and so on -->

Reverse geocoding

You can also lookup the WOE ID for any set of latitude and longitude coordinates. Imagine that you are standing in front the infamous Wall of Rant in San Francisco’s Mission district and you’d like to see photos for the rest of the neighbourhood.

Picture 1

If you know your geo coordinates you can call the flickr.places.findByLatLon method to reverse geocode a point to its nearest WOE ID which can then be used to call the trusty method. Like this (without the part since it’s basically the same as above):

# ?method=flickr.places.findByLatLon&lat=37.752969&lon=-122.420844

<places latitude="37.752969" longitude="-122.420844" accuracy="16" total="1">
	<place place_id="C.JdRBObBZkMAKSJ" woeid="2452334"
	    latitude="37.759" longitude="-122.418"


But what if you just want to see photos nearby a point? Radial queries, recently added to the method, allow you to pass lat and lon parameters and ask Flickr to “show me all the photos within an (n) km radius of a point”. You have always been able to do something like this using the bbox parameter but radial queries differ in two ways:

  1. They also save you from having to calculate a bounding box which is, you know, boring.
  2. Results are sorted by distance from the center point (you can override this by setting the sort parameter). Trying to do the same with a bounding box query would mean fetching all the results first and then sorting them which both expensive and boring and breaks the pagination model.

Radial queries are not meant for pulling all the photos at the country or even state level and as such the maximum allowable radius is 32 kilometers (or 20 miles). The default value is 5 and you can toggle between metric and imperial by assigning the radius_units parameter a value of “km” or “mi” (the default is “km”).

# ? 
# 	&radius=1&extras=geo,tags&min_taken_date=2008-09-01+00%3A00%3A00

<photos page="1" pages="1" perpage="100" total="9">
	<photo id="2820548158" owner="29072902@N00" secret="b2fc694880" server="3288"
		farm="4" title="20080901130811" ispublic="1" isfriend="0" isfamily="0"
		latitude="37.751166" longitude="-122.418833" accuracy="16"
		place_id="C.JdRBObBZkMAKSJ" woeid="2452334"
		tags="moblog shinobu"/>

	<!-- and so on -->

See that min_taken_date parameter? Like all geo-related query hooks in the method you need to pass some sort of limiting agent: a tag, a user ID, a date range, etc. If you insist on passing a pure geo query we will automagically assign a limiting agent of photos taken within the last 12 hours.

(I can) has geo

Finally (no, really) if you just want to keep things simple and use a tag search you can also call the method with the has_geo argument which will scope your query to only those photos which have been geotagged. For example, searching for all geotagged photos of kittens taken in tokyo:

# ?,kitten&tag_mode=all 
# 	&has_geo=1&extras=tags,geo

<photos page="1" pages="1" perpage="100" total="60">
	<photo id="2619847035" owner="27921677@N00" secret="0979aed596" server="3011"
       		farm="4" title="Kittens in Akiba" ispublic="1" isfriend="0" isfamily="0"
       		tags="japan cat tokyo kitten crowd akihabara unexpected"
		latitude="35.698263" longitude="139.771977" accuracy="16"
		place_id="aod14iaYAJ1rDE.R" woeid="1118370"/>

	<!-- and so on -->


Maps are purrrr-ty

Which is a nice segueway in to telling you that on Tuesday we turned on the map-love for
the city of Tokyo, in addition to Beijing and Black Rock City (aka Burning Man), using the Creative Commons licensed tiles produced by the good people at Open Street Maps. We’re pretty excited about this but rather than just showing another before and after screenshot we decided that the best way to showcase the new tiles was with… well, kittens of course!

Picture 1

Sophie’s Choice by tenaciousme


That’s a lot to digest in one go but we promise there won’t be a pop quiz on Monday. Hopefully there’s something useful for you in the twisty maze of possibilities, now or in an oh yeah, what about… moment in the future, and maybe even the seed for a brand new API application. Kittens!

Open! Hack! Day!

Once again Yahoo! is opening its doors to a horde of unwashed hackers. You should join us!

In case you missed last years Beck-filled extravaganza, Open Hack Day gives developers 24 hours to build a cool hack and demonstrate it to a packed audience and celebrity judges.

Better yet, some of the Flickr staffers will be on hand to answer any of your burning API questions and judge the Best Flickr Hack category (and yes, there are prizes).

Open Hack Day kicks off Friday with talks on Yahoo! APIs and technologies. Flickr-related talks include:

  • Getting Started with the Flickr API – Friday, Sept. 12 from 11:00am to 11:50am
  • Building a Purple Pedal GPS-Flickr Bike – Friday, Sept. 12 from 12:00pm to 12:50pm

It runs from Friday, Sept. 12 to Saturday, Sept. 13, at Yahoo! HQ in Sunnyvale, CA. If you’re interested, go to the Open Hack Day website to register.

Kitten Tuesday

Last Tuesday our "sister" site had "other ideas " about what Kitten Tuesday means. With a positive plethora of photos and videos they go totally to town with kittens, and why the heck not?

If you’re into an excess of artistic bourgeoisie I guess, meanwhile, over here on the Dev blog, we’re all about simplicity, efficiency and optimization. Where Kitten Tuesday is just that, a Kitten on Tuesday, otherwise we may as well have Furry Friday Fiesta.

So without further ado, A reader writes:

"We’re loving Kitten Tuesdays. Any chance you could consider JJ for a future appearance?"

Why of course, here’s JJ …

Photo by Chubby Bat