Future of RDBMS is RAM Clouds & SSD »

Created at: 07.12.2009 18:51, source: igvita.com, tagged: Architecture databases database ramcloud ssd

Rumors of the demise of relational database systems are greatly exaggerated. The NoSQL movement is increasingly capturing the mindshare of the developers, all the while the academia have been talking about the move away from "RDBMS as one size fits all" for several years. However, while the new storage engines are exciting to see, it is also important to recognize that relational databases still have a bright future ahead - RDBMS systems are headed into main memory, which changes the playing field all together.

Performance is only one aspect that influences the choice of a database. Tree and graph structures are not easy to model within a relational structure, which in turn leads to complicated schemas and system overhead. For that reason alone, document-stores (Tokyo, CouchDB, MongoDB), graph stores (Neo4J), and other alternative data structure databases (Redis) are finding fertile ground for adoption. However, the end of "RDBMS as one size fits all" does not mean the end of relational systems all together. It is too early to bury RDBMS in favor of No (or Less) SQL. We just need to reset how we think about the RDBMS.

Disks are the New Tape

The evolution of disks has been extremely uneven over the last 25 years: disk capacity has increased 1000x, data transfer speeds increased 50x, while seek and rotational delays have only gone up by a factor of 2. Hence, if we only needed to transfer several hundred kilobytes of data in the mid 80's to achieve good disk utilization, then today we need to read at least 10MB of data to amortize the costs of seeking the data - refresh your memory on seek, rotational, and transfer times of our rusty hard drives.

When the best we can hope for is 100-200 IOPS out of a modern hard drive, the trend towards significantly larger block sizes begins to make a lot more sense. Whereas your local filesystem is likely to use 4 or 8kb blocks, systems such as Google's GFS and Hadoop's HDFS are opting out for 64MB+ blocks in order to amortize the cost of seeking for the data - by using much larger blocks, the cost of seeks and access time is once again brought down to single digit percent figures over the transfer time.

Hence, as we generate and store more and more data, the role of the disks must inevitably become more archival. Batch processing systems such as Map-Reduce are well suited for this world and are quickly replacing the old business intelligence (BI) systems for exactly these reasons. In the meantime, the limitations imposed by the random access to disk mean that we need to reconsider the role of disk in our database systems.

OLTP is Headed Into Main Memory & Flash

An average random seek will take 5-10ms when hitting the physical disk and hundreds of microseconds for accessing data from cache. Compare that to a fixed cost of 5-10 microseconds for accessing data in RAM and the benefits of a 100-1000x speed difference can be transformative. Instead of treating memory as a cache, why not treat it as a primary data store? John Ousterhout and his co-authors outline a compelling argument for "RAMCloud". After all, if Facebook keeps over 80% of their data in memcached, and Google stores entire indexes of the web in memory many times over, then your average database-backed application should easily fit and be able to take advantage of the pure memory model also.

The moment all of the data is available in memory, it is an entirely new game: access time and seek times become irrelevant (no disk seeks), the value of optimizing for locality and access patterns is diminished by orders of magnitude, and in fact, entirely new and much richer query models can enable a new class of data-intensive applications. In a world where the developer's time is orders of magnitude more expensive than the hardware (a recent phenomenon), this also means faster iterations and less data-optimization overhead.

The downside to the RAMCloud is the equivalent order of magnitude increase in costs - RAM prices are dropping, but dollar for dollar, RAMCloud systems are still significantly more expensive. Flash storage is an obvious compromise for both speed and price. Theoretical access time for solid-state devices is on the order of 50 microseconds for reads, and 200 microseconds for writes. However, in reality, wrapping solid-state storage in SATA-like hardware devices brings us back to ~200 microseconds for reads, or ~5000 IOPS. Though, of course, innovation continues and devices such as FusionIO’s PCI-E flash storage controller bring us back to 80 microsecond reads at a cost of ~$11 per Gigabyte.

However, even the significantly higher hardware price point is often quickly offset once you factor in the saved developer time and adjacent benefits such as guaranteed performance independent of access patterns or data locality. Database servers with 32GB and 64GB of RAM are no longer unusual, and when combined with SSDs, such as the system deployed at SmugMug, often offer a much easier upgrade path than switching your underlying database system to a NoSQL alternative.

Database Architecture for the RAMCloud

Migrating your data into RAM or Flash yields significant improvements via pure speedup in hardware, however, "it is time for a complete rewrite" argument still holds: majority of existing database systems are built with implicit assumptions for disk-backed storage. These architectures optimize for disk-based indexing structures, and have to rely on multithreading and locking-based concurrency to hide latency of the underlying storage.

When access time is measured in microseconds, optimistic and lock-free concurrency is fair game, which leads to much better multi-core performance and allows us to drop thousands of lines of code for multi-threaded data structures (concurrent B-Trees, etc). RethinkDB is a drop-in MySQL engine designed for SSD drives leveraging exactly these trends, and Drizzle is a larger fork of the entire MySQL codebase aimed at optimizing the relational model for "cloud and net applications": massively distributed, lightweight kernel and extensible.

Migrating Into Main Memory

Best of all, you can start leveraging the benefits of storing your data in main memory even with the existing MySQL databases - most of them are small enough to make the memory buffers nothing but a leaky abstraction. Enable periodic flush to disk for InnoDB (innodb_flush_log_at_trx_commit=2), and create covering indexes for your data (a covering index is an index which itself contains all the required data to answer the query). Issue a couple of warm-up requests to load the data into memory and you are off to the races.

Of course, the above strategy is at best an intermediate solution, so investigating SSD’s as a primary storage layer, and if you are adventurous, give RethinkDB a try. Also keep an eye on Drizzle as the first production release is aimed for summer of 2010. Alternative data storage engines such as Redis, MongoDB and others are also worth looking into, but let us not forget: laws of physics still apply to NoSQL. There is no magic there. Memory is fast, disks are slow. Nothing is stopping relational systems from taking advantage of main memory or SSD storage.


more »

Planting the seeds »

Created at: 05.09.2009 16:00, source: Robby on Rails, tagged: Ruby on Rails ruby programming PLANET ARGON seeds database development workflow faker github rubyonrails code

Yesterday, the Rails team released 2.3.4, which includes standardized way for loading seed data into your application so that you didn’t have to clutter your database migrations.

I noticed a few comments on some blogs where people were asking how to use this new feature, so here is a quick runthrough a few ways that you can use it.

Populating Seed Data Approaches

The db/seeds.rb file is your playground. We’ve been evolving our seed file on a new project and it’s been great at allowing us to populate a really large data. Here are a few approaches that we’ve taken to diversify our data so that when we’re working on UI, we can have some diversified content.

Basic example

Any code that add to db/seeds.rb is going to executed when you run rake db:seed. You can do something as simple as:

# db/seeds.rb

Article.create(:title => 'My article title', :body => 'Lorem ipsum dolor sit amet, consectetur adipisicing elit')

Just create database records like you would in your Rails application or in script/console. Simple enough, right? Let’s play with a few other approaches that we’ve begun to use.

Use the names of real people

We’re using the Octopi gem to connect to github, collect all the names of people that follow me there, and using their names to seed our development database.

@robby_on_github = Octopi::User.find('robbyrussell')

# add a bunch of semi-real users
@robby_on_github.followers.each do |follower|
  github_person = Octopi::User.find(follower)
  next if github_person.name.nil?

  # split their name in half... good enough (like the goonies)
  first_name = github_person.name.split(' ')[0]
  last_name = github_person.name.split(' ')[1]
  new_person = Person.create(:first_name => first_name, :last_name => last_name, :email => Faker::Internet.email,
                             :password => 'secret', :password_confirmation => 'secret',
                             :github_username => follower, :website_url => github_person.blog)
  # ...
end

We do this with a few sources (twitter, github, etc..) to pull in the names of real people. If you want to be part of my seed data, you might consider following me on Github. ;-)

Use Faker for Fake data

You may have noticed in the previous code sample, that I used Faker in that code. We are using this a bunch in our seed data file. With Faker, you can generate a ton of fake data really easy.

person.links.create(:title => Faker::Lorem.words(rand(7)+1).join(' ').capitalize,
                    :url => "http://#{Faker::Internet.domain_name}/",
                    :description => Faker::Lorem.sentences(rand(4)+1).join(' '))

We might toss something like that into a method so that we can do the following:

@people = Person.find(:all)

500.times do
  generate_link_for(@people.sort_by{rand}[0])
end

...and we’ll get 500 links added randomly across all of the people we added to our system. You can get fairly creative here.

For example, we might even wanted random amounts of comments added to our links.

def generate_link_for(person)
  link = person.links.create(:title => Faker::Lorem.words(rand(7)+1).join(' ').capitalize,
                             :url => "http://#{Faker::Internet.domain_name}/",
                             :description => Faker::Lorem.sentences(rand(4)+1).join(' '))

  # let's randomly add some comments...
  if link.valid?
    rand(5).times do
      link.comments.create(:person_id => @people.sort_by{rand}[0].id,
                           :body => Faker::Lorem.paragraph(rand(3)+1))
    end
  end
end

It’s not beautiful, but it gets the job done. It makes navigating around the application really easy so that we aren’t having to constantly input new data all the time. As mentioned, it really helps when we’re working on the UI.

Your ideas?

We’re trying a handful of various approaches to seed our database. If you have some fun ways of populating your development database with data, we’d love to hear about it.


more »

MongoMapper, The Rad Mongo Wrapper »

Created at: 27.06.2009 19:59, source: RailsTips - Home, tagged: database mongo mongomapper

In which I formally release MongoMapper, a high level wrapper similar to ActiveRecord, but for MongoDB.

MongoMapper A few weeks ago, I wrote about Mongo and how awesome it is. Towards the end of the article (and in the slideshow) I mentioned MongoMapper, a project I’ve been working on.

Over the past few weeks my buddies at Squeejee and Collective Idea have started using MongoMapper and they’ve helped me squash a few bugs and add a few features.

Despite the fact that I would call it far from finished, I’ve decided to release it in hopes that people can start playing with it, finding bugs, adding features and submitting pull requests. The documentation is sparse to none, but there are plenty of tests and the code is pretty readable, I believe.

Installation

# from gemcutter
gem install mongo_mapper

Usage

So how do you use this thing? It’s pretty simple. MongoMapper uses a default connection from the Ruby driver. This means if you are using Mongo on the standard port and localhost, you don’t have to give it connection information. If you aren’t, you can do it like this:

MongoMapper.connection = Mongo::Connection.new('hostname')

Connection accepts any valid Mongo ruby driver connection. The only other setup you need to do is to tell MongoMapper what the default database is. This is pretty much the same as setting up the connection:

MongoMapper.database = 'mydatabasename'

These two operations only define the default connection and database information. Both of these can be overridden on a per model basis so that you can hook up to multiple databases on different servers.

Include Instead of Inherit

To create a new model, I went with the include pattern, instead of inheritance. In ActiveRecord, you would define a new model like this:

class Person < ActiveRecord::Base
end

In MongoMapper, you would do the following:

class Person
  include MongoMapper::Document
end

Just like ActiveRecord, this makes assumptions. It assumes you have a collection named people. Oh, and the good news is you don’t need a migration for it. The first time you try to create a person document, the collection will be created automatically. Heck yeah! I mentioned that you can override the default connection and database on a per document level. If you need to do that, it would look like this:

class Person
  include MongoMapper::Document

  connection Mongo::Connection.new('hostname')
  set_database_name 'otherdatabase'
end

Defining Keys

Each document is made up of keys. Keys are named and type-casted so you know your data is stored in the correct format. Lets fill out our Person document a bit.

class Person
  include MongoMapper::Document

  key :first_name, String
  key :last_name, String
  key :age, Integer
  key :born_at, Time
  key :active, Boolean
  key :fav_colors, Array
end

Now that we have defined our schema, we can create, update and delete documents.

person = Person.create({
  :first_name => 'John',
  :last_name => 'Nunemaker',
  :age => 27,
  :born_at => Time.mktime(1981, 11, 25, 2, 30),
  :active => true,
  :fav_colors => %w(red green blue)
})

person.first_name = 'Johnny'
person.save

person.destroy
# or you could do this to destroy
Person.destroy(person.id)

Looks pretty familiar, eh? Where it made sense, I tried to stay close to ActiveRecord in API.

Validations

But wait you say, how do I validate my data? Well, you can do it pretty much the same way as ActiveRecord.

class Person
  include MongoMapper::Document

  key :first_name, String
  key :last_name, String
  key :age, Integer
  key :born_at, Time
  key :active, Boolean
  key :fav_colors, Array

  validates_presence_of :first_name
  validates_presence_of :last_name
  validates_numericality_of :age
  # etc, etc
end

But, if you find that a bit tedious as I do, you can use some shortcuts that I’ve added in.

class Person
  include MongoMapper::Document

  key :first_name, String, :required => true
  key :last_name, String, :required => true
  key :age, Integer, :numeric => true
  key :born_at, Time
  key :active, Boolean
  key :fav_colors, Array
end

Most of the validations from Rails are supported. I still need to build in support for validates_uniqueness of and some of the options that rails supports might not be right now, but it is a good first pass.

Callbacks

Did you hear that? I swear I just heard someone whisper about callbacks. Umm, yeah, we got that too. The good news? I just used ActiveSupport’s callbacks so they are identical to Rails and most of Rails defined callbacks are supported such as before_save and the like.

Embedded Documents

So the cool thing about Mongo is that you can embed documents in other documents. Let’s say our person has multiple addresses. To handle that, we would create an embedded address document to go along with our person document.

class Address
  include MongoMapper::EmbeddedDocument

  key :address, String
  key :city,    String
  key :state,   String
  key :zip,     Integer
end

class Person
  include MongoMapper::Document

  many :addresses
end

Now we can add addresses to the person like so:

person = Person.new
person.addresses << Address.new(:city => 'South Bend', :state => 'IN')
person.addresses << Address.new(:city => 'Chicago', :state => 'IL')
person.save

Doing this actually saves the address right inside the person document. Yep, no joins. Yay! Cheers resound from the heavens! You can even query for documents based on these embedded documents. For example, if you wanted to find all people that are in the city Chicago, you could do this:

Person.all(:conditions => {'addresses.city' => 'Chicago'})

Finding Documents

The find API is very similar to AR as well. Below are a bunch of other examples:

Person.find(1)
Person.find(1,2,3,4)
Person.find(:first)
Person.first
Person.find(:last)
Person.last
Person.find(:all)
Person.all
Person.all(:last_name => 'Nunemaker', :order => 'first_name')

For more information about how to provide criteria to find, you can see the stuff covered in the finder options. If you need to, you can even throw custom mongo stuff into the mix and it just gets passed through to the mongo ruby driver (ie: $gt, $gte, $lt, $lte, etc.).

Conclusion

We take ActiveRecord for granted. It really has a lot of handy features and does a pretty good job at modeling our applications. I never realized how much it does, until I decided to create MongoMapper. That said, the experience has been fun thus far and I’m excited to see what people use it for.

There is a ton more I could talk about, but frankly, this article is long enough. Rest assured that I think Mongo is cool and that MongoMapper is headed in the right direction, but far from complete. I haven’t actually built anything with MongoMapper yet, but I will be soon. I’m sure that will lead to a lot of handy new features.

Any general discussion can happen in the comments below while they are open or over at the google group. If you find a bug or have a feature idea, create an issue at github.


more »

Subdomain accounts with Ruby on Rails explained »

Created at: 12.01.2009 01:30, source: Robby on Rails, tagged: Ruby on Rails ruby programming development code accounts subdomains database rubyonrails programming tutorial example

DHH recently posted, How to do Basecamp-style subdomains in Rails on SvN and it just happens that I was implementing some similar stuff this last week for a project we’re developing internally.

In our project, not everything needs to be scoped per-account as we are building a namespace for administrators of the application and also want a promotional site for the product. Three different interfaces, with some overlap between them all.

Let’s walk through a few quick steps that you can follow to setup the two interfaces within the same application.

Suppose that we’re going to build a new web-based product and have the following requirements initially.

  • We need a promotional site for sign-ups, frequently-asked-questions, support requests, etc.
  • When people sign-up for an account, they’ll should have their own unique sub-domain
  • There are two different visual layouts (promotional site and the account)

Note: I use RSpec and am going to skip the TDD process here and let you conquer that for yourself. Am using the default Rails commands in this tutorial.

Account model / Database

We’re going to generate a new model for Account, which will be responsible for scoping sub-domains and individual accounts.

  account-demo : ruby script/generate model Account subdomain:string 
       create  app/models/
       create  test/unit/
       create  test/fixtures/
       create  app/models/account.rb
       create  test/unit/account_test.rb
       create  test/fixtures/accounts.yml
       exists  db/migrate
       create  db/migrate/20090111220627_create_accounts.rb

Great, let’s migrate our application.

   account-demo : rake db:migrate
  ==  CreateAccounts: migrating =================================================
  -- create_table(:accounts)
     -> 0.0045s
  ==  CreateAccounts: migrated (0.0052s) ========================================

Before we get too far, let’s make sure that we’re adding an index on this table for the subdomain, as it’ll improve performance in the database as the subdomain will used in SQL conditions quite often.

account-demo : ruby script/generate migration AddIndexToAccountSubdomain
     exists  db/migrate
     create  db/migrate/20090111221009_add_index_to_account_subdomain.rb

Let’s open up this new migration file and toss in a UNIQUE INDEX on subdomain.

  class AddIndexToAccountSubdomain < ActiveRecord::Migration
    def self.up
      add_index :accounts, :subdomain, :unique => true
    end

    def self.down
      remove_index :accounts, :subdomain
    end
  end  

Okay, let’s migrate this bad boy.

   account-demo : rake db:migrate
  ==  AddIndexToAccountSubdomain: migrating =====================================
  -- add_index(:accounts, :subdomain, {:unique=>true})
     -> 0.0047s
  ==  AddIndexToAccountSubdomain: migrated (0.0050s) ============================  

Great, we’re now ready to move on to the fun stuff.

Let’s open up app/models/account.rb and throw some sugar in it.

Data Validation

Because we’re going to be dealing with subdomains, we need to make sure that we’re only allowing people to sign-up with valid data otherwise, there could be issues. URLs need to fit within certain conventions and we need to make it as graceful as possible for our customers.

Let’s make a quick list of what we need to enforce for the subdomain attributes. This can easily be expanded, but let’s cover the basics.

  • Each account should have a subdomain
  • Each subdomain should be unique within the application
  • A subdomain should be alpha-numeric with no characters or spaces with the exception of a dash (my requirement)
  • A subdomain should be stored as lowercase

So, let’s update the following default Account model….

  class Account < ActiveRecord::Base
  end  

..and add some basic validations.

  class Account < ActiveRecord::Base
    validates_presence_of :subdomain
    validates_format_of :subdomain, :with => /^[A-Za-z0-9-]+$/, :message => 'The subdomain can only contain alphanumeric characters and dashes.', :allow_blank => true
    validates_uniqueness_of :subdomain, :case_sensitive => false

    before_validation :downcase_subdomain

    protected

      def downcase_subdomain
        self.subdomain.downcase! if attribute_present?("subdomain")
      end
  end  

Reserved subdomains

In the project that our team is working on, we wanted to reserve several subdomains so that we could use them later on. We tossed in the following validation as well.

  validates_exclusion_of :subdomain, :in => %w( support blog www billing help api ), :message => "The subdomain <strong>{{value}}</strong> is reserved and unavailable."

This will prevent people from using those when they sign up.

Controller / Handling Requests

Let’s now think about how we’ll handle requests so that we can scope the application to the current account when a subdomain is being referenced in the URL.

For example, let’s say that our application is going to be: http://purplecowapp.com/ [1]

Customers will get to sign-up and reserve http://customer-name.purplecowapp.com/. I want my account subdomain to be green.purplecowapp.com and everything under this subdomain should be related to my instance of the application.

I’ve begun working on my own module, which is inspired mostly by the account_location plugin with some additions to meet some of our product’s requirements.

Here is my attempt to simplify it for you (removed some other project-specific references) and have put this into a Gist for you.

  #
  # Inspired by
  # http://dev.rubyonrails.org/svn/rails/plugins/account_location/lib/account_location.rb
  #
  module SubdomainAccounts
    def self.included( controller )
      controller.helper_method(:account_domain, :account_subdomain, :account_url, :current_account, :default_account_subdomain, :default_account_url)
    end

    protected

      # TODO: need to handle www as well
      def default_account_subdomain
        ''
      end

      def account_url( account_subdomain = default_account_subdomain, use_ssl = request.ssl? )
        http_protocol(use_ssl) + account_host(account_subdomain)
      end

      def account_host( subdomain )
        account_host = ''
        account_host << subdomain + '.'
        account_host << account_domain
      end

      def account_domain
        account_domain = ''
        account_domain << request.domain + request.port_string
      end

      def account_subdomain
        request.subdomains.first || ''
      end

      def default_account_url( use_ssl = request.ssl? )
        http_protocol(use_ssl) + account_domain
      end

      def current_account
        Account.find_by_subdomain(account_subdomain)
      end

      def http_protocol( use_ssl = request.ssl? )
        (use_ssl ? "https://" : "http://")
      end
  end  

View gist here (embed wasn’t working right when I tried)

Just include this into your lib/ directory and require it in config/environment.rb. (if people think it’s worth moving into a plugin, I could do that)

Including AccountSubdomains

In the main application controller (app/controllers/application.rb), just include this submodule.

  class ApplicationController < ActionController::Base
    include SubdomainAccounts

    ...
  end  

Now, we’ll want to add a check to verify that the requested subdomain is a valid account. (our code also checks for status on paid memberships, etc… but I’ll just show a basic version without that)

Let’s add in the following to app/controllers/application.rb. This will only check on the status of the account (via subdomain) if the current subdomain is not the default. For example: purplecowapp.com is just our promotion site, so we won’t look up the account status and/or worry about the subdomain. Otherwise, we’ll check on the status.

  before_filter :check_account_status

  protected
    def check_account_status
      unless account_subdomain == default_account_subdomain
        # TODO: this is where we could check to see if the account is active as well (paid, etc...)
        redirect_to default_account_url if current_account.nil? 
      end
    end  

Current Account meets Project model

When requests are made to an account’s subdomain, we want to be able to scope our controller actions.

WARNING: I’m going to gloss over the following steps because this is just standard Rails development stuff and I want to focus on how to scope your Rails code to account subdomains.

I’ll just say that this product gives each account many projects to do stuff within. I’ll assume that you’ll know how to handle all that and we’ll assume you have a Project model already.

What you will need is to add a foreign key to your table (projects in this example) that references Account. So, make sure that your model has an account_id attribute with and that the database table column has an INDEX.

We’ll add our associations in the models so that they can reference each other.

  # app/models/account.rb
  class Account < ActiveRecord::Base
    has_many :projects
    # ...
  end

  # app/models/project.rb
  class Project < ActiveRecord::Base
    belongs_to :account
    # ...
  end  

Okay great… back to our controllers. The SubdomainAccounts module provides you with the current_account variable, which you can use within your controllers/views. This allows us to do the following in our controllers. For example, if we had a ProjectsController.

  class ProjectsController < ApplicationController
    def index
      @projects = current_account.projects.find(:all)
    end

    def new
      @project = current_account.projects.new
    end 

    def show
      @project = current_account.projects.find(params[:id])
    end

    # ...
  end

See, this wasn’t so hard, was it?

Handling layouts

I wanted to highlight one other thing here because I suspect that most projects that fit this will likely need a promotional/resource site where people will sign-up from. In our application, we have two application layouts. One for the main application that customers will interact with via their subdomain and the promotional site layout.

The default layout is just app/views/layouts/application.html.erb and we have our promotional site layout at app/views/layouts/promo_site.html.erb. A few of our controllers are specifically for the promotional site while the rest are for the application itself and in some cases, there is some overlap down to individual action within a controller.

What we did was add a few more before filters to our application controller to a) define the proper layout to render, and b) skip login_required on the promo site.

To have the proper layout get rendered, we’re just checking whether the current request was made to the promotional site or not.

class ApplicationController < ActionController::Base
  # ...
  layout :current_layout_name # sets the proper layout, for promo_site or application

protected

  def promo_site?
    account_subdomain == default_account_subdomain
  end

  def current_layout_name
    promo_site? ? 'promo_site' : 'application'
  end

  # ...
end

Our application is using Restful Authentication and we just want to check to see if the current request is made to the promotional site or not. If it is, we’ll skip the login_required filter. Let’s assume that you have the following before_filter set.

class ApplicationController < ActionController::Base
  # ...
  before_filter :login_required

We’ll just change this to:

class ApplicationController < ActionController::Base
  # ..
  before_filter :check_if_login_is_required 

  protected
    def promo_site?
      account_subdomain == default_account_subdomain
    end

    def current_layout_name
      promo_site? ? 'promo_site' : 'application'
    end    

    def check_if_login_is_required
      login_required unless promo_site?
    end

    # ...

There we go. We can now render the proper layout given the request and only handle authentication when necessary.

Development with account subdomains

When you begin developing an application like this, you need to move beyond using http://locahost:3000 as we need to be able to develop and test with subdomains. You can open up your /etc/hosts (within a Unix-based O/S) file and add the following.


127.0.0.1 purplecowapp.dev
127.0.0.1 green.purplecowapp.dev
127.0.0.1 sample.purplecowapp.dev
127.0.0.1 planetargon.purplecowapp.dev
127.0.0.1 lollipops.purplecowapp.dev
127.0.0.1 help.purplecowapp.dev
127.0.0.1 support.purplecowapp.dev

After you edit that file (with root permissions), you can flush your dns cache with dscacheutil -flushcache (Mac OS X). This will let you make requests to http://purplecowapp.dev:3000/ and http://green.purplecowapp.dev:3000. This is a convention that our team has begun using for our own projects (TLD ending in .dev). It’s important to remember that the subdomain must be specified here in order to work for local requests. Unfortunately, hosts files don’t support wildcards (’*’).

Update

You can also use Ghost, which is a gem for managing DNS entries locally with Mac OS X. Read Get to know a gem: Ghost

Summary

I know that I glossed over some sections, but was hoping that the code itself would be the most beneficial for you. Feel free to leave any questions and/or provide some feedback on our approach. Perhaps you have some suggestions that I could incorporate into this so that we can improve on this pattern.

1 yeah, I’ve been reading more Seth Godin recently…


more »