{"id":14249,"date":"2023-04-04T13:27:26","date_gmt":"2023-04-04T20:27:26","guid":{"rendered":"https:\/\/www.couchbase.com\/blog\/?p=14249"},"modified":"2025-06-13T23:00:08","modified_gmt":"2025-06-14T06:00:08","slug":"updating-sensor-data-exploring-couchbases-multi-model-options","status":"publish","type":"post","link":"https:\/\/www.couchbase.com\/blog\/updating-sensor-data-exploring-couchbases-multi-model-options\/","title":{"rendered":"Updating Sensor Data: Exploring Couchbase&#8217;s Multi-Model Options"},"content":{"rendered":"<p><span style=\"font-weight: 400;\">Couchbase has become a popular choice for <\/span><a href=\"https:\/\/www.couchbase.com\/solutions\/iot-data-management\/\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400;\">IoT use cases<\/span><\/a><span style=\"font-weight: 400;\">, thanks to its flexible <\/span><a href=\"https:\/\/www.couchbase.com\/blog\/how-multimodel-databases-can-reduce-data-sprawl\/\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400;\">multi-model<\/span><\/a><span style=\"font-weight: 400;\"> data management capabilities.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Recently, I was working with a customer in the cruise industry that had a unique challenge &#8211; they needed Couchbase to receive and store frequent updates from many sensors that record readings on their fleet of ships. These readings could potentially come to Couchbase out of chronological order. How can they ensure that a new sensor reading could only be stored if it had a later timestamp than the previous reading?<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Each sensor has a unique key that corresponds to the latest sensor reading. A reading from 10:43:00 AM could not overwrite a reading from 10:42:30 AM, even if the latter had been received later. Below are some sample readings and their order of processing (note the timestamps are not necessarily in chronological order):<\/span><\/p>\n<pre class=\"nums:false lang:js decode:true \">id: \"C-DI_Nautical_Speed\"\r\n{\r\n\u00a0 \u00a0 \"speed\": 15,\r\n\u00a0 \u00a0 \"unit\": \"knots\",\r\n\u00a0 \u00a0 \"timeStamp\": \"2023-03-10 10:43:00 AM\"\r\n}\r\n\r\nid: \"C-DI_Nautical_Speed\"\r\n{\r\n\u00a0 \u00a0 \"speed\": 15.1,\r\n\u00a0 \u00a0 \"unit\": \"knots\",\r\n\u00a0 \u00a0 \"timeStamp\": \"2023-03-10 11:43:00 AM\"\r\n},\r\n\r\nid: \"C-DI_Nautical_Speed\"\r\n{\r\n\u00a0 \u00a0 \"speed\": 14.9,\r\n\u00a0 \u00a0 \"unit\": \"knots\",\r\n\u00a0 \u00a0 \"timeStamp\": \"2023-03-10 10:42:30 AM\"\r\n}<\/pre>\n<p><span style=\"font-weight: 400;\">In this blog post, we\u2019ll explore how Couchbase\u2019s multi-model options can help tackle this scenario and efficiently manage sensor data updates.<\/span><\/p>\n<h2>What is Multi-Model?<\/h2>\n<p><span style=\"font-weight: 400;\">Couchbase is perhaps the original multi-model database, as it combines memory-first caching with JSON data persistence to provide a flexible approach to data management. Couchbase can handle multiple data types, such as structured, semi-structured, and unstructured data, in the same database instance.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Over time, Couchbase has added <\/span><a href=\"https:\/\/www.couchbase.com\/sqlplusplus\/\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400;\">SQL++<\/span><\/a><span style=\"font-weight: 400;\">, <\/span><a href=\"https:\/\/www.couchbase.com\/products\/full-text-search\/\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400;\">Full Text Search (FTS)<\/span><\/a><span style=\"font-weight: 400;\">, <\/span><a href=\"https:\/\/www.couchbase.com\/products\/eventing\/\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400;\">Eventing<\/span><\/a><span style=\"font-weight: 400;\">, <\/span><a href=\"https:\/\/www.couchbase.com\/products\/analytics\/\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400;\">Analytics<\/span><\/a><span style=\"font-weight: 400;\"> tools: multiple models for accessing, indexing, and interacting with the same pool of data. This multi-model approach can make Couchbase more flexible than traditional databases, but it can also require a little more thought about tradeoffs compared to those legacy systems (that might only have one way to interact with data).<\/span><\/p>\n<h2>Multi-Model Options for Updating Sensor Readings<\/h2>\n<p><span style=\"font-weight: 400;\">When it comes to updating sensor readings for this use case in Couchbase\u2019s multi-model database, there are several approaches to consider:<\/span><\/p>\n<ol>\n<li style=\"list-style-type: none;\">\n<ol>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">Key-value API with optimistic or pessimistic locking<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">Key-value API with ACID transaction<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">SQL++ UPDATE statement<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">Eventing OnUpdate function<\/span><\/li>\n<\/ol>\n<\/li>\n<\/ol>\n<p><span style=\"font-weight: 400;\">All of these options have their own set of advantages and trade-offs in terms of performance, complexity, and requirements. Choosing the best approach will depend on factors such as the size and frequency of the updates, the level of concurrency, and the overall performance requirements.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Ultimately, the best approach can only be determined through real-world testing with live data or a good approximation of live data. By examining the trade-offs and experimenting with the different options, developers can identify the most effective method for updating sensor readings in Couchbase\u2019s multi-model database.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">It\u2019s important to note that in many of these scenarios, we assume that the sensor document already exists (which will be the most common scenario in a steady state). When that\u2019s not the case, we can change the <em>replace<\/em> or <em>update<\/em> operation to <em>upsert<\/em>\u00a0to ensure that the document is created if it does not exist. (Alternatively, you could &#8220;seed&#8221; the collection with a document for each sensor).<\/span><\/p>\n<p><span style=\"font-weight: 400;\">All that being said, let\u2019s examine each possibility.<\/span><\/p>\n<h2>Key-value API with Optimistic or Pessimistic Locking<\/h2>\n<p><span style=\"font-weight: 400;\">One approach to updating sensor readings in Couchbase\u2019s multi-model database is through optimistic or pessimistic locking. This locking mechanism, which has been present in Couchbase for a long time, uses a technique called <\/span><a href=\"https:\/\/docs.couchbase.com\/dotnet-sdk\/current\/howtos\/concurrent-document-mutations.html\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400;\">CAS (compare and swap)<\/span><\/a><span style=\"font-weight: 400;\"> to ensure conditional updates of individual documents.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">The CAS value is an arbitrary number that changes every time a document changes. By matching CAS values, developers can conditionally update sensor data with minimal overhead. In this section, we will explore how optimistic and pessimistic locking can be used for this sensor data use case.<\/span><\/p>\n<h3>Optimistic Locking<\/h3>\n<p><span style=\"font-weight: 400;\">Optimistic locking is a straightforward approach to updating sensor data in Couchbase, with only three steps required:<\/span><\/p>\n<p style=\"padding-left: 40px;\"><span style=\"font-weight: 400;\">The <\/span><b>first step<\/b><span style=\"font-weight: 400;\"> involves retrieving the document by key, which includes the document value and its metadata (including the CAS value).<\/span><\/p>\n<p style=\"padding-left: 40px;\"><span style=\"font-weight: 400;\">Once retrieved, the <\/span><b>second step<\/b><span style=\"font-weight: 400;\"> is to check if the timestamp is older than the incoming timestamp.<\/span><\/p>\n<p style=\"padding-left: 40px;\"><span style=\"font-weight: 400;\">If it is, the <\/span><b>third step<\/b><span style=\"font-weight: 400;\"> involves replacing the document with the new value and submitting the CAS value with it.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Here\u2019s where the &#8220;optimistic&#8221; part comes in. If the CAS values match, the operation is successful, and the sensor data is updated. However, if the CAS value doesn\u2019t match, it means that the sensor data has been updated (by some other thread\/process) since the last read operation. In this case, you have the option to <\/span><b>retry the operation<\/b><span style=\"font-weight: 400;\"> from the beginning. If you don\u2019t expect the specific sensor document to get updated frequently, then optimistic locking is the way to go (as retries would be infrequent).<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Here\u2019s an example of optimistic locking with simple retry logic:<\/span><\/p>\n<pre class=\"nums:false lang:js decode:true \">\/\/ get existing sensor reading\r\nvar currentDoc = await _collection.GetAsync(sensorId);\r\nvar currentDocCas = currentDoc.Cas;\r\nvar currentReading = currentDoc.ContentAs&lt;NauticalSpeed&gt;();\r\n\r\n\/\/ check timestamps\r\nif (newSensorReading.TimeStamp &gt; currentReading.TimeStamp)\r\n{\r\n\u00a0 \u00a0 \/\/ incoming reading is newer, update the record\r\n\u00a0 \u00a0 Console.WriteLine(\"Incoming sensor reading is newer. Updating.\");\r\n\u00a0 \u00a0 var retries = 3;\r\n\u00a0 \u00a0 while (retries &gt; 0)\r\n\u00a0 \u00a0 {\r\n\u00a0 \u00a0 \u00a0 \u00a0 try\r\n\u00a0 \u00a0 \u00a0 \u00a0 {\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 await _collection.ReplaceAsync(sensorId, newSensorReading, options =&gt; options.Cas(currentDocCas));\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 return;\r\n\u00a0 \u00a0 \u00a0 \u00a0 }\r\n\u00a0 \u00a0 \u00a0 \u00a0 catch (CasMismatchException)\r\n\u00a0 \u00a0 \u00a0 \u00a0 {\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 Console.WriteLine($\"CAS mismatch. Retries remaining: {retries}\");\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 retries--;\r\n\u00a0 \u00a0 \u00a0 \u00a0 }\r\n\u00a0 \u00a0 }\r\n\u00a0 \u00a0 Console.WriteLine(\"Retry max exceeded. Sensor reading was not updated.\");\r\n}\r\nelse\r\n{\r\n\u00a0 \u00a0 Console.WriteLine(\"Incoming sensor reading is not new. Ignoring.\");\r\n\u00a0 \u00a0 \/\/ incoming reading is not newer, so do nothing\r\n\u00a0 \u00a0 \/\/ (or possibly update a log, or whatever else you want to do)\r\n}<\/pre>\n<h3>Pessimistic Locking<\/h3>\n<p><a href=\"https:\/\/docs.couchbase.com\/dotnet-sdk\/current\/howtos\/concurrent-document-mutations.html#pessimistic-locking\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400;\">Pessimistic locking<\/span><\/a><span style=\"font-weight: 400;\"> is another way to approach the same problem. Like optimistic locking, it has three steps, but with some slight differences.<\/span><\/p>\n<p style=\"padding-left: 40px;\"><span style=\"font-weight: 400;\">The <\/span><b>first step<\/b><span style=\"font-weight: 400;\"> is to <\/span><i><span style=\"font-weight: 400;\">get and lock<\/span><\/i><span style=\"font-weight: 400;\"> a document by key, making note of the CAS value. Unlike optimistic locking, where the document is simply read, in pessimistic locking, the document is explicitly locked. This means that no other process can make changes to the document until it becomes unlocked.<\/span><\/p>\n<p style=\"padding-left: 40px;\"><span style=\"font-weight: 400;\">In the <\/span><b>second step<\/b><span style=\"font-weight: 400;\">, just like optimistic locking, the timestamp is checked to see if it\u2019s older than the incoming timestamp.<\/span><\/p>\n<p style=\"padding-left: 40px;\"><span style=\"font-weight: 400;\">If it is, then in the <\/span><b>third step<\/b><span style=\"font-weight: 400;\">, the document is replaced with the new value and submitted with the CAS value.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">In step 1 of pessimistic locking, you also have to specify a timeout window. Why? It\u2019s possible that step 3 might never happen due to an error or crash, and the document needs to eventually unlock.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">If you expect the sensor document to be updated a lot, pessimistic might be the better approach. But because of the lock, there could be a reduced latency in other processes waiting for the document to become unlocked.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">To illustrate, here\u2019s an example of pessimistic locking in action:<\/span><\/p>\n<pre class=\"nums:false lang:js decode:true \">\/\/ get current sensor data\r\nvar maxLockTime = TimeSpan.FromSeconds(30);\r\nvar currentDoc = await _collection.GetAndLockAsync(sensorId, maxLockTime);\r\nvar currentDocCas = currentDoc.Cas;\r\nvar currentReading = currentDoc.ContentAs&lt;NauticalSpeed&gt;();\r\n\r\n\/\/ check timestamps against new reading\r\nif (newSensorReading.TimeStamp &gt; currentReading.TimeStamp)\r\n{\r\n\u00a0 \u00a0 \/\/ incoming reading is newer, update the record\r\n\u00a0 \u00a0 Console.WriteLine(\"Incoming sensor reading is newer. Updating.\");\r\n\u00a0 \u00a0 await _collection.ReplaceAsync(sensorId, newSensorReading, options =&gt; options.Cas(currentDocCas));\r\n\u00a0 \u00a0 return;\r\n}\r\nelse\r\n{\r\n\u00a0 \u00a0 await _collection.UnlockAsync(sensorId, currentDocCas);\r\n\u00a0 \u00a0 Console.WriteLine(\"Incoming sensor reading is not new. Ignoring.\");\r\n\u00a0 \u00a0 \/\/ incoming reading is not newer, so do nothing\r\n\u00a0 \u00a0 \/\/ (or possibly update a log, or whatever else you want to do)\r\n}<\/pre>\n<h3>CAS Locking Tradeoffs<\/h3>\n<p><span style=\"font-weight: 400;\">When it comes to CAS locking, there are trade-offs to consider. Optimistic locking works well when conflicts are infrequent, but you\u2019ll need to implement appropriate retry logic to handle possible retries.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">To help with this tradeoff, more advanced or specialty retries could be used. For instance, in this use case, it may be acceptable to &#8220;give up&#8221; and discard an incoming sensor reading if there have been a lot of retries and\/or the reading is very old.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Pessimistic locking, on the other hand, is a &#8220;safer&#8221; approach but requires a clear understanding of the performance implications of locking. Locking can increase latency in other processes that need to wait for the document to become unlocked.<\/span><\/p>\n<h2>ACID transaction<\/h2>\n<p><span style=\"font-weight: 400;\">Another potential solution to the sensor update problem is using an ACID transaction. This approach may be overkill for updating a single document in this use case, but it could be useful in different use cases where multiple documents need to be updated atomically.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">A challenge with sensor data is that it can be coming in at a fast rate. In the time between checking the current data and updating with incoming sensor data, another reading could be coming in. To avoid this issue, an ACID transaction can be used to conditionally update data.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">The sample code below demonstrates how to use an ACID transaction to update a sensor document. The transaction ensures that only one update operation can occur at a time per sensor, preventing multiple incoming sensor readings from interfering with each other.<\/span><\/p>\n<pre class=\"nums:false lang:js decode:true \">var transaction = Transactions.Create(_cluster, TransactionConfigBuilder.Create()\r\n\u00a0 \u00a0 .DurabilityLevel(DurabilityLevel.None)); \/\/ set to 'none' because I'm using a single-node dev cluster\r\n\u00a0 \u00a0 \/\/ for more details see: https:\/\/docs.couchbase.com\/dotnet-sdk\/current\/howtos\/distributed-acid-transactions-from-the-sdk.html\r\n\r\nawait transaction.RunAsync(async (context) =&gt;\r\n{\r\n\u00a0 \u00a0 \/\/ get existing sensor reading\r\n\u00a0 \u00a0 var currentDoc = await context.GetAsync(_collection, sensorId);\r\n\u00a0 \u00a0 var currentReading = currentDoc.ContentAs&lt;NauticalSpeed&gt;();\r\n\r\n\u00a0 \u00a0 \/\/ check timestamps\r\n\u00a0 \u00a0 if (newSensorReading.TimeStamp &gt; currentReading.TimeStamp)\r\n\u00a0 \u00a0 {\r\n\u00a0 \u00a0 \u00a0 \u00a0 \/\/ incoming reading is newer, update the record\r\n\u00a0 \u00a0 \u00a0 \u00a0 Console.WriteLine(\"Incoming sensor reading is newer. Updating.\");\r\n\u00a0 \u00a0 \u00a0 \u00a0 await context.ReplaceAsync(currentDoc, newSensorReading);\r\n\u00a0 \u00a0 }\r\n\u00a0 \u00a0 else\r\n\u00a0 \u00a0 {\r\n\u00a0 \u00a0 \u00a0 \u00a0 Console.WriteLine(\"Incoming sensor reading is not new. Ignoring.\");\r\n\u00a0 \u00a0 \u00a0 \u00a0 \/\/ incoming reading is not newer, so do nothing\r\n\u00a0 \u00a0 \u00a0 \u00a0 \/\/ (or possibly update a log, or whatever else you want to do)\r\n\u00a0 \u00a0 }\r\n});<\/pre>\n<h2>ACID Transaction Trade-offs<\/h2>\n<p><span style=\"font-weight: 400;\">The key-value API should be used whenever possible to maximize performance. However, using a distributed ACID transaction in Couchbase will come with some overhead because of the additional key-value operations executed (behind the scenes) to coordinate the transaction. Since data in Couchbase is automatically distributed, operations will likely be coordinated across a network to multiple servers.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">One benefit of using an ACID transaction over a CAS operation is that the Couchbase Transaction libraries already have sophisticated retry logic built into them. This can be a way to avoid writing your own retry logic. Additionally, an ACID transaction is recommended (probably required, in fact) if a use case involves updating multiple sensor documents.<\/span><\/p>\n<h2>SQL++ Update Operation<\/h2>\n<p><span style=\"font-weight: 400;\">Another approach to performing conditional updates is to use a SQL++ UPDATE query.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Here\u2019s an example implementation:<\/span><\/p>\n<pre class=\"nums:false lang:js decode:true \">var retries = 3;\r\nwhile (retries &gt; 0)\r\n{\r\n\u00a0 \u00a0 try\r\n\u00a0 \u00a0 {\r\n\u00a0 \u00a0 \u00a0 \u00a0 await _cluster.QueryAsync&lt;dynamic&gt;(@\"UPDATE sensordata s\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 USE KEYS $sensorId\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 SET s.speed = $newSpeed, s.unit = $newUnit, s.timeStamp = $newTimeStamp\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 WHERE DATE_DIFF_STR($newTimeStamp, s.timeStamp, 'millisecond') &gt; 0\", options =&gt;\r\n\u00a0 \u00a0 \u00a0 \u00a0 {\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 options.Parameter(\"sensorId\", sensorId);\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 options.Parameter(\"newSpeed\", sensorReading.Speed);\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 options.Parameter(\"newUnit\", sensorReading.Unit);\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 options.Parameter(\"newTimeStamp\", sensorReading.TimeStamp);\r\n\u00a0 \u00a0 \u00a0 \u00a0 });\r\n\u00a0 \u00a0 \u00a0 \u00a0 return;\r\n\u00a0 \u00a0 }\r\n\u00a0 \u00a0 c atch (CasMismatchException)\r\n\u00a0 \u00a0 {\r\n\u00a0 \u00a0 \u00a0 \u00a0 Console.WriteLine($\"UPDATE CAS mismatch, tries remaining: {retries}\");\r\n\u00a0 \u00a0 \u00a0 \u00a0 retries--;\r\n\u00a0 \u00a0 }\r\n}\r\nConsole.WriteLine(\"Max retries exceeded, sensor not updated\");<\/pre>\n<p><i><span style=\"font-weight: 400;\">(By the way, using an epoch timestamp will likely provide better performance).<\/span><\/i><\/p>\n<p><span style=\"font-weight: 400;\">As you might have guessed from the code, the SQL++ query is actually using CAS behind the scenes, just as is being done with the KV API example earlier.<\/span><\/p>\n<h2>SQL++ tradeoffs<\/h2>\n<p><span style=\"font-weight: 400;\">The SQL++ approach for conditional updates does come with some trade-offs. Although the <\/span><em><span style=\"font-weight: 400;\">USE KEYS<\/span><\/em><span style=\"font-weight: 400;\"> clause helps to eliminate the need for an index, the query still needs to be parsed by the query service, which <\/span><a href=\"https:\/\/docs.couchbase.com\/server\/current\/learn\/services-and-indexes\/services\/query-service.html#query-execution\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400;\">involves many steps<\/span><\/a><span style=\"font-weight: 400;\">. This can put added pressure on the system if other components are already using the query service.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Overall, since the SQL++ approach is very similar to the KV API with the added overhead of parsing the query, it may not be the best choice unless you have a specific need for complex logic expressed in SQL++ or if using the KV API is not an option.<\/span><\/p>\n<h2>Eventing<\/h2>\n<p><span style=\"font-weight: 400;\">The last approach I want to cover is the use of Eventing.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Eventing in Couchbase consists of writing JavaScript functions that respond to data change events asynchronously and deploying them to the Couchbase cluster.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">For this particular use case, I think that using a &#8220;staging&#8221; collection as a location for the sensor readings initially is the way to go. Here\u2019s the sequence:<\/span><\/p>\n<ol>\n<li style=\"list-style-type: none;\">\n<ol>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">Incoming sensor readings are written to a &#8220;staging&#8221; collection.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">An Eventing <em>OnUpdate<\/em> function responds to new sensor readings.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">The <em>OnUpdate<\/em> function checks timestamps against the corresponding document in the &#8220;current&#8221; collection<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">If timestamp is more current, the document in the &#8220;current&#8221; collection gets updated.<\/span><\/li>\n<\/ol>\n<\/li>\n<\/ol>\n<p><em><span style=\"font-weight: 400;\">OnUpdate<\/span><\/em><span style=\"font-weight: 400;\"> will run when a document is created <\/span><i><span style=\"font-weight: 400;\">or<\/span><\/i><span style=\"font-weight: 400;\"> updated, so it\u2019s okay to leave the old document in staging (this simplifies the eventing code). Also, a TTL can be set on the collection, so that if a sensor reading isn\u2019t updated in a while, it will be automatically cleaned up.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Here\u2019s an example of an eventing function that works with this design:<\/span><\/p>\n<pre class=\"nums:false lang:js decode:true\">function OnUpdate(doc, meta) {\r\n\u00a0 \u00a0 \/\/ Only process documents with a \"timestamp\"\r\n\u00a0 \u00a0 if (doc.timestamp) {\r\n\u00a0 \u00a0 \u00a0 \u00a0 \/\/ Extract timestamp and sensor ID from the staged document\r\n\u00a0 \u00a0 \u00a0 \u00a0 var stagedTimestamp = doc.timestamp;\r\n\r\n\u00a0 \u00a0 \u00a0 \u00a0 \/\/ note that this will loop indefinitely\r\n\u00a0 \u00a0 \u00a0 \u00a0 \/\/ but you can also limit it to a certain number of reties if you wish\r\n\u00a0 \u00a0 \u00a0 \u00a0 while(true) {\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \/\/ Get the current document for the same sensor ID from the \"destination\" collection\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 var currentDoc = dst_col[meta.id];\r\n\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \/\/ If there is no current document, or the staged timestamp is later than the current timestamp, update the current document\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if (!currentDoc || stagedTimestamp &gt; currentDoc.timestamp) {\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \/\/ dst_col is a READ+WRITE ALIAS\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 dst_col[meta.id] = doc;\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \/\/ the whole document is overwritten, but you can also choose to override certain fields if you wish\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 }\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \/\/ src_col is a READ ALIAS\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 var result = couchbase.get(src_col, meta);\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if (result.success) {\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if (result.meta.cas == meta.cas) {\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \/\/ the document was unchanged in the stage collection we are done\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 break;\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 }\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 doc = result.doc;\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 stagedTimestamp = doc.timestamp;\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 } else {\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if (result.error.key_not_found) {\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \/\/ this might be okay, assuming 'staging' collection gets cleaned up or has a TTL\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \/\/ again, this will depend on what kind of retry logic you have\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 break;\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 } else {\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 log('failure could not read stage adv. get: id',meta.id,'result',result);\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 }\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 }\r\n\u00a0 \u00a0 \u00a0 \u00a0 }\r\n\u00a0 \u00a0 }\r\n}<\/pre>\n<p><span style=\"font-weight: 400;\">And here is the config for that eventing function:<\/span><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-14250\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/image_2023-04-04_132102606-674x1024.png\" alt=\"Eventing configuration window in Couchbase\" width=\"674\" height=\"1024\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2023\/04\/image_2023-04-04_132102606-674x1024.png 674w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2023\/04\/image_2023-04-04_132102606-198x300.png 198w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2023\/04\/image_2023-04-04_132102606-768x1167.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2023\/04\/image_2023-04-04_132102606-300x456.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2023\/04\/image_2023-04-04_132102606.png 950w\" sizes=\"auto, (max-width: 674px) 100vw, 674px\" \/><\/p>\n<h2>Eventing Trade-offs<\/h2>\n<p><span style=\"font-weight: 400;\">Again, notice that an optimistic CAS lock is being used in this code. In fact, you could almost say that this was a JavaScript version of the code using the KV API earlier.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">One key difference is that this function is running on the Couchbase cluster itself. And this is the key benefit to eventing: no matter where the sensor data is coming from, Couchbase\u2019s Eventing function will ensure that it gets processed. It\u2019s keeping the logic close to the data. If you have two or more clients that use the KV API instead, that means you need two or more implementations of the same code. This can lead to problems when logic changes, as it will need to be updated in multiple places.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">However, just as with SQL++, Eventing has some overhead that\u2019s involved. In this case, multiple collections, and the eventing service itself. Typically this could involve an additional node of Couchbase in production. Further, Eventing is not currently available in Couchbase Server Community.<\/span><\/p>\n<h2>Summary<\/h2>\n<p><span style=\"font-weight: 400;\">Couchbase is a multi-model database that offers options and tradeoffs for your use case. In this post, the use case of sensor data updates was covered with 4 different data access patterns, each with their pros and cons:<\/span><\/p>\n<ul>\n<li style=\"list-style-type: none;\">\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">KV API &#8211; performant, simple, but may require some retry logic<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">ACID transactions &#8211; reliable, but has overhead<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">SQL++ &#8211; familiar, declarative, but has query parsing and execution overhead<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">Eventing &#8211; close to the data, consolidates logic, but has overhead of eventing service and extra collections<\/span><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">All the code samples are <\/span><a href=\"https:\/\/github.com\/couchbaselabs\/blog-source-code\/tree\/master\/Groves\/143SensorUpdateMultiModel\/src\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400;\">available on GitHub<\/span><\/a><span style=\"font-weight: 400;\">.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Have you thought of a different approach? Leave a comment below, or share it on the <\/span><a href=\"https:\/\/www.couchbase.com\/developers\/community\/\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400;\">Couchbase Discord<\/span><\/a><span style=\"font-weight: 400;\">.<\/span><\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Couchbase has become a popular choice for IoT use cases, thanks to its flexible multi-model data management capabilities. Recently, I was working with a customer in the cruise industry that had a unique challenge &#8211; they needed Couchbase to receive [&hellip;]<\/p>\n","protected":false},"author":71,"featured_media":14251,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[1814,1815,1816,2273,1812,2201],"tags":[9499,9803,1337,9247,9600],"ppma_author":[8937],"class_list":["post-14249","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-application-design","category-best-practices-and-tutorials","category-couchbase-server","category-eventing","category-n1ql-query","category-tools-sdks","tag-acid-transactions","tag-cas-locking","tag-iot","tag-key-value-store","tag-multimodel"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v27.3 (Yoast SEO v27.3) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Updating Sensor Data: Exploring Couchbase&#039;s Multi-Model Options<\/title>\n<meta name=\"description\" content=\"Choosing the best multi-model approach for updating sensor readings depends on the size and frequency of the updates, the level of concurrency, and more.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.couchbase.com\/blog\/updating-sensor-data-exploring-couchbases-multi-model-options\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Updating Sensor Data: Exploring Couchbase&#039;s Multi-Model Options\" \/>\n<meta property=\"og:description\" content=\"Choosing the best multi-model approach for updating sensor readings depends on the size and frequency of the updates, the level of concurrency, and more.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.couchbase.com\/blog\/updating-sensor-data-exploring-couchbases-multi-model-options\/\" \/>\n<meta property=\"og:site_name\" content=\"The Couchbase Blog\" \/>\n<meta property=\"article:published_time\" content=\"2023-04-04T20:27:26+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-06-14T06:00:08+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2023\/04\/couchbase-multi-model-sensor-data.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"628\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Matthew Groves\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@mgroves\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Matthew Groves\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/updating-sensor-data-exploring-couchbases-multi-model-options\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/updating-sensor-data-exploring-couchbases-multi-model-options\\\/\"},\"author\":{\"name\":\"Matthew Groves\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#\\\/schema\\\/person\\\/3929663e372020321b0152dc4fa65a58\"},\"headline\":\"Updating Sensor Data: Exploring Couchbase&#8217;s Multi-Model Options\",\"datePublished\":\"2023-04-04T20:27:26+00:00\",\"dateModified\":\"2025-06-14T06:00:08+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/updating-sensor-data-exploring-couchbases-multi-model-options\\\/\"},\"wordCount\":1983,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/updating-sensor-data-exploring-couchbases-multi-model-options\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/1\\\/2023\\\/04\\\/couchbase-multi-model-sensor-data.png\",\"keywords\":[\"ACID transactions\",\"CAS locking\",\"IoT\",\"key-value store\",\"multimodel\"],\"articleSection\":[\"Application Design\",\"Best Practices and Tutorials\",\"Couchbase Server\",\"Eventing\",\"SQL++ \\\/ N1QL Query\",\"Tools &amp; SDKs\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/updating-sensor-data-exploring-couchbases-multi-model-options\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/updating-sensor-data-exploring-couchbases-multi-model-options\\\/\",\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/updating-sensor-data-exploring-couchbases-multi-model-options\\\/\",\"name\":\"Updating Sensor Data: Exploring Couchbase's Multi-Model Options\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/updating-sensor-data-exploring-couchbases-multi-model-options\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/updating-sensor-data-exploring-couchbases-multi-model-options\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/1\\\/2023\\\/04\\\/couchbase-multi-model-sensor-data.png\",\"datePublished\":\"2023-04-04T20:27:26+00:00\",\"dateModified\":\"2025-06-14T06:00:08+00:00\",\"description\":\"Choosing the best multi-model approach for updating sensor readings depends on the size and frequency of the updates, the level of concurrency, and more.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/updating-sensor-data-exploring-couchbases-multi-model-options\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/updating-sensor-data-exploring-couchbases-multi-model-options\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/updating-sensor-data-exploring-couchbases-multi-model-options\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/1\\\/2023\\\/04\\\/couchbase-multi-model-sensor-data.png\",\"contentUrl\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/1\\\/2023\\\/04\\\/couchbase-multi-model-sensor-data.png\",\"width\":1200,\"height\":628},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/updating-sensor-data-exploring-couchbases-multi-model-options\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Updating Sensor Data: Exploring Couchbase&#8217;s Multi-Model Options\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/\",\"name\":\"The Couchbase Blog\",\"description\":\"Couchbase, the NoSQL Database\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#organization\",\"name\":\"The Couchbase Blog\",\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/2023\\\/04\\\/admin-logo.png\",\"contentUrl\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/2023\\\/04\\\/admin-logo.png\",\"width\":218,\"height\":34,\"caption\":\"The Couchbase Blog\"},\"image\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#\\\/schema\\\/person\\\/3929663e372020321b0152dc4fa65a58\",\"name\":\"Matthew Groves\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/70feb1b28a099ad0112b8d21fe1e81e1a4524beed3e20b7f107d5370e85a07ab?s=96&d=mm&r=gba51e6aacc53995c323a634e4502ef54\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/70feb1b28a099ad0112b8d21fe1e81e1a4524beed3e20b7f107d5370e85a07ab?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/70feb1b28a099ad0112b8d21fe1e81e1a4524beed3e20b7f107d5370e85a07ab?s=96&d=mm&r=g\",\"caption\":\"Matthew Groves\"},\"description\":\"Matthew D. Groves is a guy who loves to code. It doesn't matter if it's C#, jQuery, or PHP: he'll submit pull requests for anything. He has been coding professionally ever since he wrote a QuickBASIC point-of-sale app for his parent's pizza shop back in the 90s. He currently works as a Senior Product Marketing Manager for Couchbase. His free time is spent with his family, watching the Reds, and getting involved in the developer community. He is the author of AOP in .NET, Pro Microservices in .NET, a Pluralsight author, and a Microsoft MVP.\",\"sameAs\":[\"https:\\\/\\\/crosscuttingconcerns.com\",\"https:\\\/\\\/x.com\\\/mgroves\"],\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/author\\\/matthew-groves\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Updating Sensor Data: Exploring Couchbase's Multi-Model Options","description":"Choosing the best multi-model approach for updating sensor readings depends on the size and frequency of the updates, the level of concurrency, and more.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.couchbase.com\/blog\/updating-sensor-data-exploring-couchbases-multi-model-options\/","og_locale":"en_US","og_type":"article","og_title":"Updating Sensor Data: Exploring Couchbase's Multi-Model Options","og_description":"Choosing the best multi-model approach for updating sensor readings depends on the size and frequency of the updates, the level of concurrency, and more.","og_url":"https:\/\/www.couchbase.com\/blog\/updating-sensor-data-exploring-couchbases-multi-model-options\/","og_site_name":"The Couchbase Blog","article_published_time":"2023-04-04T20:27:26+00:00","article_modified_time":"2025-06-14T06:00:08+00:00","og_image":[{"width":1200,"height":628,"url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2023\/04\/couchbase-multi-model-sensor-data.png","type":"image\/png"}],"author":"Matthew Groves","twitter_card":"summary_large_image","twitter_creator":"@mgroves","twitter_misc":{"Written by":"Matthew Groves","Est. reading time":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.couchbase.com\/blog\/updating-sensor-data-exploring-couchbases-multi-model-options\/#article","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/updating-sensor-data-exploring-couchbases-multi-model-options\/"},"author":{"name":"Matthew Groves","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/3929663e372020321b0152dc4fa65a58"},"headline":"Updating Sensor Data: Exploring Couchbase&#8217;s Multi-Model Options","datePublished":"2023-04-04T20:27:26+00:00","dateModified":"2025-06-14T06:00:08+00:00","mainEntityOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/updating-sensor-data-exploring-couchbases-multi-model-options\/"},"wordCount":1983,"commentCount":0,"publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/updating-sensor-data-exploring-couchbases-multi-model-options\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2023\/04\/couchbase-multi-model-sensor-data.png","keywords":["ACID transactions","CAS locking","IoT","key-value store","multimodel"],"articleSection":["Application Design","Best Practices and Tutorials","Couchbase Server","Eventing","SQL++ \/ N1QL Query","Tools &amp; SDKs"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.couchbase.com\/blog\/updating-sensor-data-exploring-couchbases-multi-model-options\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.couchbase.com\/blog\/updating-sensor-data-exploring-couchbases-multi-model-options\/","url":"https:\/\/www.couchbase.com\/blog\/updating-sensor-data-exploring-couchbases-multi-model-options\/","name":"Updating Sensor Data: Exploring Couchbase's Multi-Model Options","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/updating-sensor-data-exploring-couchbases-multi-model-options\/#primaryimage"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/updating-sensor-data-exploring-couchbases-multi-model-options\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2023\/04\/couchbase-multi-model-sensor-data.png","datePublished":"2023-04-04T20:27:26+00:00","dateModified":"2025-06-14T06:00:08+00:00","description":"Choosing the best multi-model approach for updating sensor readings depends on the size and frequency of the updates, the level of concurrency, and more.","breadcrumb":{"@id":"https:\/\/www.couchbase.com\/blog\/updating-sensor-data-exploring-couchbases-multi-model-options\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.couchbase.com\/blog\/updating-sensor-data-exploring-couchbases-multi-model-options\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.couchbase.com\/blog\/updating-sensor-data-exploring-couchbases-multi-model-options\/#primaryimage","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2023\/04\/couchbase-multi-model-sensor-data.png","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2023\/04\/couchbase-multi-model-sensor-data.png","width":1200,"height":628},{"@type":"BreadcrumbList","@id":"https:\/\/www.couchbase.com\/blog\/updating-sensor-data-exploring-couchbases-multi-model-options\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.couchbase.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Updating Sensor Data: Exploring Couchbase&#8217;s Multi-Model Options"}]},{"@type":"WebSite","@id":"https:\/\/www.couchbase.com\/blog\/#website","url":"https:\/\/www.couchbase.com\/blog\/","name":"The Couchbase Blog","description":"Couchbase, the NoSQL Database","publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.couchbase.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.couchbase.com\/blog\/#organization","name":"The Couchbase Blog","url":"https:\/\/www.couchbase.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","width":218,"height":34,"caption":"The Couchbase Blog"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/3929663e372020321b0152dc4fa65a58","name":"Matthew Groves","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/70feb1b28a099ad0112b8d21fe1e81e1a4524beed3e20b7f107d5370e85a07ab?s=96&d=mm&r=gba51e6aacc53995c323a634e4502ef54","url":"https:\/\/secure.gravatar.com\/avatar\/70feb1b28a099ad0112b8d21fe1e81e1a4524beed3e20b7f107d5370e85a07ab?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/70feb1b28a099ad0112b8d21fe1e81e1a4524beed3e20b7f107d5370e85a07ab?s=96&d=mm&r=g","caption":"Matthew Groves"},"description":"Matthew D. Groves is a guy who loves to code. It doesn't matter if it's C#, jQuery, or PHP: he'll submit pull requests for anything. He has been coding professionally ever since he wrote a QuickBASIC point-of-sale app for his parent's pizza shop back in the 90s. He currently works as a Senior Product Marketing Manager for Couchbase. His free time is spent with his family, watching the Reds, and getting involved in the developer community. He is the author of AOP in .NET, Pro Microservices in .NET, a Pluralsight author, and a Microsoft MVP.","sameAs":["https:\/\/crosscuttingconcerns.com","https:\/\/x.com\/mgroves"],"url":"https:\/\/www.couchbase.com\/blog\/author\/matthew-groves\/"}]}},"acf":[],"authors":[{"term_id":8937,"user_id":71,"is_guest":0,"slug":"matthew-groves","display_name":"Matthew Groves","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/70feb1b28a099ad0112b8d21fe1e81e1a4524beed3e20b7f107d5370e85a07ab?s=96&d=mm&r=g","0":null,"1":"","2":"","3":"","4":"","5":"","6":"","7":"","8":""}],"_links":{"self":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts\/14249","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/users\/71"}],"replies":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/comments?post=14249"}],"version-history":[{"count":0,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts\/14249\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/media\/14251"}],"wp:attachment":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/media?parent=14249"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/categories?post=14249"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/tags?post=14249"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/ppma_author?post=14249"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}