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

Embracing Constraints with CouchDB (IPC2010 2010-10-13)

Embracing Constraints with CouchDB (IPC2010 2010-10-13)

Presentation given at International PHP Conference 2010 in Mainz, Germany.

David Zuelke

October 13, 2010
Tweet

More Decks by David Zuelke

Other Decks in Programming

Transcript

  1. COUCHDB STORES DOCUMENTS • CouchDB stores documents with arbitrary keys

    and values • Each document is identified by an ID and has a revision • Documents can have file attachments • Stored as JSON, so it’s easy to interface with
  2. COUCHDB SPEAKS HTTP • CouchDB uses HTTP to communicate with

    clients & servers • That means scalability • That means a lot of kick ass stuff totally for free • Caching • Load Balancing • Content Negotiation
  3. COUCHDB USES MVCC • Multiversion Concurrency Control • When updating,

    you must supply a revision number • Your change will be rejected if the revision is not the latest • All writes are serialized • No need for locks, but puts some responsibility on developers
  4. REPLICATION • You can do Master-Master replication • Conflicts are

    detected and marked automatically • Conflicts are supposed to be resolved by applications • Or by users, who usually know best what to do!
  5. BASIC PRINCIPLE: MAPPER • The Mapper reads records and emits

    <key, value> pairs • Example: Apache access.log • Each line is a record • Extract client IP address and number of bytes transferred • Emit IP address as key, number of bytes as value • For hourly rotating logs, the job can be split across 24 nodes* * In pratice, it’s a lot smarter than that
  6. BASIC PRINCIPLE: REDUCER • A Reducer is given a key

    and all values for this specific key • Even if there are many Mappers on many computers; the results are aggregated before they are handed to Reducers • Example: Apache access.log • The Reducer is called once for each client IP (that’s our key), with a list of values (transferred bytes) • We simply sum up the bytes to get the total traffic per IP!
  7. EXAMPLE OF MAPPED INPUT IP Bytes 212.122.174.13 18271 212.122.174.13 191726

    212.122.174.13 198 74.119.8.111 91272 74.119.8.111 8371 212.122.174.13 43
  8. REDUCER WILL RECEIVE THIS IP Bytes 212.122.174.13 18271 212.122.174.13 191726

    212.122.174.13 198 212.122.174.13 43 74.119.8.111 91272 74.119.8.111 8371
  9. THE KEY DIFFERENCE • Maps and Reduces are incremental: •

    If one document changes, only that one document needs: • mapping • reduction • Then a few new reduce runs are performed to compute the final result
  10. MAPPER: DOCS BY TAGS function(doc)  {    if(doc.type  ==  'product')

     {        (doc.tags  ||  []).forEach(function(tag)  {            emit(tag,  doc);        });    } }
  11. MAPREDUCE: COUNT TAGS function(doc)  {    if(doc.type  ==  'product')  {

           (doc.tags  ||  []).forEach(function(tag)  {            emit(tag,  1);        });    } } function(key,  values)  {    return  sum(values); }
  12. JOIN CATEGORIES & ITEMS function(doc)  {    if(doc.type  ==  'product')

     {        emit([doc._id,  0],  doc);        emit([doc._id,  1],  {  _id:  doc.category_id  });    } } ["123",  0]            {_id:  "123",  _rev:  "5-­‐a72",  type:  "product",  "name":  "Laser  Beam"} ["123",  1]            {_id:  "est",  _rev:  "2-­‐9af",  type:  "category",  "name":  "Evil  Stuff"} ["817",  0]            {_id:  "817",  _rev:  "2-­‐aa8",  type:  "product",  "name":  "Rocketship"} ["817",  1]            {_id:  "cst",  _rev:  "3-­‐d8a",  type:  "category",  "name":  "Cool  Stuff"} ["441",  0]            {_id:  "441",  _rev:  "19-­‐fdf",  type:  "product",  "name":  "Sharks"} ["441",  1]            {_id:  "est",  _rev:  "2-­‐9af",  type:  "category",  "name":  "Evil  Stuff"}
  13. JOIN PRODUCTS & ITEMS function(doc)  {    if(doc.type  ==  'category')

     {        emit([doc._id,  0],  doc);    }  elseif(doc.type  ==  'product')  {        emit([doc.category_id,  doc._id],  doc);    } } ["est",  0]            {_id:  "est",  _rev:  "2-­‐9af",  type:  "category",  "name":  "Evil  Stuff"} ["est",  "123"]    {_id:  "123",  _rev:  "5-­‐a72",  type:  "product",  "name":  "Laser  Beam"} ["est",  "441"]    {_id:  "441",  _rev:  "19-­‐fdf",  type:  "product",  "name":  "Sharks"} ["cst",  0]            {_id:  "cst",  _rev:  "3-­‐d8a",  type:  "category",  "name":  "Cool  Stuff"} ["cst",  "817"]    {_id:  "817",  _rev:  "2-­‐aa8",  type:  "product",  "name":  "Rocketship"}
  14. VALIDATE DOCUMENTS function  (newDoc,  savedDoc,  userCtx)  {    if(savedDoc  &&

     savedDoc.created_at  !=  newDoc.created_at)  {        throw({forbidden:  'created_at  is  immutable'});    }    if(doc.type  ==  'product')  {        if(!doc.price)  {            throw({forbidden:  'product  must  have  a  price'});        }    } }
  15. VALIDATE DOCUMENTS function  (newDoc,  savedDoc,  userCtx)  {    function  require(beTrue,

     message)  {        if(!beTrue)  throw({forbidden:  message});    }    require(savedDoc  &&  savedDoc.created_at  !=  newDoc.created_at,        'created_at  is  immutable'    );    if(doc.type  ==  'product')  {        require(!doc.price,            'product  must  have  a  price'        );    } }
  16. DID YOU SEE THE HEAD FAKE? This Talk Was Not

    About Embracing Constraints It Was About Embracing Awesomeness