Key Value Operations

    +
    Key-Value (KV) or data service offers the simplest way to retrieve or mutate data where the key is known. Here we cover CRUD operations, document expiration, and optimistic locking with CAS.

    Documents

    A document refers to an entry in the database (other databases may refer to the same concept as a row). A document has an ID (primary key in other databases), which is unique to the document and by which it can be located. The document also has a value which contains the actual application data. See the concept guide to Documents for a deeper dive into documents in the Couchbase Data Platform. Or read on, for a hands-on introduction to working with documents from the Ruby SDK.

    CRUD Operations

    The core interface to Couchbase Server is simple KV operations on full documents. Make sure you’re familiar with the basics of authorization and connecting to a Cluster from the Start Using the SDK section. We’re going to expand on the short Upsert example we used there, adding options as we move through the various CRUD operations. Here is the Insert operation, with simple error handling:

    begin
      collection.insert("document-key", {"title" => "My Blog Post"})
    rescue Error::DocumentExists
      puts "The document already exists!"
    end

    Setting a Compare and Swap (CAS) value is a form of optimistic locking - dealt with in depth in the CAS page. Here we just note that the CAS is a value representing the current state of an item; each time the item is modified, its CAS changes. The CAS value is returned as part of a document’s metadata whenever a document is accessed. Without explicitly setting it, a newly-created document would have a CAS value of 0.

    collection.upsert("my-document", {"initial" => true})
    
    result = collection.get("my-document")
    content = result.content
    content["modified"] = true
    content["initial"] = false
    options = Collection::ReplaceOptions.new
    options.cas = result.cas
    collection.replace("my-document", content, options)

    Expiration sets an explicit time to live (TTL) for a document. For a discussion of item (Document) vs Bucket expiration, see the Expiration Overview page.

    options = Collection::InsertOptions.new
    options.expiration = 2 * 60 * 60
    # or
    #   require 'active_support/core_ext/numeric/time'
    #   options.expiration = 2.hours
    collection.upsert("my-document", {"doc" => true}, options)

    Durability

    If Server 6.5 or above is being used, you can take advantage of the Durable Write feature, in which Couchbase Server will only return success to the SDK after the requested replication level has been achieved. The three replication levels are:

    • Majority - The server will ensure that the change is available in memory on the majority of configured replicas.

    • MajorityAndPersistToActive - Majority level, plus persisted to disk on the active node.

    • PersistToMajority - Majority level, plus persisted to disk on the majority of configured replicas.

    The options are in increasing levels of safety. Note that nothing comes for free - for a given node, waiting for writes to storage is considerably slower than waiting for it to be available in-memory. These trade offs, as well as which settings may be tuned, are discussed in the durability page.

    The following example demonstrates using the newer durability features available in Couchbase server 6.5 onwards.

    options = Collection::UpsertOptions.new
    options.durability_level = :majority
    collection.upsert("my-document", {"doc" => true}, options)

    To stress, durability is a useful feature but should not be the default for most applications, as there is a performance consideration, and the default level of safety provided by Couchbase will be reasonable for the majority of situations.

    Sub-Document Operations

    All of these operations involve fetching the complete document from the Cluster. Where the number of operations or other circumstances make bandwidth a significant issue, the SDK can work on just a specific path of the document with Sub-Docunent Operations.

    Retrieving full documents

    Using the .get() method with the document key can be done in a similar fashion to the other operations:

    begin
      get_result = collection.get("document-key")
      title = get_result.content["title"]
      puts title
      #=> My Blog Post
    rescue Error::DocumentExists
      puts "Document not found!"
    end

    You can then add in logic to filter on the fields returned:

    found = collection.get("document-key")
    content = found.content
    if content["author"] == "mike"
      # do something
    else
      # do something else
    end

    Removing

    When removing a document, you will have the same concern for durability as with any additive modification to the Bucket:

    begin
      collection.remove("my-document")
    rescue Error::DocumentNotFound
      puts "Document did not exist when trying to remove"
    end

    Expiration / TTL

    Couchbase Server includes an option to have particular documents automatically expire after a set time. This can be useful for some use-cases, such as user sessions, caches, or other temporary documents.

    You can set an expiry value when creating a document:

    options = Collection::InsertOptions.new
    options.expiration = 2 * 60 * 60
    # or
    #   require 'active_support/core_ext/numeric/time'
    #   options.expiration = 2.hours
    collection.upsert("my-document", {"doc" => true}, options)

    When getting a document, the expiry is not provided automatically by Couchbase Server but it can be requested:

    options = Collection::GetOptions.new
    options.with_expiration = true
    found = collection.get("my-document", options)
    puts "Expiry of found doc: #{found.expiration} (or #{Time.at(found.expiration)})"
    #=> Expiry of found doc: 1595789542 (or 2020-07-26 21:52:22 +0300)

    Note that when updating the document, special care must be taken to avoid resetting the expiry to zero. Here’s how:

    options = Collection::GetOptions.new
    options.with_expiration = true
    found = collection.get("my-document", options)
    
    options = Collection::ReplaceOptions.new
    options.expiration = found.expiration
    collection.replace("my-document", {"content" => "something new"}, options)

    Some applications may find getAndTouch useful, which fetches a document while updating its expiry field. It can be used like this:

    collection.get_and_touch("my-document", 24 * 60 * 60)
    # or
    #   require 'active_support/core_ext/numeric/time'
    #   collection.get_and_touch("my-document", 1.day)
    If the absolute value of the expiry is less than 30 days (such as 60 * 60 * 24 * 30), it is considered an offset. If the value is greater, it is considered an absolute time stamp. For more on expiration see the expiration section of our documents discussion doc.

    Additional Resources

    Working on just a specific path within a JSON document will reduce network bandwidth requirements - see the Sub-Document pages.

    Our Query Engine enables retrieval of information using the SQL-like syntax of N1QL.