Abstracting CouchDB

Posted by on August 1, 2010
CouchCamp

Karel Minařík sent this to the user list. All the complaints are accurate and I don’t think they are specific to Ruby at all. I’m going to try and address why these issues exist, why they are unlikely to go away, and how developers can deal with them.

“plethora of gems”

The basic complaint here is that there are way too many CouchDB clients. This happens in pretty much every language. It’s just way too easy to write a CouchDB client. The REST API is pretty well defined and every language has support for HTTP already worked out.

Languages that have better support for HTTP will end up having more clients. There are 4 or 5 competing Python clients, some of them actually include their own HTTP clients because Python’s stdlib HTTP client is crap. Compare that with node, which is relatively young, and there are already 4 competing clients all using the HTTP support provided by node which is quite nice (if you said “but it doesn’t support keep-alive yet” I’d yell back “I’m working on it!”).

Even worse though, everyone is quite opinionated about which library you should use. This is similar to the situation with test frameworks in almost every language. There are a ton of test frameworks for any language you want to pick up and everyone is really opinionated about which one to use because it’s not very hard to write a test framework and anyone who has a complaint with the one they are using is free to go off and write their own. At the end of the day the best way to write tests is to use what ever tool provides the least “framework” putting your debugging closer to the tests and the system and not buried in the framework.

Having some diversity can be healthy but it’s also confusing for new users. Most databases have a one-off binary protocol they invented for talking to the database. This means that the implementors of the database usually provide a “driver” for each language and that usually serves as the “definitive” client library in that language. Others may build on top of that to provide ORM’s or better APIs but the underlying library is usually the same which means that if you learn it well you can easily debug and work with all the higher level clients.

CouchDB has a well defined API implemented by the CouchDB core committers available to every language that is close to the metal and provides well defined errors and responses to users, it’s the HTTP REST API. It’s definitive and well defined and needs very little abstraction if you have a good HTTP library. All the other libraries are just opinions and framework on top of that API. You can figure out what any library is actually doing by looking at the HTTP traffic with something like HTTPScoop.

I’m not saying you should never use a higher level abstraction, but if you want to really understand CouchDB and always be able to debug any higher level library you’re going to need to understand the REST interface as well as you understand the abstractions.

For me, I’m most comfortable with the REST API and when I write code that talks to CouchDB in node.js I just use my HTTP library, request.

Compatibility With The Rest of the World

There are two other sections Karel wrote before this one but the reasons for them are essentially the same.

We have a lot of libraries and frameworks that try to normalize between varied relational databases. We also have form libraries, authentication systems, and query libraries that expect database APIs to operate a certain way in order to integrate nicely.

CouchDB does not fit well in to those abstractions. For what it’s worth, neither does Redis or Riak or a bunch of other damn good new databases.

That’s because none of us are trying to make a “replacement” for relational databases. If you want a database that works well behind an ORM you should use a relational database. If your problems and all of your tools which you don’t want to put away fit the relational model there isn’t much need for you to be messing with CouchDB.

CouchDB is trying to be something better and different. We’re trying to create a database, and an application platform, that fits in to the problem space of the web better than relational databases which grew to fit problems they were not originally architected for.

You’re just never going to have a great experience using CouchDB behind ActiveRecord, or the Django ORM, or any other “generic” database API. Those abstractions work really well across a half dozen relational databases that have a similar structure and feature sets but with varied implementation details.

What you should want is a good API for CouchDB if you want to use and learn CouchDB. CouchDB has a very large feature set and very little of it doesn’t map well to abstractions originally built for relational databases. It also requires you to think about solving you problem differently. It takes a while to really internalize the couch way of doing things, nothing happens overnight, but once you do you’re quite happy and incredibly productive.

This also isn’t unique to CouchDB. Redis is fuckin’ awesome, but you wouldn’t have a good time trying to map your relational way of doing things to Redis. You’d think Redis was stupid and didn’t have the features you need. But really, you were trying to map not just your problem but your old way of solving your problem to Redis and Redis just isn’t for that. Neither is CouchDB.

We’re all trying to build better tools for building stuff on the web and we won’t be weighed down by integrating with the legacy of relational dominance in the space. If we did, we’d never be able to make something truly better or different and that’s what we need.

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • StumbleUpon
  • Technorati
  • Reddit
  • Slashdot
  • Twitter
  • HackerNews

NYC node.js meetup this Saturday, July 24th

Posted by on July 22, 2010
CouchCamp

I’m in New York for the next few weeks and Marak has helped organize a little node meetup.

venue: Hill Country

location: 30 West 26th Street NY, NY 10001

time: Saturday July 24th, 6pm

when arriving just tell the hostess “Javascript Party” and you will be seated with your fellow nerds.

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • StumbleUpon
  • Technorati
  • Reddit
  • Slashdot
  • Twitter
  • HackerNews

No more comments :(

Posted by on July 19, 2010
CouchCamp

The post I did on MongoDB Performance and Durability was incredibly popular.

The onslaught of comments it got stretched my WordPress instance. The page load time for that article slowed to a crawl and it was starting to look like I was in danger of getting some overage on my super cheap dreamhost account.

I’ve been working on a replacement for this blog for some time. Part of what I wanted to capture on the new blog is the discussion that happens around articles. For the most part, that discussion isn’t happening *on* the blog, it’s happening on Twitter and news.ycombinator and digg among other places.

Dealing with the immediate issue made me to start to evaluate how valuable the comment system on my blog actually was. The first 30 or 40 comments were pretty good but it basically devolved in to dual sided trolling. There are a lot of reasons for that, like allowing anonymous and the psycology of commenting on an individual’s blog but the important part is that the conversation continued and remained very civil and valuable on other sites. Everything that was covered on my blog comments were covered in more depth and in a better way on the news.ycombinator thread.

I decided the best thing to do to handle the immediate issues was just to remove comments from this blog. When my new blog code finally rolls out it will integrate a view of comments and discussion on Twitter, news.ycombinator and other sites where the conversation is actually happening instead of hosting comments in the blog itself. Until then, I would point everyone to the news.ycombinator thread if you’re missing the comments that were on the MongoDB durability post. People from 10gen answered all the issues people brought up very well and advocated their positions very clearly in that thread.

[UPDATE: Mathias has posted an archive of the comments from the MongoDB post.]

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • StumbleUpon
  • Technorati
  • Reddit
  • Slashdot
  • Twitter
  • HackerNews

Tech that is not tech

Posted by on July 16, 2010
CouchCamp

I was in the market the other day with my iPhone4 and some kid, probably 13 or 14, asked if he could play with it for a second because he was thinking of getting one. He commented on the screen and the feel of the device and asked if there were any games he could check out.

The whole experience felt odd to me. I had a really hard time understanding how you could actually evaluate a device this way because none of what he was doing exposed what I do the most on the device.

The reason I continue to buy Apple products is because the more I use them the less I notice them. They quickly become part of my life and I take them for granted. The magic mouse and two finger scrolling are probably the most noticeably intuitive things that I went from never using to not thinking about within minutes.

I used to really enjoy tinkering with my every day technology. I shirked almost anything that was easy for things that were harder and required me to constantly maintain them. I found it enjoyable and felt like I was really learning something.

I don’t know if it’s just because I got a little older, or because I started working so much with JavaScript and writing web stuff, but I can’t stand anything that is hard to use or requires me to maintain it in any way. I have plenty of work to do. On my laptop I’ve got TextMate and iTerm and a browser but anything else that is happening on the machine I don’t want to worry about. I actually find myself annoyed by System Updates.

My iPhone and my iPad are perfect. The best part about them is that there isn’t a terminal, it doesn’t stay on IRC or IM all day and allow people to bug me. But when someone sends me a message on twitter my phone looks the same as when some txt’s me. Whenever I think of something i need to look up I have it up on my iPhone before I’ve even realized I was looking it up.

This isn’t possible when there is friction in the interface or the design. You can’t take products for granted when you notice that you’re using them.

It’s kind of like this. I listen to a lot of podcasts. A few years ago the podcasts varied pretty greatly in recording quality. Now I don’t listen to *any* podcasts that aren’t recorded well. When you listen to something that isn’t recorded well part of your brain has to be working in the background to figure out what is being said. It’s a little more draining to listen to and you can’t to other things like cooking while you listen to it. Interfaces work the same way. When they are less simple and intuitive you have to spend a lot more energy working with them.

I’m not particularly attached to Apple as a brand I just require technology that isn’t technology once I start using it. Once I take can take it for granted it’s like water or air, it’s something I reach for and don’t have to think about. Open alternatives are great and I hope they continue to grow and polish but until I can use them and not think of them as technology but as a tool I won’t be able to adopt them.

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • StumbleUpon
  • Technorati
  • Reddit
  • Slashdot
  • Twitter
  • HackerNews

MongoDB Performance & Durability

Posted by on July 7, 2010
CouchCamp

This post has been coming for some time now. I am in love with this new world of a thousand databases. Whatever your use case is, there is a database for you. In the old days you figured out how to keep up MySQL or Postgres and that was the hammer you used to bang in every screw. Forget screwdrivers, that’s another thing you’d have to learn and it took you a long time just to figure out this hammer. Which is why I’m not all that surprised when everyone finds their new shiny screwdriver and are trying to bang on all their nails with it.

The database world has spent a long time reassuring you that a database is a place for you to put things permanently and that they won’t corrupt or degrade over time if you do things right. Remember when memcached wasn’t a database? That’s because you don’t put things in to memcached permanently. Now we call memcached a database, not because it changed, but because how we evaluate software to determine if it’s a database has changed. Databases are more dynamic about where they sacrifice durability and consistency for performance and the old requirements on durability aren’t the test we use to determine if a database is good for our use case, instead we have to understand our use case and decide what we need.

I’m going to pick on MongoDB because they have a cavalier attitude toward durability, in the pursuit of perceived performance, that isn’t clear to most of the developers that 10gen is encouraging to drop existing durable solutions (mostly MySQL and Postgres). I’m a CouchDB guy and I work on CouchDB stuff for a living but I’m going to talk a lot about Redis and Cassandra and even some relational databases because they help tell a bigger story about performance and durability in the larger world of databases that you couldn’t understand by just looking at CouchDB and MongoDB or just Postgres and MongoDB. This is from me, not my employer, as a database geek.

Request/Response

Redis is an in-memory data structure server. Think of it like a place on the network to stick your global objects so that you can scale your application servers horizontally sharing their state in Redis. It’s incredibly fast, necessarily, to handle it’s use case properly. By default Redis returns a response once it’s finished putting your write in to memory, in other words once it’s accessible to be read. Optionally you can set Redis to only return after it’s written to it’s append-only log (by log I mean an internal transaction log, most SQL databases do something similar, more on this later).

Cassandra has a long list of options you can use for what kind of assurances you want when you do a write which you can set on the client. These allow you to say things like “only return a response after you’ve written this data to 3 other nodes”. This is one of the things that they mean by “fully consistent”, you have assurances that your data is persisted to multiple nodes in a cluster if you like.

CouchDB has two options. One returns responses as soon as the document is available and does an fsync() every second (delayed_commits). But, we encourage people to run in production with delayed_commits off. This means that all the pending writes are fsync()’d and when that fsync() returns any other pending writes are flushed (this is sometimes referred to as a “group commit”) and the responses to the clients are only returned after the fsync() is finished. Under concurrent load running with delayed_commits off has the same throughput as fsync()ing every minute, the only difference is that the responses to the clients might return 10-100ms after but it’s probably better to have on-disc assurances and in the real world a write delay to a client that is sub-second isn’t a huge deal. Relational databases have very similar “group commit” and “delayed commit” features in their fsync() strategy.

MongoDB, by default, doesn’t actually have a response for writes. You just write your data to the socket and assume it’s going to be available when you try to read it. Under concurrent load you can’t expect to reliably store stateful data like session information like this. It’s kind of like, in your webapp, if you were to spawn a thread to do some work and at the end set some global but you return a response to the client immediately. Where there wasn’t any load you would be fine because your thread is faster than the roundtrip time to the client. But under heavy load the operations in the thread could take too long and a subsequent request wouldn’t have access to that global because it wasn’t set yet. This is why you can’t store session data reliably in MongoDB without changing the default client option to return a response, because no matter how “fast” it is if you send a response to the client (or no response at all) before the data is available for read it’s useless.

To people who don’t live in databases all day it’s hard to explain just how odd this choice is. I don’t know of another database that even allows you to return a response before the write data is accessible much less not even have a response by default. This is kind of like using UDP for data that you care about getting somewhere, it’s theoretically faster but the importance of making sure your data gets somewhere and is accessible is almost always more important.

It’s no secret that people are hitting IO concurrency issues with traditional Python and Ruby web frameworks. Solutions to IO concurrency problems are gaining traction; erlang, node.js, nginx, EventMachine, Tornado, and all of these technologies use at least some non-blocking IO to limit the amount of overhead a connection has. Languages like Ruby and Python traditionally use threads, or in some cases system processes, which have a static per-connection cost taken out of the available resources (usually available memory). Once you hit the upper limit of how many connections you can have open at once you have to start limiting the amount of time each connection is open if you want to continue to increase your requests per second. Not waiting for a response from MongoDB cuts down the overall connection time. This must seem like a silver bullet for people scaling their Rails or Django app but it’s entirely possible that under load your users aren’t actually seeing the data they write or are having to hit refresh in order to finish their login a few seconds after the session data is actually available.

Durability

After many many years of database engineering most databases have come to the same conclusion. Using an append-only file is the preferred, sane and most assured way to handle data loss or corruption. This is hard for a lot of people to understand but it’s not just a possibility that a write to disc might fail, it’s a guarantee that some time in the life of the database it will fail. Programs crash, discs have issues, computers are not perfect machines, at some point in the life of your database there will be a problem.

SQL databases tend to keep an append-only “log”, a sequential list of every transaction on the database. This way it can always be replayed to recover from corruption. Redis keeps a similar log in an append-only file. CouchDB actually uses an append-only btree on disc for it’s entire database removing the need for a traditional “log”.

The trick to append-only file formats is to write a “header” to the end of the file after every operation. If a crash happens in the middle of a write you just find that last header and disregard everything after it. If you notice some corruption on disc it’s easy to isolate the space between operations. If you write to a file format “in place” instead of using append-only the complexity of tracking down corruption and invalid writes is mind boggling.

The catch with append-only is that you have to “compact” or “vacuum” from time to time. Since every delete and update operation is actually adding to the size of the file you’ll eventually want to write a new file with only the latest versions of the data dramatically reducing disc usage.

MongoDB does not keep an append-only log, and does not use an append-only file format for it’s db. It writes in place to it’s on disc format. They’ve stated that this is “faster” and that compaction is costly. The actual write being faster is a puzzling statement and I believe it to be entirely untrue. The advantage of writing to an append-only file is that all the writes are sequential which are significantly faster on spinning discs and even SSDs, seeks aren’t free so the individual write operations in place will never be faster than append-only.

In CouchDB compaction does not lock the database (nothing can lock the database) so the only way compaction might be “costly” is if you triggered it during heavy write load (the new writes and the compaction task would both be competing for use of the disc). Which is why CouchDB doesn’t do automatic compaction, you are supposed to trigger it when your write load isn’t peaking.

As I said before, at some point in the life of your database a disc write will fail. Not keeping something append-only around is incredibly concerning. Stories like this are dubious not because they expose a few bugs in MongoDB but because they show inherent architectural problems you cannot overcome long term without something append-only. MongoDB encourages you to throw heavy load at it by touting their performance but everyone’s load looks a little different and when MongoDB does fall it falls hard and you’re left with whatever the last backup was assuming that predates any of your corruption.

Consistency Guarantees

For the most part, when you write data you want to be assured that it’s going to stay there and be accessible. I touched on this a little earlier when I talked about the request/response differences between everyone and MongoDB. Understanding the difference between when something is availability and when something is persisted to disc is also important. Traditionally something isn’t to be considered “guaranteed” until the fsync() to disc finishes.

Some databases, like Cassandra, actually take this a little further offering “full consistency” across a cluster of nodes giving assurances that fsync’s across multiple nodes are complete. CouchDB uses “eventual consistency” which is to say that a single CouchDB node has your data on disc and will replicate with other nodes at some point in the future. CouchDB allows you to take nodes offline and bring your entire database with you on your devices including mobile phones so “full consistency” across nodes that might be offline is actually impossible. This is a good example of differences in use cases when you decide what granularity of consistency you need for you application.

Redis is an in-memory database (with a soon to be released version using a hybrid of memory and virtual memory) but they keep around an append-only file so that you can also bring back up a node after it’s crashed and get your data. There are 3 options for how it will fsync(). One is “never”, it just sends writes to the kernel and lets the kernel flush the data to the append only file when it feels like it. Another option is every second and the last option is “always” which does a “group commit” style continuous fsync().

MongoDB writes to a mem-mapped file and lets the kernel fsync it whenever the kernel feels like it. More recent versions added a feature that does an fsync() to disc every minute if the kernel hasn’t done it already. So, at any time you could lose up to one minute of your data if the node goes down. That’s longer than most, if not all, other databases that persist at all to disc (when Redis lets the kernel handle it exclusively it might actually be longer).

Some Conclusions

When you look at MongoDB more critically I don’t see how you could actually justify using it for anything resembling the traditional role of a database. They have a great feature list which makes you think of all these things you can do with it but most if not all of those things will require it to not lose the data you put in it. If you wanted a cache with great indexing, or you needed to store data quickly that was too large to put in to memory but didn’t care about losing it, MongoDB would be a good choice. Of course, this is not what they market it as. 10gen’s sizable marketing effort promotes MongoDB as the new M in your LAMP stack, or Ruby/Python equivalent, without addressing their differences in durability with your existing M and almost any alternative “NoSQL” database.

The clincher for me was when Josh Berkus told me that he had an in-memory version of Postgres and once you turn off the log and all the durability it’s neck and neck with MongoDB write performance and I thought “who would be crazy enough to run that in production” but then I realized it’s about the same reliability you get with MongoDB.

It’s sad but in this gold rush of new databases where you can find a database that fits your exact use case so many people are choosing one that just isn’t really fitting for theirs. If “NoSQL” is going to survive as a movement of replacements for relational databases it’ll have to do it with proper durability and consistency guarantees like their RDBMS counterparts when the use case necessitates it. Most have delivered on these guarantees, some have not.

I work on CouchDB and to people who are writing webapps in Ruby and Python it probably looks like we’re competing with MongoDB but in reality we aren’t. We’re putting CouchDB on your mobile phone so you can take your applications and data with you and work on it offline. We’re trying to extend the web platform to mobile and be the glue HTML5 uses cross-mobile. But, I still find uses for Redis and love it when I need it. I don’t have a warehouse full of data but if I did I’m sure I’d take a serious look at Cassandra. There are great databases out there and people should understand them and use them when you have the use case they are built for.

[UPDATE: I removed comments from my blog, a long reasoned post is up here.]

[UPDATE: Mathias has posted an archive of the comments, it loads about 100x faster than my blog :) ]

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • StumbleUpon
  • Technorati
  • Reddit
  • Slashdot
  • Twitter
  • HackerNews

CouchDB builtin reduce functions

Posted by on June 30, 2010
CouchCamp

Today was one of those “how did I not know about this?!” days.

At lunch @jchris mentioned that using the “builtin” sum reduce was up to 100x faster than using a JavaScript reduce function that does the same thing. Then of course my reaction was “wtf is a builtin reduce?”.

Apparently CouchDB has these awesome internal erlang functions for common reduce operations that you can use instead of a calling out to the view server that you just set in your design document instead of a JavaScript function. Since they don’t talk to the view server and can work on native erlang terms, skipping the JSON serialization steps, they are absurdly fast. Like so fast that there is very little reason to ever write another JavaScript reduce.

It’s all written up on the wiki but here is the gist of it.

{
  "_id":"_design/company",
  "_rev":"12345",
  "language": "javascript",
  "views":
  {
    "all_customers": {
      "map": "function(doc) { if (doc.type == 'customer')  emit(doc.id, 1) }",
      "reduce" : "_count"
    },
    "total_purchases_by_customer": {
      "map": "function(doc) { if (doc.type == 'purchase')  emit(doc.customer_id, doc.amount) }",
      "reduce": "_sum"
    }
  }
}
Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • StumbleUpon
  • Technorati
  • Reddit
  • Slashdot
  • Twitter
  • HackerNews

CouchUp Thursday 6pm at Pacific Coast Brewery, Oakland

Posted by on June 29, 2010
CouchCamp

It’s time again to combine our favorite two things, drinks and CouchDB :)

We’ll be doing a CouchUp on Thursday the 1st of July at 6pm. The location is a local brewery just a few blocks from the 12th st BART station, Pacific Coast Brewing Co.


View Larger Map

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • StumbleUpon
  • Technorati
  • Reddit
  • Slashdot
  • Twitter
  • HackerNews

Open Source BBQ, Sunday, June 27th 2010

Posted by on June 22, 2010
CouchCamp

Time for another Open Source BBQ!

All are welcome, contributions appreciated, I’m gonna start cooking around 3pm and probably close it down when the sun goes down.

Please RSVP:

http://www.mobaganda.com/opensourcebbq-june2010

or on Plancast

http://plancast.com/a/3pt9

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • StumbleUpon
  • Technorati
  • Reddit
  • Slashdot
  • Twitter
  • HackerNews

Performance Testing

Posted by on May 4, 2010
CouchCamp

I’ve been working on performance tests for CouchDB for quite some time and haven’t really blogged about it yet.

First, what is the wrong way to do performance testing?

You can focus on testing single writer/reader performance. Nobody should care about single writer performance because even poor single writer performance is usually “good enough” if you’ve only got one client. What we really need to care about is concurrent performance, how many concurrent clients can we support before the response times are too slow for the experience we want.

The next thing, and this is a little controversial, don’t chart performance historically over time. If someone says that they’ll need to “rebase” the results, you’ve done it wrong.

In fact, don’t even show numbers likes “x writes/reads per second on average”. That’s a worthless number. For one, it’s completely dependent on the conditions of a machine nobody has access to. Operating systems don’t like to do nothing. Saying that the state of any machine today is what it will be in a week is stupidly optimistic.

And most importantly, the process of removing any part of the environment that might cause differences means that you’re removing all the real world conditions that might effect performance you’re suppose to be testing for.

So what do you do instead?

First, embrace environmental craziness. Assume that the conditions available right now will never be available again. Test, and chart, the difference between the test target and the baseline. Take the last release, or trunk, and on the same box, at nearly the same time, run the test against it and chart that difference against the test target (a branch, commit, patch, whatever).

Whatever environmental differences exist will effect both test runs so you can just stop worrying about them, and instead of removing the real world from your tests you get to test the real world.

I’ve built a concurrent performance tester for CouchDB and a graph server for charting the test results.

http://github.com/mikeal/relaximation

The tool is documented in the README in markdown so you can read it all on GitHub. It charts the average response time, between two couches, for the same test, averaged over a few recurring runs.

By default the tool will POST the results to http://mikeal.couchone.com/graphs and print you a link to a graph like this one http://mikeal.couchone.com/graphs/_design/app/_show/compareWriteReadTest/c34d5d47f99e11be1f591832d00037e5

The graph server is still missing some information from the test but I’ll be fixing that all soon enough :)

Also, the performance test code is all written in node but uses TCP to minimize the processing necessary on HTTP so we can support a really huge number of concurrent clients. In my tests node uses about 1/10 the CPU that CouchDB is using to process the data :)

[UPDATE: This post has been translated in to Belorussian!]

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • StumbleUpon
  • Technorati
  • Reddit
  • Slashdot
  • Twitter
  • HackerNews

Open Source BBQ this Friday 4/9/2010

Posted by on April 6, 2010
CouchCamp

That’s right! Open Source BBQ this Friday 4/9/2010 in Oakland, CA in my backyard at 4pm until it gets dark.

There will be meat, and stuff that’s not meat, and beer. Contributions welcome, it’s open source after all.

Everyone that writes code is welcome. Please RSVP at http://www.mobaganda.com/opensourcebbq.

I live walking distance from MacAurthur BART, here is a map. When you get there just walk down the driveway in to the backyard.

Friday is also “open friday” at the couch.io office so you can come and work/play at the office during the day and then roll up to the BBQ around 4 if you like.

Badass!

Disclaimer: Image is for effect only, hot rod BBQ will not be attending.

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • StumbleUpon
  • Technorati
  • Reddit
  • Slashdot
  • Twitter
  • HackerNews