Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building a SaaS app on CouchDB

langalex
November 07, 2011

Building a SaaS app on CouchDB

In late 2010 I started working on cobot.me, a software as a service for coworking spaces mainly built on Ruby on Rails and CouchDB. In this talk, I share my experiences, the problems encountered and the solutions found, what works well and what doesn't. The talk will cover the every day problems of working on an app that became quite complex over time.

langalex

November 07, 2011
Tweet

Other Decks in Programming

Transcript

  1. we’re in a movie theatre, i’m sorry you don’t get

    to watch a movie. let’s watch a trailer at least.
  2. Did you know that there is a global community of

    people dedicated to the values of Collaboration, Openness,Community, Accessibility, and Sustainability in their workplaces? It's called Coworking. And people seem to think it's swell. coworking.com our space co.up (co-up.de)
  3. user = User.new email: ‘[email protected]’ user.valid? db = CouchPotato.database db.save

    user # true|false db separated from models, easier to test, separation of concerns
  4. class User include CouchPotato::Persistence view :by_email, key: :email view :by_email_and_encrypted_password,

    map: <<-JS function(doc) { if(doc.ruby_class == ‘User’) { emit([doc.email, encrypt(doc.password)]) } } JS end 1st: auto generated, also: erlang 2nd: custom
  5. 1.5 Programmers NUMBERS * fully bootstrapped. 2 founders, me +

    thilo, who brings in most of the money * I also du customer support, UI, ops
  6. Replication Local Storage Cloudant BigCouch Cluster other backups: copy db

    file to s3 every hour, retain for 4 weeks append only - can just copy file
  7. MAP FUNCTION if(doc.ruby_class == 'Membership' && doc.confirmed_at) { emit([doc.space_id, to_month(doc.confirmed_at),

    doc.plans[0].name], emit_value(doc.plans[0])); if(doc.plans.length > 1) { var last_canceled_to = null; for(var i in doc.plans) { var plan = doc.plans[i]; if(plan.canceled_to) { last_canceled_to = plan.canceled_to; emit([doc.space_id, to_month(plan.canceled_to), plan.name], emit_value(plan) * -1); }; if(i > 0) { emit([doc.space_id, to_month(last_canceled_to), plan.name], emit_value(plan)); }; }; }; if(doc.canceled_to) { emit([doc.space_id, to_month(doc.canceled_to), doc.plans[0].name], emit_value(doc.plans[0]) * -1); }; function emit_value(plan) { return plan.total_price_per_cycle_in_cents; }; function to_month(date) { return date.substring(0,4) + '-' + date.substring(5, 7) }; };
  8. MAP FUNCTION emit(doc.confirmed_at, doc.plans[0].price); for(var i in doc.plans) { var

    plan = doc.plans[i]; if(plan.canceled_to) { emit(plan.canceled_to, plan.price * -1); }; if(i > 0) { emit(plan.starts_at, plan.price); }; }; };
  9. {“type”: “Charge”, “price”: 15, “invoiced”: true} {“type”: “Booking”, “price”: 50,

    “invoiced”: true} {“type”: “Invoice”, “price”: 65, “number”: 1} “traditional approach”, spans multiple docs
  10. {“type”: “Charge”, “price”: 15, “invoiced”: true, “_id”: “1”} {“type”: “Booking”,

    “price”: 50, “invoiced”: true, “_id”: “2”} {“type”: “Invoice”, “price”: 65, “number”: 1, “item_ids”: [“1”, “2”] } store everything in invoice
  11. Key Value Comment null “1” Invoice 1 null “2” Invoice

    1 “1” 15 Booking 1 “2” 50 Booking 2 result of map
  12. var invoiced_ids = []; if(row['key'] == null) { invoiced_ids.push(row['value']); }

    else { if(!invoiced_ids.include(row['_id'])) { send_row(row); }; }; LIST FUNCTION emits json, filters invoiced items
  13. Key Value Comment null “1” Invoice 1 null “2” Invoice

    1 “1” 15 Booking 1 “2” 50 Booking 2 all invoiced items removed
  14. SUMMARY No silver bullet. Choose your lead bullets wisely. stale

    views, optimize performance (erlang), view heater
  15. day_tickets = DB.view('day_ticket/by_space_id', :reduce => false, :include_docs => true) day_tickets.each

    do |doc| doc['ruby_class'] = 'DayPass' DB.save_doc doc end RENAME DAY TICKETS TO DAY PASSES
  16. CHANGING 10K DOCS TIMES 150 VIEWS = 1.5M UPDATES 3k

    updates/s = 8 minutes, high load, website non-responsive, timeouts