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.

    The complete code sample used on this page can be downloaded from here — from which you can see in context how to authenticate and connect to a Couchbase Cluster, then perform these Collection operations.

    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 Go 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 at its simplest:

    // Insert Document
    type myDoc struct {
    	Foo string `json:"foo"`
    	Bar string `json:"bar"`
    }
    document := myDoc{Foo: "bar", Bar: "foo"}
    result, err := collection.Insert("document-key", &document, nil)
    if err != nil {
    	panic(err)
    }

    Options like timeout may also be added to operations. Timeout in the Go SDK has a type value of time.Duration. Timeout sets the timeout value for the underlying network connection.

    // Insert Document with options
    resultwithOptions, err := collection.Insert("document-key-options", &document, &gocb.InsertOptions{
    	Timeout: 3 * time.Second,
    })
    if err != nil {
    	panic(err)
    }

    CAS

    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.

    // Replace Document with Cas
    replaceResultWithCas, err := collection.Replace("document-key", &document, &gocb.ReplaceOptions{
    	Cas: 12345,
    })
    if err != nil {
    	// We expect this to error
    	fmt.Println(err)
    }

    Typically we would want to use CAS for something more meaningful like performing a Get, modifying the result and updating the document. By using the CAS value we know that if anyone else modified this document and updated it before our update then ours will error.

    // Get and Replace Document with Cas
    updateGetResult, err := collection.Get("document-key", nil)
    if err != nil {
    	panic(err)
    }
    
    var doc myDoc
    err = updateGetResult.Content(&doc)
    if err != nil {
    	panic(err)
    }
    
    doc.Bar = "moo"
    
    updateResult, err := collection.Replace("document-key", doc, &gocb.ReplaceOptions{
    	Cas: updateGetResult.Cas(),
    })

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

    // Upsert with Expiry
    expiryResult, err := collection.Upsert(key, &document, &gocb.UpsertOptions{
    	Timeout: 100 * time.Millisecond,
    	Expiry:  60 * time.Second,
    })

    Durability

    Writes in Couchbase are written to a single node, and from there the Couchbase Server will take care of sending that mutation to any configured replicas. The optional durability parameter, which all mutating operations accept, allows the application to wait until this replication (or persistence) is successful before proceeding.

    In Couchbase Server releases before 6.5, Durability was set with two options — see the 6.0 Durability documentation — covering how many replicas the operation must be propagated to and how many persisted copies of the modified record must exist. Couchbase Data Platform 6.5 refines these two options, with Durable Writes — although they remain essentially the same in use. The Go SDK exposes both of these forms of Durability.

    First we will cover the newer durability features available in Couchbase server 6.5 onwards. The SDK exposes three durability levels:

    // DurabilityLevelMajority specifies that a mutation must be replicated (held in memory) to a majority of nodes.
    DurabilityLevelMajority = DurabilityLevel(1)
    
    // DurabilityLevelMajorityAndPersistToActive specifies that a mutation must be replicated (held in memory) to a
    // majority of nodes and also persisted (written to disk) on the active node.
    DurabilityLevelMajorityAndPersistToActive = DurabilityLevel(2)
    
    // DurabilityLevelPersistToMajority specifies that a mutation must be persisted (written to disk) to a majority
    // of nodes.
    DurabilityLevelPersistToMajority = DurabilityLevel(3)

    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.

    Below we can see how to set this on an operation:

    // Upsert with Durability level Majority
    durableResult, err := collection.Upsert("document-key", &document, &gocb.UpsertOptions{
    	DurabilityLevel: gocb.DurabilityLevelMajority,
    })

    If a version of Couchbase Server lower than 6.5 is being used then the application can fall-back to 'client verified' durability. The older type of durability, also known as observe based durability, works by monitoring the server to ensure that the change has been replicated or persisted to the required number of nodes within the timeout specified on the operation. Here we can see how that is set:

    key = "replicateToAndPersistTo"
    val = "Durabilty ReplicateTo and PersistTo Test Value"
    _, err = collection.Upsert(key, &val, &gocb.UpsertOptions{
    	PersistTo:   1,
    	ReplicateTo: 1,
    })
    if err != nil {
    	panic(err)
    }

    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-Document Operations.

    Retrieving full documents

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

    // Get
    getResult, err := collection.Get("document-key", nil)
    if err != nil {
    	panic(err)
    }
    
    var getDoc myDoc
    err = getResult.Content(&getDoc)
    if err != nil {
    	panic(err)
    }
    fmt.Println(getDoc)

    Timeout can also be set, as in the earlier Insert example:

    // Get with timeout
    getTimeoutResult, err := collection.Get("document-key", &gocb.GetOptions{
    	Timeout: 10 * time.Millisecond,
    })
    if err != nil {
    	panic(err)
    }
    
    var getTimeoutDoc myDoc
    err = getTimeoutResult.Content(&getTimeoutDoc)
    if err != nil {
    	panic(err)
    }
    fmt.Println(getTimeoutDoc)

    Removing

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

    // Remove with Durability
    removeResult, err := collection.Remove("document-key", &gocb.RemoveOptions{
    	Timeout:         100 * time.Millisecond,
    	DurabilityLevel: gocb.DurabilityLevelMajority,
    })
    if err != nil {
    	panic(err)
    }

    Expiration / TTL

    We already touched on how to set Expiry on an operation but we didn’t discuss how to handle extending that expiry time. By default, Couchbase documents do not expire, but transient or temporary data may be needed for user sessions, caches, or other temporary documents. You can use expiration values on documents to handle transient data. To prevent a document that already has expiry from expiring you can use Touch operations which will extend the expiry by the time specified.

    // Touch
    touchResult, err := collection.Touch(key, 60*time.Second, &gocb.TouchOptions{
    	Timeout: 100 * time.Millisecond,
    })
    if err != nil {
    	panic(err)
    }

    If you want to get the document at the same time as extending expiry then you can use GetAndTouch.

    // GetAndTouch
    getAndTouchResult, err := collection.GetAndTouch(key, 60, &gocb.GetAndTouchOptions{
    	Timeout: 100 * time.Millisecond,
    })
    if err != nil {
    	panic(err)
    }
    
    var getAndTouchDoc myDoc
    err = getAndTouchResult.Content(&getAndTouchDoc)
    if err != nil {
    	panic(err)
    }
    
    fmt.Println(getAndTouchDoc)

    Unresolved include directive in modules/howtos/pages/kv-operations.adoc - include::6.5@sdk:shared:partial$documents.adoc[]

    Atomic Counters

    The value of a document can be increased or decreased atomically using Binary().Increment() and Binary().Decrement().

    Increment
    binaryC := collection.Binary()
    key := "goDevguideExampleCounter"
    curKeyValue, err := binaryC.Increment(key, &gocb.IncrementOptions{
    	Initial: 10,
    	Delta:   2,
    })
    if err != nil {
    	panic(err)
    }
    Decrement
    // Issue same operation, increment value by 2, to 12
    curKeyValue, err = binaryC.Decrement(key, &gocb.DecrementOptions{
    	Initial: 10,
    	Delta:   4,
    })
    if err != nil {
    	panic(err)
    }
    Increment & Decrement are considered part of the 'binary' API and as such may still be subject to change.

    Scoped KV Operations

    It is possible to perform scoped key-value operations on named Collections with Couchbase Server release 7.0. See the API docs for more information.

    Here is an example showing an upsert in the users collection, which lives in the travel-sample.tenant_agent_00 keyspace:

    agentScope := bucket.Scope("tenant_agent_00")
    usersCollection := agentScope.Collection("users")
    
    type userDoc struct {
    	Name           string `json:"name"`
    	PreferredEmail string `json:"preferred_email"`
    }
    document := userDoc{Name: "John Doe", PreferredEmail: "johndoe111@test123.test"}
    
    result, err := usersCollection.Upsert("user-key", &document, &gocb.UpsertOptions{})
    if err != nil {
    	panic(err)
    }

    Additional Resources

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

    Another way of increasing network performance is to pipeline operations with Bulk Operations API.

    As well as various Formats of JSON, Couchbase can work directly with arbitary bytes, or binary format.

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