<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Ronen Botzer</title>
    <description>The latest articles on Forem by Ronen Botzer (@rbotzer).</description>
    <link>https://forem.com/rbotzer</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F317448%2F57fc4f56-b4c5-4dd7-a05d-db63a1dca068.jpeg</url>
      <title>Forem: Ronen Botzer</title>
      <link>https://forem.com/rbotzer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/rbotzer"/>
    <language>en</language>
    <item>
      <title>In-memory database improvements with Database 7</title>
      <dc:creator>Ronen Botzer</dc:creator>
      <pubDate>Wed, 15 Nov 2023 07:50:18 +0000</pubDate>
      <link>https://forem.com/aerospike/in-memory-database-improvements-with-database-7-3bdf</link>
      <guid>https://forem.com/aerospike/in-memory-database-improvements-with-database-7-3bdf</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eFgI79NP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://developer-hub.s3.us-west-1.amazonaws.com/78458717/improving-in-memory-performance-aerospike-database-7_1700028415335.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eFgI79NP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://developer-hub.s3.us-west-1.amazonaws.com/78458717/improving-in-memory-performance-aerospike-database-7_1700028415335.webp" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Welcome to Aerospike Database 7, and the initial server 7.0 release that sets the foundation for big developer API additions in server 7.1 and beyond. Server 7.0 also overhauls in-memory namespace in significant ways.&lt;/p&gt;

&lt;p&gt;Aerospike releases have always been a continuum, with preceding minor releases providing prerequisite work upon which the next major release stands. For example, server 5.6 added a data structure for set indexes later used for secondary indexes (SI). Server 5.7 overhauled secondary index garbage collection and cut down SI memory consumption by 60%. Aerospike Database 6 was built on these essential improvements. Similarly, server 6.4 removed support for &lt;a href="https://docs.aerospike.com/reference/configuration#single-bin"&gt;&lt;code&gt;single-bin&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://docs.aerospike.com/reference/configuration#data-in-index"&gt;&lt;code&gt;data-in-index&lt;/code&gt;&lt;/a&gt; namespaces, freeing up primary index (PI) space needed for upcoming server 7.1 features.&lt;/p&gt;

&lt;p&gt;Though major server releases have distinct themes, work on specific subsystems doesn’t end when our main focus shifts. Just as server versions 6.1 and 6.4 delivered significant throughput improvements to &lt;a href="https://aerospike.com/products/features/cross-datacenter-replication-xdr/"&gt;cross-datacenter replication&lt;/a&gt; (XDR) following the XDR rewrite theme of Aerospike Database 5, secondary index improvements will continue in Aerospike Database 7 releases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unified storage format revolutionizes in-memory namespaces
&lt;/h2&gt;

&lt;p&gt;After previously using the same storage format for &lt;a href="https://docs.aerospike.com/server/operations/configure/namespace/storage"&gt;namespaces that persist data&lt;/a&gt; on SSD or Intel Optane™ Persistent Memory (PMem), an overhaul of in-memory namespaces in server 7.0 consolidates all three storage engines to the same efficient flat format. This results in multiple operational benefits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Faster restarts for in-memory namespaces
&lt;/h3&gt;

&lt;p&gt;In Aerospike Enterprise Edition (EE) and Standard Edition (SE), the new storage-engine memory places its data in shared memory (shmem) rather than volatile process memory. This means that in-memory namespaces can now &lt;a href="https://docs.aerospike.com/server/operations/manage/aerospike/fast_start"&gt;fast restart&lt;/a&gt; (AKA warmstart) after clean shutdowns. &lt;/p&gt;

&lt;p&gt;An in-memory namespace without persistence shares the fast restart capability mentioned above. More interestingly, cold restarts, during which Aerospike daemon (asd) rebuilds its indexes, run much faster, as record data is read from shared-memory, rather than a storage device. &lt;/p&gt;

&lt;p&gt;An in-memory namespace with storage-backed persistence benefits from faster cold restarts when certain configuration parameters are adjusted (for example, &lt;a href="https://docs.aerospike.com/server/reference/configuration#partition-tree-sprigs"&gt;&lt;code&gt;partition-tree-sprigs&lt;/code&gt;&lt;/a&gt;). Only after a crash will Aerospike read from storage-backed persistence to repopulate record data into memory, along with rebuilding the indexes.&lt;/p&gt;

&lt;p&gt;Adding to its existing capability of backing up index shmem segments to disk, the Aerospike Shared Memory Tool (&lt;a href="https://docs.aerospike.com/tools/asmt"&gt;ASMT&lt;/a&gt;) can now be used after Aerospike shuts down to back up in-memory namespace data ahead of restarting the host machine.&lt;/p&gt;

&lt;p&gt;To summarize, in-memory namespaces without storage-backed persistence don’t lose their data on restarts of asd, and all in-memory namespaces benefit from faster restarts (both warmstart and faster coldstart).&lt;/p&gt;

&lt;h3&gt;
  
  
  Compression for in-memory namespaces
&lt;/h3&gt;

&lt;p&gt;Now you can configure an in-memory namespace to use storage compression, such as ZStandard (zstd), LZ4, or Snappy. Customers already using storage compression in the persistence layer of an in-memory namespace (or data on SSD or PMem) will achieve the same compression ratio for their data, regardless of the storage engine, at the same CPU cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stability and performance improvements for in-memory namespaces
&lt;/h3&gt;

&lt;p&gt;As in-memory and on-device storage use the exact same storage format in server 7.0, an in-memory namespace is mirrored to its persistence layer. This means that its write-block defragmentation happens much faster in memory and no longer requires device reads. The same continuous write-block defrag mechanism eliminates heap fragmentation encountered in the former JEMalloc-based in-memory storage. Similarly, tomb raiding of durable-delete tombstones happens fully in memory and requires no device reads. This removes back-pressure generated by the persistence layer’s devices on in-memory namespace operations.&lt;/p&gt;

&lt;p&gt;The unified format's single-pointer and contiguous record storage improve performance in two other ways. First, reading the entire record costs a single memory access, as opposed to the older scattered bins approach requiring multiple independent reads from RAM. Second, since write-blocks are mirrored to the persistence layer, write operations save the CPU previously consumed by separate serialization to a second storage format.&lt;/p&gt;

&lt;h3&gt;
  
  
  Capacity planning for in-memory namespaces
&lt;/h3&gt;

&lt;p&gt;The first thing to note about in-memory namespaces with storage-backed persistence in server 7.0 is that the persistence layer is exactly the same as the in-memory storage; the two are mirrors of each other, which has a capacity planning implication. For previous versions, we had recommended that persistent storage be a multiple of &lt;a href="https://docs.aerospike.com/server/reference/configuration#memory-size"&gt;&lt;code&gt;memory-size&lt;/code&gt;&lt;/a&gt; (the max memory allocation for the namespace), typically 4x. Starting with server 7.0, persistent storage needs to be at a 1:1 ratio to the memory you wish to dedicate to your in-memory namespace.&lt;/p&gt;

&lt;p&gt;The second thing to be aware of is that in-memory data storage is static, and gets pre-allocated in server 7.0 rather than progressively growing and bound by the (now obsolete) &lt;code&gt;memory-size&lt;/code&gt; configuration parameter.&lt;/p&gt;

&lt;p&gt;Capacity planning for indexes has not changed. In server 7.0, indexes continue to start small and grow in increments defined by the configuration parameters &lt;a href="https://docs.aerospike.com/server/reference/configuration#index-stage-size"&gt;&lt;code&gt;index-stage-size&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://docs.aerospike.com/server/reference/configuration#sindex-stage-size"&gt;&lt;code&gt;sindex-stage-size&lt;/code&gt;&lt;/a&gt;. &lt;a href="https://docs.aerospike.com/server/operations/plan/capacity#calculating-set-index-storage"&gt;Set indexes&lt;/a&gt; grow in 4KiB increments after an initial pre-allocation. After upgrading a cluster node, the namespace indexes consume the same amount of memory as before, out of the system memory not pre-allocated for namespace data storage.&lt;/p&gt;

&lt;p&gt;We no longer have diverging capacity planning formulas for data in-memory versus data on persistent storage. All storage-engine configurations use the same storage format and a single formula in the &lt;a href="https://docs.aerospike.com/server/operations/plan/capacity"&gt;capacity planning guide&lt;/a&gt;. Treating capacity planning of in-memory data storage the same as you would data storage on an SSD is helpful. In both cases, you are aiming to size pre-allocated storage.&lt;/p&gt;

&lt;p&gt;The memory consumed by your indexes (and room for them to grow), plus the pre-allocated namespace data storage, should fit within your host machine’s RAM and within the previously declared memory-size. Read the &lt;a href="https://docs.aerospike.com/server/operations/upgrade/special_upgrades/70_upgrade"&gt;special upgrade instructions for Server 7.0&lt;/a&gt; for more details.&lt;/p&gt;

&lt;h3&gt;
  
  
  Caveats
&lt;/h3&gt;

&lt;p&gt;Support for single-bin namespaces was removed in server 6.4. Aerospike users without single-bin namespaces in their cluster may upgrade to server 7.0 through a regular rolling upgrade. Otherwise, please consult the &lt;a href="https://docs.aerospike.com/server/operations/upgrade/special_upgrades/64_upgrade"&gt;special upgrade instructions for Server 6.4&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Due to the unified storage format, a &lt;a href="https://docs.aerospike.com/server/reference/configuration#write-block-size"&gt;&lt;code&gt;write-block-size&lt;/code&gt;&lt;/a&gt; limit of 8MiB applies to in-memory namespaces (with or without persistence). Aerospike users who depend on the former 128MiB record size limit of in-memory without persistence will need to break up their records. Customers may choose to delay upgrading till server 7.1, which will enable an easier transition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration and monitoring
&lt;/h2&gt;

&lt;p&gt;The newly released &lt;a href="https://aerospike.com/download/#aerospike-monitoring-stack"&gt;Aerospike Observability Stack 3.0&lt;/a&gt; and &lt;a href="https://aerospike.com/download/#aerospike-tools"&gt;Tools 10.0&lt;/a&gt; support metrics and configuration for both Aerospike 6 and Aerospike 7 and are designed to ease your transition to server 7.0. If you are not familiar with &lt;a href="https://aerospike.com/products/observability-management/"&gt;Aerospike Observability and Management&lt;/a&gt; (O&amp;amp;M), we have a short video, &lt;a href="https://aerospike.com/blog/architect/manage-what-matters-aerospike-observability-stack/"&gt;blog&lt;/a&gt;, and webinar available on our site.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration parameter and metric changes in detail
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.aerospike.com/server/operations/configure/namespace"&gt;Namespace configuration&lt;/a&gt;, regardless of &lt;a href="https://docs.aerospike.com/server/operations/configure/namespace/storage"&gt;storage engine&lt;/a&gt; choice, is simpler in server 7.0.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aerospike.com/server/operations/configure/namespace/retention"&gt;Stop-writes and eviction thresholds&lt;/a&gt; are controlled by the storage-engine configuration parameters &lt;a href="https://docs.aerospike.com/server/reference/configuration#stop-writes-used-pct"&gt;&lt;code&gt;stop-writes-used-pct&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://docs.aerospike.com/server/reference/configuration#stop-writes-avail-pct"&gt;&lt;code&gt;stop-writes-avail-pct&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://docs.aerospike.com/server/reference/configuration#evict-used-pct"&gt;&lt;code&gt;evict-used-pct&lt;/code&gt;&lt;/a&gt;, which are relative to the namespace data storage size. There are also a pair of thresholds relative to the system memory - &lt;a href="https://docs.aerospike.com/server/reference/configuration#stop-writes-sys-memory-pct"&gt;&lt;code&gt;stop-writes-sys-memory-pct&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://docs.aerospike.com/server/reference/configuration#evict-sys-memory-pct"&gt;&lt;code&gt;evict-sys-memory-pct&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Data storage metrics have been simplified to &lt;code&gt;data_used_bytes&lt;/code&gt;, &lt;code&gt;data_total_bytes&lt;/code&gt;, &lt;code&gt;data_used_pct&lt;/code&gt;, and &lt;code&gt;data_avail_pct&lt;/code&gt; for all storage engine types.&lt;/p&gt;

&lt;p&gt;Index metrics are also simpler. Set indexes have &lt;code&gt;set_index_used_bytes&lt;/code&gt;. Primary and secondary indexes have &lt;code&gt;index_used_bytes&lt;/code&gt; and &lt;code&gt;sindex_used_bytes&lt;/code&gt;, whether they’re stored in shared memory, PMem, or an SSD. If they’re in persistent storage, they also have &lt;code&gt;index_mounts_used_pct&lt;/code&gt; and &lt;code&gt;sindex_mounts_used_pct&lt;/code&gt; relative to the mounts_budget. &lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-tenancy
&lt;/h2&gt;

&lt;p&gt;Many Aerospike customers deploy their database clusters as a multi-tenant service, with distinct users separated by &lt;a href="https://docs.aerospike.com/server/architecture/data-model"&gt;sets&lt;/a&gt; within a namespace. Multi-tenancy leans on Aerospike enterprise features, such as scoped role-based access control (&lt;a href="https://docs.aerospike.com/server/operations/configure/security/access-control"&gt;RBAC&lt;/a&gt;), &lt;a href="https://docs.aerospike.com/server/guide/security/rate_quotas"&gt;rate quotas&lt;/a&gt;, and &lt;a href="https://docs.aerospike.com/server/operations/manage/sets#capping-the-size-of-a-set"&gt;set quotas&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Server 7.0 makes multi-tenant deployment easier with several new features.The limit of 64K unique bin names per-namespace was removed. Operators no longer need to advise developers to restrict how many bin names their applications write into the namespace. As a result, the &lt;code&gt;bins&lt;/code&gt; info command and  the &lt;code&gt;available_bin_names&lt;/code&gt; namespace statistic, were removed.&lt;/p&gt;

&lt;p&gt;The limit on unique set names per namespaces was raised from 1023 to 4095, allowing for set-level segregation of more tenants on the same Aerospike cluster.&lt;/p&gt;

&lt;p&gt;Finally, an operator can now assign a unique set-level &lt;code&gt;default-ttl&lt;/code&gt; as an override of the namespace &lt;code&gt;default-ttl&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  New developer API features
&lt;/h2&gt;

&lt;p&gt;Server 7.0 adds the capability to &lt;a href="https://docs.aerospike.com/server/guide/query"&gt;index and query&lt;/a&gt; bytes data (BLOBs).&lt;/p&gt;

&lt;p&gt;Application developers may now choose to persist key-ordered Map indexes, trading off extra storage for improved performance. The new MapPolicy option can be applied when creating a new Map bin or with the Map set_type operation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;MapPolicy(MapOrder order, int flags, boolean persistIndex)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Persisting Map indexes should only be used by an application once all the nodes in the Aerospike cluster have been upgraded to version &amp;gt;= 7.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dropping support for old OS versions
&lt;/h2&gt;

&lt;p&gt;Server 6.4 added support for Amazon Linux 2023 and Debian 12. As I &lt;a href="https://developer.aerospike.com/blog/aerospike-database-64-improved-query-and-data-distribution"&gt;previously warned&lt;/a&gt;, server 7.0 removes support and el7 builds for Red Hat Enterprise Linux 7 and its variants, including CentOS 7, Amazon Linux 2, and Oracle Linux 7. Similarly, server 7.0 will not be available on Debian 10.&lt;/p&gt;

&lt;p&gt;Aerospike engineering takes performance seriously, and Aerospike users choose to build mission-critical applications on our database because it has the best cost-performance in its field. Performance is hindered by running new Aerospike server versions on ancient lower-performing OS kernels, such as the 3.10 Linux kernel packaged with RHEL 7. In a recent announcement, the Linux kernel team declared they will no longer offer six years of LTS support. This announcement validated our perspective on the stability and performance cost of running Aerospike on old kernels.&lt;/p&gt;

&lt;p&gt;Both Debian 10 and RHEL 7 reach their end of life (EOL) in June 2024 ( 5 and 10 years from their initial release, respectively ). Each new major and minor Aerospike server version gets two years of bug fixes and security vulnerability support. Going forward, new server versions will not be offered on OS distro versions scheduled to expire during this support period. Subsequent patch releases (hotfixes) will continue to be built and tested on the same OS distro versions as when they were first released.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try Aerospike 7.0
&lt;/h2&gt;

&lt;p&gt;Persistence, fast restart capability, and built-in compression all make the new Aerospike in-memory namespaces appealing for in-memory database use cases.&lt;/p&gt;

&lt;p&gt;Consult the &lt;a href="https://docs.aerospike.com/reference/platform-support"&gt;platform compatibility&lt;/a&gt; page and the &lt;a href="https://developer.aerospike.com/client/client_matrix#minimum-usable-client-versions"&gt;minimum usable client versions&lt;/a&gt; table. For more details, read the &lt;a href="https://docs.aerospike.com/reference/release_notes/server/7.0-server-release-notes"&gt;Server 7.0 release notes&lt;/a&gt;. You can &lt;a href="https://aerospike.com/download/"&gt;download&lt;/a&gt; Aerospike EE and run in a single-node evaluation; you can get started with a 60-day multi-node trial at our &lt;a href="https://aerospike.com/lp/try-now/"&gt;Try Now&lt;/a&gt; page.&lt;/p&gt;

</description>
      <category>aerospike</category>
      <category>inmemory</category>
      <category>speed</category>
    </item>
    <item>
      <title>Aerospike Database 6.4: Improved query and data distribution</title>
      <dc:creator>Ronen Botzer</dc:creator>
      <pubDate>Fri, 04 Aug 2023 15:51:24 +0000</pubDate>
      <link>https://forem.com/aerospike/aerospike-database-64-improved-query-and-data-distribution-31bo</link>
      <guid>https://forem.com/aerospike/aerospike-database-64-improved-query-and-data-distribution-31bo</guid>
      <description>&lt;p&gt;In this blog post, I’ll cover new features and changes in the generally available (GA) release of Aerospike 6.4.This release concludes our secondary index storage offerings with the introduction of secondary index on flash. &lt;/p&gt;

&lt;h2&gt;
  
  
  Secondary index on Flash
&lt;/h2&gt;

&lt;p&gt;Before &lt;a href="https://aerospike.com/products/database/"&gt;Aerospike Database 6&lt;/a&gt; , &lt;a href="https://docs.aerospike.com/server/architecture/secondary-index"&gt;secondary indexes&lt;/a&gt; could only live in the Aerospike daemon (&lt;code&gt;asd&lt;/code&gt;) process memory (RAM). This prevented Aerospike Database Enterprise Edition (EE) clusters from being able to &lt;a href="https://docs.aerospike.com/server/operations/manage/aerospike/fast_start"&gt;fast restart&lt;/a&gt; (AKA warmstart) when secondary indexes were used on a namespace.&lt;br&gt;
One focus of our secondary index work has been to add storage types that can warm-start.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The shared memory (&lt;code&gt;shmem&lt;/code&gt;) sindex-type was added in server 6.1.&lt;/li&gt;
&lt;li&gt;The Intel Optane™ Persistent Memory (&lt;code&gt;pmem&lt;/code&gt;) storage type was added in server 6.3.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://docs.aerospike.com/reference/release_notes/server/6.4-server-release-notes"&gt;Server 6.4&lt;/a&gt; adds the ability to have all secondary indexes in a specified namespace stored on an NVMe flash device. Not only do secondary indexes on flash persist and enable their namespace to warmstart, they also consume no RAM. &lt;a href="https://docs.aerospike.com/server/operations/plan/capacity/secondary_indexes"&gt;Secondary index capacity planning&lt;/a&gt; is simply the calculated memory space cast to disk storage.&lt;/p&gt;

&lt;p&gt;Though secondary indexes on flash only affect writes and queries, they carry a significant extra device IO cost for heavy write workloads, which should be taken into account for capacity planning. That being said, Aerospike should continue to perform well, compared to other databases, in mixed workloads where many reads and queries happen along with writes that modify the secondary index. You should choose the appropriate secondary index type for your use case. &lt;/p&gt;

&lt;p&gt;Write operations are impacted on latency and throughput when they must adjust secondary indexes. For each secondary index on a record bin where a value changed, the server crawls down the B-tree, at worst one device IO per layer of B-tree. As the B-tree layers fan out widely, the roots should get cached by &lt;code&gt;mmap&lt;/code&gt; if the secondary index is used often, and we expect 1-3 device IOs per-write related adjustment. When the secondary index is very big, the cost per-adjustment might be 2-6 device IOs. Having enough memory for &lt;code&gt;mmap&lt;/code&gt; to cache the first couple of layers of the B-tree will avoid paging, and drive down the number of extra IOPS.&lt;/p&gt;

&lt;p&gt;The impact on query performance is use case dependent. In general, the B-tree structure is very efficient. For example, a secondary index with 8 million entries has a B-tree three layers deep. The top two layers only cost a few MiBs of &lt;code&gt;mmap&lt;/code&gt; cache, so traversing to the lowest layer may only cost a single device IO. &lt;/p&gt;

&lt;p&gt;Adjacent values are packed in the same node, so range queries (using &lt;code&gt;BETWEEN&lt;/code&gt;) will get multiple values per device IO. Secondary index on flash will also be efficient for long queries which return many records.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aerospike.com/server/guide/queries"&gt;Short queries&lt;/a&gt; – queries that consistently return a small number of records – will perform noticeably worse on secondary indexes on flash, relative to secondary indexes in memory (shared memory or PMem). In situations where your short queries return no records or a single record, you should model your data differently. Using a secondary index as a &lt;code&gt;UNIQUE INDEX&lt;/code&gt;, regardless of whether the index is in memory or on flash, is an anti-pattern in a NoSQL database where single-key lookups have very low latency.&lt;/p&gt;

&lt;p&gt;The cause for the performance difference between a secondary index on flash and a secondary index in memory is directly tied to the latency difference between memory access and device IO, as well as drives having a finite number of IOPS. &lt;a href="https://docs.aerospike.com/server/operations/configure/namespace/secondary_index"&gt;Tuning&lt;/a&gt; how much memory is given to &lt;code&gt;mmap&lt;/code&gt; has a bigger positive impact with secondary indexes on flash than with the primary index on flash. This is because the access patterns aren’t random - the B-tree roots are effectively cached, and with more RAM available to the &lt;code&gt;mmap&lt;/code&gt; cache, the extra device IO cost decreases. When choosing to store the namespace secondary indexes on flash, you need to have enough aggregate device IO capacity to cover the increased IOPS cost needed to sustain your desired latency and throughput.&lt;/p&gt;

&lt;h2&gt;
  
  
  Secret Agent: Integrating with secrets management services
&lt;/h2&gt;

&lt;p&gt;Starting with server 6.4, Aerospike EE can be configured to tell the newly released &lt;a href="https://docs.aerospike.com/server/operations/configure/security/secrets"&gt;Aerospike Secret Agent&lt;/a&gt; to fetch secrets from an external secrets management service. &lt;/p&gt;

&lt;p&gt;Secret Agent runs as an independent process, either on the same host as the Aerospike cluster node, or on a dedicated instance serving multiple nodes. The agent wraps around the native library of the service provider, and handles authentication against the service. Aerospike server is agnostic of the destination, and with a simple common configuration can be connected to a variety of services.&lt;/p&gt;

&lt;p&gt;This initial release of Secret Agent fetches secrets from &lt;a href="https://aws.amazon.com/secrets-manager/"&gt;AWS Secrets Manager&lt;/a&gt;. The independent design makes it easy to rapidly add new integrations on a separate release cycle from the server, with Google Cloud Secret Manager, Azure Key Vault, and Hashicorp Vault planned next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improved Cross-Datacenter Replication (XDR) throughput
&lt;/h2&gt;

&lt;p&gt;In server 6.4 an optimization previously used in server 6.1 to enhance XDR throughput in recovery mode or when rewinding a namespace was applied to stable-state XDR shipping. Ship requests are distributed across service threads using partition affinity. This highly efficient approach uses fewer service threads to ship more data, while creating significantly fewer socket connections.&lt;/p&gt;

&lt;p&gt;As a result, the &lt;a href="https://docs.aerospike.com/reference/configuration#max-used-service-threads"&gt;&lt;code&gt;max-used-service-threads&lt;/code&gt;&lt;/a&gt; configuration parameter was removed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Newly supported operating system distributions
&lt;/h2&gt;

&lt;p&gt;Server 6.4 adds support for &lt;a href="https://aws.amazon.com/linux/amazon-linux-2023/"&gt;Amazon Linux 2023&lt;/a&gt;. While Amazon Linux 2 is based on, and mostly compatible with, CentOS 7, AL2023 is a &lt;a href="https://docs.aws.amazon.com/linux/al2023/ug/compare-with-al2.html"&gt;distinct flavor of Linux&lt;/a&gt;. As such, the Aerospike Database 6.4 server and tools packages have an &lt;code&gt;amzn2023&lt;/code&gt; RPM distinct from the &lt;code&gt;el7&lt;/code&gt; one used for Amazon Linux 2.&lt;/p&gt;

&lt;p&gt;We recommend that users of Amazon Linux 2 upgrade their OS to AL2023 because CentOS 7 and Amazon Linux 2 will lose support in Aerospike Database 7.&lt;/p&gt;

&lt;p&gt;Server 6.4 also adds support for Debian 12. Users of Debian 10 are encouraged to upgrade in order to enjoy the benefits of the newer OS kernel.&lt;/p&gt;

&lt;h2&gt;
  
  
  End of the line for single-bin namespaces
&lt;/h2&gt;

&lt;p&gt;After many years as an optional &lt;a href="https://docs.aerospike.com/server/operations/configure/namespace/storage"&gt;storage configuration&lt;/a&gt;, support for single-bin namespaces was removed in server 6.4. Single-bin namespaces provided savings of several bytes per record, an appealing feature when Aerospike Database was a new product, flash storage was much slower, and SSD and NVMe drives were much smaller. Today, common modern flash drives are significantly faster and larger, Aerospike’s storage formats are far more efficient than they were when single-bin was introduced, and Aerospike EE has a compression feature that helps reduce disk storage consumption.&lt;/p&gt;

&lt;p&gt;The removal of the &lt;a href="https://docs.aerospike.com/reference/configuration#single-bin"&gt;&lt;code&gt;single-bin&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://docs.aerospike.com/reference/configuration#data-in-index"&gt;&lt;code&gt;data-in-index&lt;/code&gt;&lt;/a&gt; configuration parameters paves the way for many exciting new features in the upcoming Aerospike Database 7, including a major overhaul of the in-memory storage engine. Users of single-bin namespaces may choose to delay their upgrade until server 7.0 in order to gain the benefit of these new features while avoiding the pain of upgrading to a server version which no longer allows for these storage options.&lt;/p&gt;

&lt;p&gt;Aerospike users without single-bin namespaces in their cluster may upgrade to server 6.4 through a regular rolling upgrade. Otherwise, please consult the &lt;a href="https://docs.aerospike.com/server/operations/upgrade/special_upgrades/64_upgrade"&gt;special upgrade instructions for Server 6.4&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Miscellaneous
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Query performance - sharing partition reservations provides enhanced query performance for equality (point) and &lt;code&gt;BETWEEN&lt;/code&gt; (range) short queries, scans, and set index queries.&lt;/li&gt;
&lt;li&gt;Improved defragmentation - Server 6.4 avoids superfluous defragmentation of blocks with indexed records.&lt;/li&gt;
&lt;li&gt;Batch operations - The configuration parameter &lt;a href="https://docs.aerospike.com/reference/configuration#batch-max-requests"&gt;&lt;code&gt;batch-max-requests&lt;/code&gt;&lt;/a&gt; was removed in server 6.4. Also, the benchmarks &lt;code&gt;{ns}-batch-sub-read&lt;/code&gt;, &lt;code&gt;{ns}-batch-sub-write&lt;/code&gt;, &lt;code&gt;{ns}-batch-sub-udf&lt;/code&gt; are now auto-enabled and exposed by the &lt;a href="https://docs.aerospike.com/reference/info#latencies"&gt;&lt;code&gt;latencies&lt;/code&gt;&lt;/a&gt; info command.&lt;/li&gt;
&lt;li&gt;Hot-key logging - the new &lt;code&gt;key-busy&lt;/code&gt; &lt;a href="https://docs.aerospike.com/server/operations/configure/logging/aerospike-logs"&gt;logging context&lt;/a&gt; provides the digest, user IP, and type of operation when an error code 14 (&lt;code&gt;KEY_BUSY&lt;/code&gt;) is returned to the client.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Breaking Changes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://docs.aerospike.com/reference/configuration#scheduler-mode"&gt;&lt;code&gt;scheduler-mode&lt;/code&gt;&lt;/a&gt; configuration parameter was removed. &lt;/li&gt;
&lt;li&gt;The deprecated scan &lt;a href="https://docs.aerospike.com/reference/info"&gt;info commands&lt;/a&gt; (&lt;code&gt;scan-show&lt;/code&gt;, &lt;code&gt;scan-abort&lt;/code&gt;, &lt;code&gt;scan-abort-all&lt;/code&gt;) have been removed. Use the equivalent query commands (&lt;code&gt;query-show&lt;/code&gt;, &lt;code&gt;query-abort&lt;/code&gt;, &lt;code&gt;query-abort-all&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more details, read the &lt;a href="https://docs.aerospike.com/reference/release_notes/server/6.4-server-release-notes"&gt;Server 6.4 release notes&lt;/a&gt;. You can &lt;a href="https://aerospike.com/download/"&gt;download&lt;/a&gt; Aerospike EE and run in a single-node evaluation; you can get started with a 60 day multi-node trial at our &lt;a href="https://aerospike.com/lp/try-now/"&gt;Try Now&lt;/a&gt; page.&lt;/p&gt;

</description>
      <category>aerospike</category>
      <category>database</category>
    </item>
    <item>
      <title>Aerospike Database 6: Partitioned Secondary Index Queries, Batch Anything, and JSON Document Models</title>
      <dc:creator>Ronen Botzer</dc:creator>
      <pubDate>Thu, 28 Apr 2022 21:46:12 +0000</pubDate>
      <link>https://forem.com/aerospike/aerospike-database-6-partitioned-secondary-index-queries-batch-anything-and-json-document-models-20hk</link>
      <guid>https://forem.com/aerospike/aerospike-database-6-partitioned-secondary-index-queries-batch-anything-and-json-document-models-20hk</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bHrmTawD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://developer-hub.s3.us-west-1.amazonaws.com/ronen-botzer/diagram-Aerospike-Real-time-Data-Platform-1900w_1651181763224.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bHrmTawD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://developer-hub.s3.us-west-1.amazonaws.com/ronen-botzer/diagram-Aerospike-Real-time-Data-Platform-1900w_1651181763224.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Aerospike Database 6: Partitioned Secondary Index Queries, Batch Anything, and JSON Document Models
&lt;/h1&gt;

&lt;p&gt;Aerospike is proud to announce Aerospike Database 6, our newest database server release, now generally available (GA). This version of Aerospike is full of exciting developer features, which open up new capabilities for application developers to build on.&lt;/p&gt;

&lt;p&gt;Launched two years ago, Aerospike Database 5 delivered a vastly improved Cross-Datacenter Replication (XDR) subsystem. It enabled our customers to create high-performance, geo-distributed applications with fine-grain control over the distribution of their data. Our newest database product release builds on these features and reflects our increased focus on queries. Combined with Aerospike Connect for Spark and Aerospike Connect for Presto (Trino), the Aerospike Data Platform enables our customers to serve both low latency transactional and analytical workloads against their large data sets. &lt;/p&gt;

&lt;p&gt;We will cover three primary capabilities in this blog post. First, a discussion of the new query capabilities in our 6.0 release, and second, support for document data models through the Document API and our secondary index work, and finally our completion of batch operations, including batch writes, for greater efficiency through pipelined operations.&lt;/p&gt;

&lt;p&gt;Server version 6.0. comes after seven release candidates, and is the culmination of 14 months of engineering effort. This is a major release, and includes breaking changes. Please review the &lt;a href="https://enterprise.aerospike.com/enterprise/download/server/notes.html"&gt;release notes&lt;/a&gt; and the &lt;a href="https://docs.aerospike.com/server/operations/upgrade/special_upgrades"&gt;special upgrade instructions&lt;/a&gt; related to the new storage format and new secondary index query capability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Partitioned Secondary Index Queries
&lt;/h2&gt;

&lt;p&gt;The path to the new query subsystem started in the last two releases of Aerospike Database 5.&lt;/p&gt;

&lt;p&gt;Server version 5.6 added &lt;a href="https://docs.aerospike.com/server/architecture/set-indexes"&gt;set indexes&lt;/a&gt;, an optional index type that &lt;a href="https://aerospike.com/blog/set-index-performance/"&gt;improves performance&lt;/a&gt; for a special kind of query. Using set indexes enables low latency access to all the records of a small set that lives inside a large namespace. Like the primary index, set indexes support &lt;a href="https://docs.aerospike.com/server/operations/manage/aerospike/fast_start"&gt;fast restart&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Server version 5.7 delivered a 60% reduction in the memory consumed by secondary indexes, and a brand new, highly efficient garbage collection system. Query performance and throughput were also improved.&lt;/p&gt;

&lt;p&gt;Aerospike Database 6 builds on these changes with a new architectural approach aligned with the design of the &lt;a href="https://docs.aerospike.com/server/architecture/primary-index"&gt;primary index&lt;/a&gt;. The data in each Aerospike namespace is evenly distributed across 4096 logical partitions, which in-turn are evenly distributed across the cluster nodes. The data for each partition is stored and indexed locally in multiple primary index sub-trees called &lt;em&gt;sprigs&lt;/em&gt;. This enables primary index (PI) queries (formerly known as ‘scans’) to be massively parallelized. &lt;/p&gt;

&lt;p&gt;A PI query can target all the partitions, a set of partitions, or a single data partition. Leveraging this capability, the Spark and Presto (Trino) connectors can split a PI query into hundreds and thousands of partitioned queries, feeding data to many thousands of workers in parallel, and attacking the job of rapidly processing terabytes of data through horizontal scaling. This approach fits well with the architecture of these analytics systems. The combination creates a next-level distributed computing data platform.&lt;/p&gt;

&lt;p&gt;Before version 6.0, secondary index queries could only be parallelized at the node level. This meant that if a cluster had 40 nodes, the best parallelization the Spark and Presto (Trino) connectors could possibly use was 40 workers. As our customers make production use of Spark clusters with thousands of cores, having the huge majority of them sit idle was unacceptable, so these connectors did not implement support for secondary index queries.&lt;/p&gt;

&lt;p&gt;In version 6.0, secondary indexes have been re-architected to separately index each partition. This enables massively parallelizing secondary index (SI) queries, as well as supporting pagination, similar to PI queries. Furthermore, SI queries in version 6.0 are tolerant of &lt;a href="https://docs.aerospike.com/server/architecture/data-distribution#automatic-rebalancing"&gt;rebalancing&lt;/a&gt;, unaffected by the automatic data migration that occurs when the cluster size changes. As a result, the Spark and Presto (Trino) connectors will implement SI query support in the same way that they currently do PI queries. This opens the door for operators of Aerospike to optionally trade memory for performance improvements. By adding secondary indexes to sets that have the right cardinality, SI queries can run at orders of magnitude better speeds than equivalent PI queries.&lt;/p&gt;

&lt;p&gt;The change in the secondary index architecture is reflected in the server’s query subsystem, which now unifies both types of queries - primary index and secondary index ones. This change goes deep into a common execution layer; into metrics, which have been merged and renamed; into the client API, which deprecates the Scan class, and provides the same rich functionality to both PI and SI queries from a single Query class.&lt;/p&gt;

&lt;p&gt;Partitioned queries are achieved through client-server coordination, and require a new client version, such as Java client 6.0.0, C client 6.0.0, Go client 6.0.0, C# client 5.0.0 or Python client 7.0.0. Applications using the previous release of these clients may run against server 6.0, but will not benefit from the rebalance tolerance. Similarly, the new clients can talk to both server 5.x and server 6.0 nodes, but will need the cluster upgrade to be completed to unlock the new features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upcoming Query Features in Aerospike Database 6
&lt;/h2&gt;

&lt;p&gt;Aerospike has delivered better query performance, a lower memory footprint for indexes, query stability, and higher query throughput. Subsequent releases of Aerospike Database 6 will add more functionality and operational improvements to queries.&lt;/p&gt;

&lt;p&gt;In Aerospike Database Community Edition (CE), the primary index and secondary indexes are stored in process memory, which means that they must be rebuilt upon restart in a relatively lengthy cold restart. In Aerospike Database Enterprise Edition (EE), the primary index is kept in shared memory by default, or optionally in persistent memory or on a flash device. This enables an Aerospike EE server to go through a warm (fast) restart, which is significantly faster. Server version 6.1 will add the ability to store secondary indexes in shared memory, allowing warm restarts of the Aerospike daemon (asd) when they are present. Later versions will allow secondary indexes to be stored in persistent memory and even on flash devices.&lt;/p&gt;

&lt;p&gt;Currently secondary indexes can be built over the top level keys of a Map data structure. This is typically employed to index the top-level fields of JSON documents, which are stored in Aerospike as Maps. Server version 6.1 will add the ability to index elements nested at any depth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storing, Indexing and Querying JSON Documents
&lt;/h2&gt;

&lt;p&gt;Since the introduction of Map and List Collection Data Types (CDTs), developers have been storing JSON documents in key-ordered Maps, and using Aerospike as a document database. Developers use the rich Map and List APIs in multi-operation transactions to query and manipulate document data atomically on the server-side. Documents (Maps) are stored in a space-efficient &lt;a href="https://msgpack.org/index.html"&gt;MessagePack&lt;/a&gt; binary serialization, facilitating fast access.&lt;/p&gt;

&lt;p&gt;The Aerospike Document API library (introduced mid 2021) added the ability to store, modify and query documents using the popular JSONPath query language. The Document API splits these queries into server-side execution based on the native Map API, and augmented by a JSONPath library.&lt;/p&gt;

&lt;p&gt;The Document API is currently available as a wrapper to the Java client, and as an interface in the Aerospike gateway (also known as the REST client). The Document API library will be ported to other programming languages that have an Aerospike client.&lt;/p&gt;

&lt;p&gt;Together with the upcoming capability to index deeply nested elements, Aerospike Database 6 enhances the development of applications that use a document model approach. Combined with features such as Strong Consistency and Aerospike’s ability to scale up to petabytes of data and hundreds of billions of objects, while maintaining sub-millisecond transaction latencies, results in a document database at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Batch Anything
&lt;/h2&gt;

&lt;p&gt;Since the beginning, the client has had support for a simple batch &lt;code&gt;get&lt;/code&gt; command, to allow  multiple records (or bins within them) to be retrieved together based on a list of keys. Similarly, a batch &lt;code&gt;exists&lt;/code&gt; command checks on the existence of multiple keys all at once, from a specified list of keys.&lt;/p&gt;

&lt;p&gt;Later the client added the ability to execute the same multi-operation transaction against a list of keys in parallel, using the batch &lt;code&gt;operate&lt;/code&gt; command, but limited the type of operations in the transaction to read-only ones.&lt;/p&gt;

&lt;p&gt;With server 6.0, the addition of batch write commands (&lt;code&gt;put&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;, &lt;code&gt;operate&lt;/code&gt; transactions without restrictions on write operations) completes the ability of a developer to &lt;a href="https://docs.aerospike.com/server/guide/batch"&gt;batch anything&lt;/a&gt; in their application - reads, writes, updates, deletes or UDFs. Logically related operations can be sent all at once to the database cluster.&lt;/p&gt;

&lt;p&gt;Batch writes are more efficient than asynchronously launching a series of commands at the server. Using batch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduces the round-trip time (RTT) needed to complete all the operations, lowering the overall latency&lt;/li&gt;
&lt;li&gt;Reduces network traffic, using less connections, and combining operations into fewer IP packets&lt;/li&gt;
&lt;li&gt;Improves parallelization, supporting faster data ingest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Developers of applications used in heavy writes or mixed workloads should consider converting from async writes to batch, for better performance and a more stable cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Enhancements
&lt;/h2&gt;

&lt;p&gt;Server 6.0 adds three new granular privileges for role-based &lt;a href="https://docs.aerospike.com/server/operations/configure/security/access-control"&gt;access control&lt;/a&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;sindex-admin&lt;/code&gt; privilege grants a user the ability to add and drop secondary indexes. &lt;/li&gt;
&lt;li&gt;The &lt;code&gt;udf-admin&lt;/code&gt; privilege grants a user the ability to add and remove UDF modules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Previously these privileges were only available through the &lt;code&gt;data-admin&lt;/code&gt; privilege, which some users were reluctant to grant widely.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;truncate&lt;/code&gt; privilege is now a standalone privilege, and no longer a part of the &lt;code&gt;write&lt;/code&gt; privilege. Users representing applications that perform truncates should be granted the &lt;code&gt;truncate&lt;/code&gt; privilege to one of their roles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Breaking Changes
&lt;/h2&gt;

&lt;p&gt;As mentioned earlier, make sure to read the &lt;a href="https://enterprise.aerospike.com/enterprise/download/server/notes.html"&gt;release notes&lt;/a&gt; and the &lt;a href="https://docs.aerospike.com/server/operations/upgrade/special_upgrades"&gt;upgrade instructions&lt;/a&gt;. The breaking changes in server version 6.0 include&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A storage format change (the addition of a 4 byte end marker to each record) requires that persistent storage devices (with the exception of PMEM) be erased as part of the upgrade. The header (first 8MiB) of raw SSD devices must be zeroized. See &lt;a href="https://docs.aerospike.com/server/operations/plan/ssd/ssd_init"&gt;SSD Initialization&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Several configuration parameters have been renamed or removed:

&lt;ul&gt;
&lt;li&gt;A small number of configuration parameters have been renamed.

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;scan-max-done&lt;/code&gt; to &lt;code&gt;query-max-done&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scan-threads-limit&lt;/code&gt; to &lt;code&gt;query-threads-limit&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;background-scan-max-rps&lt;/code&gt; to &lt;code&gt;background-query-max-rps&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;single-scan-threads&lt;/code&gt; to &lt;code&gt;single-query-threads&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;The following query configuration parameters were removed.

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;query-threads&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;query-worker-threads&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;query-microbenchmark&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;query-batch-size&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;query-in-transaction-thread&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;query-long-q-max-size&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;query-priority&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;query-priority-sleep-us&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;query-rec-count-bound&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;query-req-in-query-thread&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;query-short-q-max-size&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;query-threshold&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;query-untracked-time-ms&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;batch-without-digests&lt;/code&gt; configuration parameter was removed.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;truncate&lt;/code&gt; privilege needs to be granted to applications using truncates. It is no longer part of the &lt;code&gt;write&lt;/code&gt; privilege.&lt;/li&gt;
&lt;li&gt;The long deprecated Predicate Filtering (PredExp) was removed. Use &lt;a href="https://docs.aerospike.com/server/guide/expressions"&gt;Filter Expressions&lt;/a&gt; instead.&lt;/li&gt;
&lt;li&gt;The ‘scan’ module of the &lt;code&gt;jobs:&lt;/code&gt; info command has been removed. Use the ‘query’ module instead.&lt;/li&gt;
&lt;li&gt;Be aware that scan and query related &lt;a href="https://docs.aerospike.com/reference/metrics"&gt;metrics&lt;/a&gt; have changed. We will publish a separate blog to detail these changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deprecation Notice
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;jobs:&lt;/code&gt; info command, initially deprecated in server 5.7 is scheduled to be removed after 6 more months. Use &lt;code&gt;query-show&lt;/code&gt; instead.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;scan-show&lt;/code&gt; info command is now deprecated. Use &lt;code&gt;query-show&lt;/code&gt; instead.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;scan-abort&lt;/code&gt; info command is now deprecated. Use &lt;code&gt;query-abort&lt;/code&gt; instead.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;scan-abort-all&lt;/code&gt; command is now deprecated. Use &lt;code&gt;query-abort-all&lt;/code&gt; instead.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Learn more
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://developer.aerospike.com/blog/config-metric-info-changes-in-database-6"&gt;Get details about Config, Metrics, and Info Changes in Aerospike Database 6.0&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>secondaryindex</category>
      <category>batch</category>
      <category>update</category>
    </item>
    <item>
      <title>Aerospike Modeling: User Profile Store</title>
      <dc:creator>Ronen Botzer</dc:creator>
      <pubDate>Sun, 26 Apr 2020 04:19:55 +0000</pubDate>
      <link>https://forem.com/aerospike/aerospike-modeling-user-profile-store-4k8k</link>
      <guid>https://forem.com/aerospike/aerospike-modeling-user-profile-store-4k8k</guid>
      <description>&lt;h4&gt;
  
  
  Audience Segmentation for Personalization
&lt;/h4&gt;

&lt;p&gt;I recently published the article &lt;a href="https://dev.to/aerospike/aerospike-modeling-iot-sensors-453a"&gt;“Aerospike Modeling: IoT Sensors”&lt;/a&gt; to highlight a different modeling approach when using Aerospike versus Cassandra in IoT use cases. There is a similar juxtaposition of Cassandra’s column-oriented ‘many tiny records’ to Aerospike’s row-oriented ‘fewer, larger, records’ when modeling user profiles.&lt;/p&gt;

&lt;h4&gt;
  
  
  tl;dr
&lt;/h4&gt;

&lt;p&gt;Cassandra databases, including derivatives such as ScyllaDB, have a needle in a haystack problem that affects performance when you need low latency key-value operations. Aerospike is uniquely capable to deliver speed at scale.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fz929cgtgt9n1ofpuzio4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fz929cgtgt9n1ofpuzio4.jpg" alt="Laptop with ads" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;
Photo by &lt;a href="https://unsplash.com/@bugsster?utm_content=creditCopyText"&gt;Taras Shypka&lt;/a&gt; on &lt;a href="https://unsplash.com/?utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;



&lt;h3&gt;
  
  
  Overview
&lt;/h3&gt;

&lt;p&gt;If you haven’t heard the terms &lt;a href="https://www.aerospike.com/solutions/technology/user-profile-store/"&gt;user profile store&lt;/a&gt; or audience segmentation, I recommend Google Cloud’s article on &lt;a href="https://cloud.google.com/solutions/infrastructure-options-for-serving-advertising-workloads#unique_user_profile_store"&gt;digital advertising&lt;/a&gt;, and adpushup’s &lt;a href="https://www.adpushup.com/blog/audience-segmentation/"&gt;What is Audience Segmentation?&lt;/a&gt; Aerospike was first used heavily in the &lt;a href="https://www.aerospike.com/solutions/industry/adtech/"&gt;Ad Tech&lt;/a&gt; ecosystem, so it is not surprising that it’s an effective solution for storing user profiles.&lt;/p&gt;

&lt;p&gt;Audience segmentation for real time bidding (RTB) is a special case of user profile stores. It’s a form of personalization that happens tens of millions of times per-second, without stop, as ads are served in real time around the world to people using apps, visiting web pages, and watching streaming content. It’s simple to describe, and generally applicable to other forms of online personalization.&lt;/p&gt;

&lt;p&gt;A user is typically identified by a cookie containing a unique ID. Associated with that unique ID is a set of audience segment IDs that a Digital Management Platform (DMP) deduced the user to be in. This includes demographic, psychographic, behavioral, and geographic segments.&lt;/p&gt;

&lt;p&gt;The user is supplied to an RTB ad exchange by a Supply Side Platform (SSP). The exchange matches eyeballs to advertisers by providing the user ID to Demand Side Platforms (DSP). These DSPs store DMP provided profiles, and pull up the segments known to match this user ID from their user profile store. With this information, the DSP determines if they have an ad programmed to target the user’s audience segments. The DSP can then choose to bid in the ad exchange on the right to serve an ad to the user. If it wins the bid, it will serve the ad to the user, and the entire process from end-to-end lasts around 150ms. Of that, just 20ms is used to decide on whether to try and serve an ad to the user. Aerospike evolved to deliver speed at very large scales, as the database used for many successful companies in this ecosystem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modeling
&lt;/h3&gt;

&lt;p&gt;There may be millions of distinct segments, each segment ID an integer value. An average user may have a thousand audience segments they occupy at each point in time. When a user is assigned a segment, that association is given a specific time-to-live (TTL) value. As the user profile store is continuously refreshed from DMP data, temporary associations (such as a location change due to travel or a short term interest in something) will expire, while strong associations will have their segment’s TTL extended.&lt;/p&gt;

&lt;h4&gt;
  
  
  Modeling in Cassandra
&lt;/h4&gt;

&lt;p&gt;Let’s consider how you would potentially model this kind of user profile store in Cassandra.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;userspace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_segments&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;segment_id&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;attr1&lt;/span&gt; &lt;span class="nb"&gt;smallint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;attr2&lt;/span&gt; &lt;span class="nb"&gt;smallint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;segment_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You would now upsert &lt;code&gt;user_id, segment_id&lt;/code&gt; data pairs with a TTL, each pair a distinct row.&lt;/p&gt;
&lt;h4&gt;
  
  
  Modeling in Aerospike
&lt;/h4&gt;

&lt;p&gt;In Aerospike we would keep all of a user’s audience segmentation data in a single record, whose key is the user ID. Just a reminder, a record in an Aerospike &lt;a href="https://www.aerospike.com/docs/architecture/data-model.html"&gt;namespace&lt;/a&gt; is uniquely identified by the tuple &lt;code&gt;(namespace, set, user-key)&lt;/code&gt;. The Aerospike client hashes the pair &lt;code&gt;set, user-key&lt;/code&gt; through RIPEMD-160 &lt;a href="https://www.aerospike.com/docs/architecture/data-distribution.html"&gt;into a 20 byte digest&lt;/a&gt;, which is the actual primary index identifier of the record in the namespace. This means that if you keep to the default key policy of &lt;code&gt;KEY_DIGEST&lt;/code&gt;, storage is saved as the set (table) name and the 36 character UUID are hashed into 20 bytes of digest.&lt;/p&gt;

&lt;p&gt;We will store the user’s segments in a map with a segment ID acting as map key, and the tuple &lt;code&gt;[segment-TTL, {attr1, attr2}]&lt;/code&gt; as the map value.&lt;/p&gt;

&lt;p&gt;Depending on the precision we desire for the segment TTL, we can use a smaller numeric value than the 8 bytes needed to hold a Unix epoch timestamp. Let’s assume the precision of segment TTLs is hourly, and our application has a local epoch of January 1st 2019, dating to when it was first deployed. The value of the &lt;code&gt;segment-TTL&lt;/code&gt; would be an enumeration of the hours since that epoch. For example, December 20th, 2019 at 10am is 8530 hours since the epoch.&lt;/p&gt;

&lt;p&gt;So, each user has as a map of {segmentID: [segment-TTL, {attr1, attr2}]}, for example:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="mi"&gt;8457&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8889&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}],&lt;/span&gt;
  &lt;span class="mi"&gt;12845&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8889&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}],&lt;/span&gt;
  &lt;span class="mi"&gt;42199&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8889&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}],&lt;/span&gt;
  &lt;span class="mi"&gt;43696&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8889&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;a href="https://www.aerospike.com/docs/guide/cdt-map.html#element-ordering"&gt;map ordering&lt;/a&gt; options are &lt;code&gt;UNORDERED&lt;/code&gt;, &lt;code&gt;K-ORDERED&lt;/code&gt; and &lt;code&gt;KV-ORDERED&lt;/code&gt;. All map operations can be applied to a map, regardless of its ordering, but it does affect the &lt;a href="https://www.aerospike.com/docs/guide/cdt-map-performance.html"&gt;performance of map operations&lt;/a&gt;. I’ll follow the tip that in general, when a namespace is stored on SSD, choosing &lt;code&gt;K-ORDERED&lt;/code&gt; gives the best performance.&lt;/p&gt;
&lt;h4&gt;
  
  
  Storage Space
&lt;/h4&gt;

&lt;p&gt;If you read my article on modeling IoT uses cases, you already know that Aerospike’s MessagePack serialization will reduce the storage space needed to store these maps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyl64gvuys0p2ii7ewfrz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyl64gvuys0p2ii7ewfrz.png" alt="CE 500K records" width="800" height="180"&gt;&lt;/a&gt;&lt;/p&gt;
CE-4.8.0, 500K users, each with 1000 segments





&lt;p&gt;Using the Aerospike Enterprise Edition &lt;a href="https://www.aerospike.com/products/features/compression/"&gt;Compression&lt;/a&gt; feature further compacts the segmentation data. When I compared running my sample code on CE and EE (using Zstandard level 1 compression), I saw a 0.69 compression ratio, saving about 30% on storage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fi88d4buul6d0twdre7gn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fi88d4buul6d0twdre7gn.png" alt="EE 500K records" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;
EE-4.8.0, 500K users, each with 1000 segments





&lt;p&gt;Aerospike customers modeling similar data saw significantly better compression ratios of 0.25–0.20. As usual, the compression ratio depends on the codec, as well as the data being compressed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fe8cra2qmtbxp18argzwo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fe8cra2qmtbxp18argzwo.png" alt="storage compression stats" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;
Aerospike Enterprise Edition has namespace level statistics for storage compression





&lt;h4&gt;
  
  
  Advantages
&lt;/h4&gt;

&lt;p&gt;This map structure has several advantages. We can use the &lt;a href="https://www.aerospike.com/docs/guide/cdt-map.html#map-apis"&gt;&lt;code&gt;remove_by_value_interval&lt;/code&gt;&lt;/a&gt; map operation to trim expired segments. We can use &lt;code&gt;get_by_value_interval&lt;/code&gt; to filter segments that have a specific ‘freshness’. We can easily upsert new user segments into the map as they are processed.&lt;/p&gt;

&lt;p&gt;Mainly, this allows for orders of magnitude faster retrieval of a user’s segments from the user profile store. In Cassandra finding a single user requires a query pulling a small number of records from a much larger partition. In Aerospike this is a single record read, which is always low latency, regardless of the number of records in the cluster.&lt;/p&gt;

&lt;p&gt;An ad tech user profile store may have tens of billions of profiles, each with a large number of segments. This is due to the fact that there are hundreds of millions (billions) of distinct devices, users utilize incognito modes to browse, and browsers and operating systems anonymize through further means. There are many more ad tech cookies out there than there are humans.&lt;/p&gt;

&lt;p&gt;Let’s consider a real use case where Aerospike was chosen over Scylla to replace a petabyte scale Apache Cassandra user profile store. With &lt;strong&gt;50 billion&lt;/strong&gt; users, and an average of 1000 segments per-user, the C* store had 50 trillion rows. It took many seconds to retrieve a given user from this profile store, leading to an approach of ‘pre warming’ whole segments of users in advance into a smaller front-end Aerospike cluster. This was very costly in terms of cluster resources. For Aerospike, there would only be 50 billion records, one per-user, and fetching any one of those is near 1ms latency.&lt;/p&gt;
&lt;h4&gt;
  
  
  Summit Talk
&lt;/h4&gt;

&lt;p&gt;Matt Cochran, Director of Data Engineering at The Trade Desk, gave a talk about migrating petabytes of data from Cassandra to Aerospike using this modeling approach:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/lA8MXNZ9uY4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;
&lt;h3&gt;
  
  
  Code Sample
&lt;/h3&gt;

&lt;p&gt;The code sample I wrote is located in &lt;a href="https://github.com/aerospike-examples/modeling-user-segmentation"&gt;aerospike-examples/modeling-user-segmentation&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  Loading Data
&lt;/h4&gt;

&lt;p&gt;I started by running &lt;a href="https://github.com/aerospike-examples/modeling-user-segmentation/blob/master/run_workers.sh"&gt;&lt;code&gt;run_workers.sh&lt;/code&gt;&lt;/a&gt;, which launched ten Python &lt;a href="https://github.com/aerospike-examples/modeling-user-segmentation/blob/master/populate_user_profile.py#L119-L147"&gt;&lt;code&gt;populate_user_profile.py&lt;/code&gt;&lt;/a&gt; workers at a time, until a few minutes later I had a profile store containing 500K users, each with 1000 random segment IDs. The value of a segment ID is an integer in the range between 0 and 81999.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ./run_workers.sh 
Generating users 1 to 5001
Generating users 5001 to 10001
Generating users 10001 to 15001
:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Next I ran &lt;a href="https://github.com/aerospike-examples/modeling-user-segmentation/blob/master/update_query_user_profile.py"&gt;&lt;code&gt;update_query_user_profiles.py&lt;/code&gt;&lt;/a&gt;, which has an &lt;code&gt;--interactive&lt;/code&gt; mode to make it easier to see the operations and their results.&lt;/p&gt;
&lt;h4&gt;
  
  
  Inserting and Updating Segments
&lt;/h4&gt;

&lt;p&gt;I upserted a single segment into a user’s segment map, within a transaction that shows the state of that segment ID before and after.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;The original 1000 segments for this user are random, so there’s a chance that this code segment produces an update rather than an insert.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python update_query_user_profiles.py --interactive
Upsert segment 64955 =&amp;gt; [10581] to user u1
Segment value before: []
Number of segments after upsert: 1001
Segment value after: [64955, [10581, {}]]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Similarly, I upserted 8 more segments at once using &lt;code&gt;map_put_items&lt;/code&gt;.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;To fetch the 9 most recent segments out of the user’s 1009, I used &lt;code&gt;map_get_by_value&lt;/code&gt; to search for any map value matching a list that looks like &lt;code&gt;[10581, *]&lt;/code&gt;, with the &lt;a href="https://www.aerospike.com/docs/guide/cdt-ordering.html#wildcard"&gt;‘Wildcard’&lt;/a&gt; glob. See the &lt;a href="https://www.aerospike.com/docs/guide/cdt-ordering.html#comparison"&gt;ordering rules&lt;/a&gt; for more on how Aerospike compares between list values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Updating multiple segments for user u1
{ 537: [10581, {}],
  5484: [10581, {}],
  12735: [10581, {}],
  21894: [10581, {}],
  23223: [10581, {}],
  24124: [10581, {}],
  40680: [10581, {}],
  66659: [10581, {}]}
Show all segments with TTL 10581:
[64955, [10581, {}], 537, [10581, {}], 5484, [10581, {}], 
 12735, [10581, {}], 21894, [10581, {}], 23223, [10581, {}], 
 24124, [10581, {}], 40680, [10581, {}], 66659, [10581, {}]]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Next I updated a segment’s TTL. As I mentioned in my article &lt;a href="https://dev.to/aerospike/operations-on-nested-data-types-in-aerospike-1d5l"&gt;Operations on Nested Data Types in Aerospike&lt;/a&gt;, to operate on the embedded list holding the segment TTL and associated data, I needed to provide the context of how to get to that list element.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
The context is map key 5484 =&amp;gt; 0 index of the list stored as this map key’s value





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Add 5 hours to the TTL of user u1's segment 5484
[5484, [10586, {}]]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Reading a User’s Segment Data
&lt;/h4&gt;

&lt;p&gt;I demonstrated how I can get just the segments that will not be expiring today.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
Only get segments outside the specified segment TTL range.



&lt;p&gt;I used the map &lt;a href="https://www.aerospike.com/docs/guide/cdt-map.html#map-apis"&gt;&lt;code&gt;get_by_value_interval&lt;/code&gt;&lt;/a&gt; operation to find all the segments whose expiration is between &lt;code&gt;[0, NIL]&lt;/code&gt; and &lt;code&gt;[start-of-today, NIL]&lt;/code&gt; and specified that I wanted all elements &lt;em&gt;not in that range&lt;/em&gt;. Notice the &lt;code&gt;True&lt;/code&gt; argument designating the inverse of this range for the Python client’s &lt;a href="https://aerospike-python-client.readthedocs.io/en/latest/aerospike_helpers.operations.html#aerospike_helpers.operations.map_operations.map_get_by_value_range"&gt;&lt;code&gt;map_get_by_value_range()&lt;/code&gt;&lt;/a&gt; helper method.&lt;/p&gt;

&lt;p&gt;To showcase another capability of the map API, I counted how many segments the user had in a range of segment IDs.&lt;/p&gt;

&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
Using the ‘count’ map result type for the map &lt;a href="https://www.aerospike.com/docs/guide/cdt-map.html#map-apis"&gt;get_by_key_interval&lt;/a&gt; operation





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Count how many segments u1 has in the segment ID range 8000-9000
15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In the case of fetching a user’s segments, a simple read operation (&lt;code&gt;get&lt;/code&gt;) may be preferred because it is the fastest. My code sample is meant to show the expressiveness of Aerospike’s native map and list operations.&lt;/p&gt;
&lt;h4&gt;
  
  
  Trimming Stale Segments
&lt;/h4&gt;

&lt;p&gt;There is a complement remove operation for most read operations in the list and map API.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Clean the stale segments for user u1
User u1 had 860 stale segments trimmed, with 149 segments remaining
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As this operation has to inspect whether every segment is inside the specified range, it’s not one to add ahead of every read operation. Instead, it can be called periodically (once an hour, once a day) to perform the cleanup. As of &lt;a href="https://www.aerospike.com/enterprise/download/server/notes.html#4.7.0.2"&gt;version 4.7&lt;/a&gt; (both Community Edition and Enterprise Edition), this operation can be attached to a background scan, to be applied to all records in a namespace or set.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;A row-oriented modeling approach, leveraging the map and list data types, gives Aerospike an advantage in key-value operations over C* implementations, including an advanced C-based one such as ScyllaDB.&lt;/p&gt;

&lt;p&gt;Combined with unique optimizations around NVMe drives, and lacking dependence on lots of DRAM, Aerospike provides much higher performance for user profile stores, with a lot less hardware, whether the scale is measured in gigabytes, terabytes, or petabytes.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on Medium (Aerospike Developer Blog), February 16 2020&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aerospike</category>
      <category>database</category>
      <category>data</category>
    </item>
    <item>
      <title>Aerospike Modeling: IoT Sensors</title>
      <dc:creator>Ronen Botzer</dc:creator>
      <pubDate>Sat, 25 Apr 2020 11:08:28 +0000</pubDate>
      <link>https://forem.com/aerospike/aerospike-modeling-iot-sensors-453a</link>
      <guid>https://forem.com/aerospike/aerospike-modeling-iot-sensors-453a</guid>
      <description>&lt;h4&gt;
  
  
  Rethinking a ScyllaDB Benchmark as an Aerospike Developer
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmpcsjhq4ajimed1a5uuh.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmpcsjhq4ajimed1a5uuh.jpg" alt="Alt Text" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;
Photo by &lt;a href="https://unsplash.com/@danlefeb?utm_content=creditCopyText"&gt;Dan LeFebvre&lt;/a&gt; on &lt;a href="https://unsplash.com/?utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;



&lt;p&gt;In November, the folks over at Scylla announced that an IoT benchmark of theirs achieved a “1 billion rows per second” scan. In this article I’ll show how you would model this use case differently with Aerospike, in a way that achieves higher performance with less hardware.&lt;/p&gt;

&lt;h3&gt;
  
  
  Overview
&lt;/h3&gt;

&lt;p&gt;The Scylla benchmark involved 1 million sensors, logging a temperature measurement once a minute for a year, so each sensor logs 1440 measurements per day, 525,600 per year. Last I checked (2019–12–02) this announcement wasn’t published as a detailed benchmark on the Scylla website, but rather as a (2019–11–05) press release, which sketches out their claims. The lack of details is unfortunate, but I’ll work with that.&lt;/p&gt;

&lt;h4&gt;
  
  
  A Cassandra Modeling Approach
&lt;/h4&gt;

&lt;p&gt;In a Cassandra derivative like ScyllaDB, you would typically have a temperature column containing a single numeric value, with the row representing an instance of measurement. Accordingly, the Scylla benchmark talks about a dataset of 526 billion rows. In this modeling approach, each row is extremely sparse, holding just one data point for a distinct sensor-timestamp pair.&lt;/p&gt;

&lt;p&gt;According to the Scylla press release, to retrieve the entire year’s worth of data, you need to scan the dataset. To get 3 months of sensor information you need to scan the dataset. But the same holds for getting a day’s data for all the sensors, a year’s data for a single sensor, or a single day’s data for an individual sensor — you scan the dataset.&lt;/p&gt;

&lt;p&gt;Cassandra is a column-oriented database, and as such is more suitable for ad-hoc analytics than key-value operations. Like Aerospike, ScyllaDB is written in C (rather than Java), and Scylla have worked to optimize network and disk IO. The Scylla folks are good engineers, and they’ve put in hard work to be better than Apache Cassandra and DataStax. However, when it comes to transactional workloads (rather than analytics), a row-oriented NoSQL database like Aerospike has the upper hand. Modeling this use case in Aerospike will utilize fewer, larger records, with multiple data points in each.&lt;/p&gt;

&lt;h4&gt;
  
  
  The ScyllaDB Cluster Capacity
&lt;/h4&gt;

&lt;p&gt;How big is this dataset anyway? Let’s assume that the row’s primary key is &lt;code&gt;(sensor-id &amp;lt;int (4B)&amp;gt;, timestamp &amp;lt;timestamp (8B)&amp;gt;)&lt;/code&gt; and it has a temperature column with a single &lt;code&gt;int (4B)&lt;/code&gt; value. A rough estimate, with a single copy of the data, would be &lt;code&gt;526 * 10^9 * 16 / 1024^4 = 7.65TiB&lt;/code&gt; . The cluster described by Scylla has 83 x n2.xlarge bare-metal servers from Packet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fm5lpwj11zii8vuswro0m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fm5lpwj11zii8vuswro0m.png" alt="Packet n2.xlarge" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;
Packet n2.xlarge server specs [2019–12–02]



&lt;p&gt;In total, the cluster has a combined capacity of &lt;strong&gt;286TiB&lt;/strong&gt; of NVMe Flash, &lt;strong&gt;31TiB&lt;/strong&gt; of RAM, 2324 physical cores, 3320Gbps of NIC. By my estimate the dataset is &lt;strong&gt;7.65TiB&lt;/strong&gt;. Yes, the disparity between the cluster capacity and the size of this benchmark’s dataset should raise eyebrows.&lt;/p&gt;

&lt;h3&gt;
  
  
  An Aerospike Modeling Approach
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Data Model
&lt;/h4&gt;

&lt;p&gt;For the same use case described in the overview, we will collect each day’s data from one sensor into a single record. Every minute the application will log a temperature reading as a tuple &lt;code&gt;[timestamp, temperature]&lt;/code&gt; and append it to a list of such tuples. At the end of the day this list will have 1440 tuples, and our application will roll over to a new day’s record for the sensor.&lt;/p&gt;

&lt;p&gt;The choice of setting the &lt;a href="https://www.aerospike.com/docs/guide/cdt-list.html#ordered-lists"&gt;list's type&lt;/a&gt; to &lt;code&gt;UNORDERED&lt;/code&gt; versus &lt;code&gt;ORDERED&lt;/code&gt; depends on whether we want to optimize for faster writes or faster in-record read operations, such as getting a range of values from the record. The lists’s type does not limit the operations that can be performed, but it does change their &lt;a href="https://www.aerospike.com/docs/guide/cdt-list-performance.html"&gt;performance characteristics&lt;/a&gt;. Since time increases monotonically, I’m choosing to use an unordered list, for which the append operation has a O(1) performance.&lt;/p&gt;

&lt;h4&gt;
  
  
  Storage Requirements
&lt;/h4&gt;

&lt;p&gt;Let’s consider &lt;a href="https://www.aerospike.com/docs/operations/plan/capacity/data_sizes.html"&gt;how much space&lt;/a&gt; is used for this data structure. Aerospike lists are serialized using &lt;a href="https://msgpack.org/index.html"&gt;MessagePack&lt;/a&gt;, which compacts the data. An 8 byte timestamp and an 8 byte float will end up taking 15 bytes.&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F96xzyg8dtj43j88x4tk3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F96xzyg8dtj43j88x4tk3.png" alt="msgpack basic" width="800" height="251"&gt;&lt;/a&gt;&lt;br&gt;
However, there’s no need to use a full &lt;a href="https://www.unixtimestamp.com/"&gt;Unix epoch timestamp&lt;/a&gt; here. Each record represents a day, so we can instead use midnight as the local epoch and enumerate the minutes since midnight. Midnight would be minute 0, 00:01 would be minute 1, etc. Similarly, the temperature does not need the precision of a float. If the sensor reports temperatures with a precision of one decimal point, we can multiply the value by 10 and store that as a (small) integer. MessagePack can now compact this tuple into 7 bytes.&lt;br&gt;
&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fask95eiy4snl4oblpryh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fask95eiy4snl4oblpryh.png" alt="Alt Text" width="800" height="251"&gt;&lt;/a&gt;&lt;/p&gt;
A reading from 23:22, with a temperature of 62.1 degrees




&lt;h4&gt;
  
  
  Leveraging Compression
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://www.aerospike.com/products/features/compression/"&gt;Compression&lt;/a&gt; is an Aerospike Enterprise Edition feature that can be used to further compact the IoT data we are collecting.&lt;br&gt;
First, let’s consider the storage requirements without compression. At the end of the day we expect a record to take roughly 10K (1440 measurement tuples, plus some overhead). A year’s data for one sensor is ~ 3.5MB across 365 records. The memory cost for a record is &lt;code&gt;365 * 64 /1024 = 22.8KiB&lt;/code&gt;. In my example, I populated an Aerospike database with a year of measurements from one thousand sensors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ft3k7g41s8bnqtx6p32pi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ft3k7g41s8bnqtx6p32pi.png" alt="CE one year uncompressed" width="800" height="185"&gt;&lt;/a&gt;&lt;/p&gt;
A year of uncompressed data from 1000 sensors in Aerospike Community Edition 4.6





&lt;p&gt;The example above is running on a modest 2 core VM with 16GiB storage allocated and 2GiB of RAM.&lt;/p&gt;

&lt;p&gt;Next, I upgraded to Aerospike Enterprise Edition 4.7 and added compression to my namespace configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; {
  &lt;span class="n"&gt;high&lt;/span&gt;-&lt;span class="n"&gt;water&lt;/span&gt;-&lt;span class="n"&gt;disk&lt;/span&gt;-&lt;span class="n"&gt;pct&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
  &lt;span class="n"&gt;high&lt;/span&gt;-&lt;span class="n"&gt;water&lt;/span&gt;-&lt;span class="n"&gt;memory&lt;/span&gt;-&lt;span class="n"&gt;pct&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
  &lt;span class="n"&gt;replication&lt;/span&gt;-&lt;span class="n"&gt;factor&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="n"&gt;memory&lt;/span&gt;-&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="m"&gt;2048&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;
  &lt;span class="n"&gt;storage&lt;/span&gt;-&lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt; {
    &lt;span class="n"&gt;file&lt;/span&gt; /&lt;span class="n"&gt;opt&lt;/span&gt;/&lt;span class="n"&gt;aerospike&lt;/span&gt;/&lt;span class="n"&gt;data&lt;/span&gt;/&lt;span class="n"&gt;test&lt;/span&gt;.&lt;span class="n"&gt;dat&lt;/span&gt;
    &lt;span class="n"&gt;filesize&lt;/span&gt; &lt;span class="m"&gt;16834&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;
    &lt;span class="n"&gt;compression&lt;/span&gt; &lt;span class="n"&gt;zstd&lt;/span&gt;
    &lt;span class="n"&gt;compression&lt;/span&gt;-&lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Compression is applied on a record-by-record basis, so none of the existing data is automatically compressed after restarting the upgraded (EE-4.7) &lt;code&gt;asd&lt;/code&gt;. To change this, I wrote a &lt;a href="https://github.com/aerospike-examples/modeling-iot-sensors/blob/master/ttl.lua"&gt;simple UDF&lt;/a&gt; that just touches a record, and applied it with a background scan to all the records in my namespace. This caused the records to be updated with a new TTL value, and stored in a compressed state, as defined by the namespace configuration.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;Aerospike&lt;/span&gt; &lt;span class="n"&gt;Query&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;
&lt;span class="k"&gt;Version&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;C&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt; &lt;span class="k"&gt;Version&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;
&lt;span class="n"&gt;Copyright&lt;/span&gt; &lt;span class="mi"&gt;2012&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2019&lt;/span&gt; &lt;span class="n"&gt;Aerospike&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="k"&gt;All&lt;/span&gt; &lt;span class="n"&gt;rights&lt;/span&gt; &lt;span class="n"&gt;reserved&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;aql&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;register&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="s1"&gt;'./ttl.lua'&lt;/span&gt;
&lt;span class="n"&gt;OK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="n"&gt;added&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;aql&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;show&lt;/span&gt; &lt;span class="n"&gt;modules&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;-----------+--------------------------------------------+-------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;                                       &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;type&lt;/span&gt;  &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;-----------+--------------------------------------------+-------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"ttl.lua"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"9614a68daf5353109372d96517d3d4f863e64ec1"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"LUA"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;-----------+--------------------------------------------+-------+&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;127&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;002&lt;/span&gt; &lt;span class="n"&gt;secs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;aql&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;execute&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;touchttl&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sensor_data&lt;/span&gt;
&lt;span class="n"&gt;OK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Scan&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;13120129825472024600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;aql&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;show&lt;/span&gt; &lt;span class="n"&gt;scans&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;----------------+--------+------------+--------------+-------------+--------------+----------------+--------------------+------------------------+--------------+---------------+----------+------------------+--------+----------------+--------------------+------------+----------+--------------+--------+-----------------+----------------+-----------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;threads&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;ns&lt;/span&gt;     &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;udf&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;udf&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;recs&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;udf&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;recs&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;succeeded&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;recs&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;filtered&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;bins&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;trid&lt;/span&gt;                   &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;           &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;         &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;recs&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;throttled&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;recs&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;filtered&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;     &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;time&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;rps&lt;/span&gt;    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;since&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;                  &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;----------------+--------+------------+--------------+-------------+--------------+----------------+--------------------+------------------------+--------------+---------------+----------+------------------+--------+----------------+--------------------+------------+----------+--------------+--------+-----------------+----------------+-----------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"0"&lt;/span&gt;            &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"test"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"0"&lt;/span&gt;        &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"ttl"&lt;/span&gt;        &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"0"&lt;/span&gt;         &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"touchttl"&lt;/span&gt;   &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"365000"&lt;/span&gt;       &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"0"&lt;/span&gt;                &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"13120129825472024600"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"100.00"&lt;/span&gt;     &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"sensor_data"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"0"&lt;/span&gt;      &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"background-udf"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"scan"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"365000"&lt;/span&gt;       &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"0"&lt;/span&gt;                &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"done(ok)"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"73279"&lt;/span&gt;  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"30"&lt;/span&gt;         &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"5000"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"1814"&lt;/span&gt;          &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"10000"&lt;/span&gt;        &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;"192.168.141.2:54138"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;----------------+--------+------------+--------------+-------------+--------------+----------------+--------------------+------------------------+--------------+---------------+----------+------------------+--------+----------------+--------------------+------------+----------+--------------+--------+-----------------+----------------+-----------------------+&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;127&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;001&lt;/span&gt; &lt;span class="n"&gt;secs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Looking at the cluster with &lt;a href="https://www.aerospike.com/docs/tools/asadm/index.html"&gt;asadm&lt;/a&gt;, I saw that the dataset compressed to 32.8% of its original size (&lt;a href="https://www.aerospike.com/docs/reference/metrics/index.html#device_compression_ratio"&gt;&lt;code&gt;device_compression_ratio 0.328&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjljq34az3s2nwpk04usa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjljq34az3s2nwpk04usa.png" alt="EE 4.7 with compression" width="800" height="277"&gt;&lt;/a&gt;&lt;/p&gt;
Same dataset with a 0.328 compression ratio thanks to Zstandard level 1





&lt;p&gt;Compression doesn’t just save on space, it usually also lowers latency, as it’s faster to read a 3KiB object than a 10KiB one, even if the server has to decompress it (zstd has a very high decompression speed compared to the read IO speed).&lt;/p&gt;
&lt;h4&gt;
  
  
  Code Sample
&lt;/h4&gt;

&lt;p&gt;The code I used is located in the repo &lt;a href="https://github.com/aerospike-examples/modeling-iot-sensors"&gt;aerospike-examples/modeling-iot-sensors&lt;/a&gt;. I started by running &lt;a href="https://github.com/aerospike-examples/modeling-iot-sensors/blob/master/run_sensors.sh"&gt;&lt;code&gt;run_sensors.sh&lt;/code&gt;&lt;/a&gt;, which launched ten Python &lt;a href="https://github.com/aerospike-examples/modeling-iot-sensors/blob/master/populate_sensor_data.py"&gt;&lt;code&gt;populate_sensor_data.py&lt;/code&gt;&lt;/a&gt; workers at a time, until a few minutes later I had a year of data from a thousand sensors. The sensor data is based on a year of (hourly) temperature data points I had downloaded from NOAA.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;query_iot_data.py&lt;/code&gt; script has an &lt;code&gt;--interactive mode&lt;/code&gt;, which makes it easier to see what it’s doing. Even on my underpowered VM it runs fast over the example data.&lt;/p&gt;

&lt;p&gt;To fetch three hours of data from one sensor I used the list &lt;a href="https://www.aerospike.com/docs/guide/cdt-list.html#list-api"&gt;&lt;code&gt;get_by_value_interval&lt;/code&gt;&lt;/a&gt; operation to find all the values between &lt;code&gt;[480, NIL]&lt;/code&gt; and &lt;code&gt;[660, NIL]&lt;/code&gt;. See the &lt;a href="https://www.aerospike.com/docs/guide/cdt-ordering.html#comparison"&gt;ordering rules&lt;/a&gt; for more on how Aerospike compares between values in the list.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
Three hours of a single sensor’s data for 2018–12–31





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;520&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;481&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;521&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;482&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;521&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;657&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;614&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;658&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;614&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;659&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;614&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Getting a day’s data for any sensor is a single read. The number of records doesn’t matter; the latency of this operation will be the same, regardless of which day we fetch, or how many records are in the dataset.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
Gets a single sensor’s data for 2018–04–02



&lt;p&gt;To get a year of data for an individual sensor we do not need to scan the entire dataset. The way we’ve modeled our data allows us to build an array of 365 keys, then use it to make a single &lt;a href="https://www.aerospike.com/docs/guide/batch.html"&gt;batch-read&lt;/a&gt; operation. If the batch was bigger it might have been more optimal to iterate through a few sub batches. For information on tuning batch operations see the knowledge base article &lt;a href="https://discuss.aerospike.com/t/faq-batch-index-tuning-parameters/4842"&gt;FAQ — batch index tuning parameters&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Getting the data from all the sensors on a specific day is just as simple. If the batch was bigger than &lt;a href="https://www.aerospike.com/docs/reference/configuration/#batch-max-requests"&gt;&lt;code&gt;batch-max-requests&lt;/code&gt;&lt;/a&gt; we would need to either tune that configuration parameter or iterate through several smaller batches.&lt;/p&gt;

&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
A day of data from all the sensors, no scan necessary



&lt;p&gt;Finally, we can scan the entire dataset and filter out the records we want using a &lt;a href="https://www.aerospike.com/docs/guide/predicate.html"&gt;predicate filter&lt;/a&gt;. In the example below, I am doing a modulo operation on the record’s digest. This operation executes on the primary index metadata (in memory), without needing to read the records, so it’s very fast. A predicate filter can be as complex as you need it to be, with the conditional logic applied on the server side.&lt;/p&gt;

&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
Scans the entire dataset with a predicate filter, returning about 1000 records



&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Aerospike is more suited for the type of IoT timeseries data collection and retrieval described in the original benchmark than ScyllaDB. You can intuitively see how with a row-oriented modeling approach, leveraging Aerospike’s strengths, you would get better performance than a C* database, using far less hardware.&lt;/p&gt;

&lt;p&gt;Aerospike does not yet provide full timeseries database functionality such as being able to apply aggregate functions over a range of data. But it is a high performance, hyper scalable database, and provides the functionality needed for time slicing datasets.&lt;/p&gt;

&lt;p&gt;An application can compute its own aggregates over the data retrieved quickly and efficiently from Aerospike. Similarly, you can do such computations in Spark, using &lt;a href="https://www.aerospike.com/docs/connectors/enterprise/spark/index.html"&gt;Aerospike Connect for Spark&lt;/a&gt; to fetch data from Aerospike into Spark. The connector library knows when to use batch reads, and when to scan (with or without a predicate filter), using a similar pattern to my code sample. This approach is already being used in production by enterprise and community users.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on Medium (Aerospike Developer Blog), December 3 2019&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aerospike</category>
      <category>database</category>
      <category>data</category>
    </item>
    <item>
      <title>Operations on Nested Data Types in Aerospike</title>
      <dc:creator>Ronen Botzer</dc:creator>
      <pubDate>Sat, 25 Apr 2020 05:07:52 +0000</pubDate>
      <link>https://forem.com/aerospike/operations-on-nested-data-types-in-aerospike-1d5l</link>
      <guid>https://forem.com/aerospike/operations-on-nested-data-types-in-aerospike-1d5l</guid>
      <description>&lt;h4&gt;
  
  
  A document store modeling approach
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frilr9orhz59e82xdm9xc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frilr9orhz59e82xdm9xc.jpg" alt="Alt Text" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;
Photo by &lt;a href="https://unsplash.com/@yingchih_hao?utm_content=creditCopyText"&gt;Yingchih&lt;/a&gt; on &lt;a href="https://unsplash.com/?utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;



&lt;p&gt;&lt;a href="https://www.aerospike.com/blog/aerospike-4-6-enhancing-security-developer-features/"&gt;Aerospike version 4.6&lt;/a&gt; (released in August 2019) added the ability to apply &lt;a href="https://www.aerospike.com/docs/guide/cdt-list.html"&gt;list&lt;/a&gt; and &lt;a href="https://www.aerospike.com/docs/guide/cdt-map.html"&gt;map&lt;/a&gt; operations to elements nested at an arbitrary depth. In this post we'll see how this works. I'll start with an overview, so if you're familiar with Aerospike you can skip the following section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Overview
&lt;/h3&gt;

&lt;p&gt;Aerospike is a high performance, row-oriented, distributed database. Objects in Aerospike are called &lt;a href="https://www.aerospike.com/docs/architecture/data-model.html#records"&gt;records&lt;/a&gt;. They are similar to rows in relational databases. Records are uniquely identified by the 3-tuple &lt;code&gt;(namespace, set, user-key)&lt;/code&gt;. A &lt;a href="https://www.aerospike.com/docs/architecture/data-model.html"&gt;namespace&lt;/a&gt; combines the database and tablespace concepts of a relational database. The &lt;a href="https://www.aerospike.com/docs/architecture/data-model.html#sets"&gt;set&lt;/a&gt; in Aerospike is similar to a schema-less table. The &lt;em&gt;user-key&lt;/em&gt; is simply the unique identifier for a record in this set from the perspective of the application. This is similar to how a primary key uniquely identifies a single row of a table in a relational database. The entire record is stored contiguously in the storage medium defined by the namespace (on SSD, in persistent memory, or in DRAM).&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvlw4drcwjzng41ds9lmk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvlw4drcwjzng41ds9lmk.png" alt="Alt Text" width="800" height="132"&gt;&lt;/a&gt;&lt;br&gt;
An Aerospike record keeps its data in one or more &lt;em&gt;bins&lt;/em&gt;, which are similar to the columns of a row in a relational database, just without a schema. Each bin holds a value of a supported &lt;a href="https://www.aerospike.com/docs/guide/data-types.html"&gt;data type&lt;/a&gt;: integer, double, string, bytes, list, map, geospatial. Each data type has an API of atomic, server-side operations. For example, the &lt;a href="https://www.aerospike.com/docs/guide/data-types.html#integer"&gt;integer&lt;/a&gt; data type has an increment operation, which can be used to implement counters in the record.&lt;/p&gt;

&lt;p&gt;The list and map data types are particularly interesting and flexible. As storage units they can embed other data types inside them, including nesting other lists and maps. Both have extensive APIs.&lt;/p&gt;

&lt;p&gt;The Aerospike database does single record transactions. Multiple operations, against a single record, can be executed efficiently under a record lock, atomically and in isolation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tracking High Scores
&lt;/h3&gt;

&lt;p&gt;In this example, we’ll track the high scores for classic video games using a nested data structure &lt;code&gt;{ player: [score, {attribute map}] }&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Scores can be added individually or in bulk using &lt;a href="https://aerospike-python-client.readthedocs.io/en/latest/aerospike_helpers.operations.html#aerospike_helpers.operations.map_operations.map_put_items"&gt;&lt;code&gt;map_put_items()&lt;/code&gt;&lt;/a&gt;, the Python client’s implementation of the Aerospike map API’s &lt;a href="https://www.aerospike.com/docs/guide/cdt-map-ops.html"&gt;&lt;code&gt;add_items()&lt;/code&gt;&lt;/a&gt; operation.&lt;/p&gt;

&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
Each video game is tracked in a different record



&lt;p&gt;At this point the scores can be retrieved by rank. The rank is established based on the &lt;a href="https://www.aerospike.com/docs/guide/cdt-ordering.html"&gt;ordering rules&lt;/a&gt; for the values of this map, which in this case are all lists with the tuple structure &lt;code&gt;[score, {attribute map}]&lt;/code&gt;.&lt;/p&gt;

&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
Returns all the map elements by ascending rank. Due to Aerospike’s ordering rules, the list values of this map get ordered primarily by the value of their first element (the score)





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ETC&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;9200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2018-05-01 13:47:26&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1525182446891&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CPU&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;9800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2017-12-05 01:01:11&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1512435671573&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CFO&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;17400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2017-11-19 15:22:38&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1511104958197&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;EIR&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;18400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2018-03-18 18:44:12&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1521398652483&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SOS&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;24700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2018-01-05 01:01:11&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1515114071923&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ACE&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;34500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1979-04-01 09:46:28&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;291807988156&lt;/span&gt;&lt;span class="p"&gt;}]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Before Aerospike version 4.6 there was no way to apply map operations on the attribute map nested inside the list values of the &lt;code&gt;scores&lt;/code&gt; map. The &lt;a href="https://www.aerospike.com/docs/guide/data-types.html#complex-data-types-cdts-"&gt;Complex Data Types&lt;/a&gt; (CDT) operations were limited to the top level elements of the list or map in question. Let’s assume that the attribute map optionally contains awards won by the players.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
Grants the 🦄 award once and only once



&lt;p&gt;In earlier versions of Aerospike, we would need to read the list value from the server to the application, add the awards map to the attribute map, then write the modified list back to the server. Leveraging the feature added in version 4.6, it can now be done atomically. I created a &lt;a href="https://www.aerospike.com/docs/guide/cdt-context.html"&gt;context&lt;/a&gt; that identifies the path to the attribute map, then applied a &lt;code&gt;map_put&lt;/code&gt; operation at that spot. The map policy &lt;code&gt;MAP_WRITE_FLAGS_CREATE_ONLY&lt;/code&gt; ensures this award is granted once. The &lt;code&gt;MAP_WRITE_FLAGS_NO_FAIL&lt;/code&gt; policy makes the operation behave in a tolerant way if the 🦄 award was already in place. The transaction continues to the next operation (if there is one) and the client side doesn’t need to handle an exception.&lt;/p&gt;

&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
Give the player with the top score a 🏆award





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ACE&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="mi"&gt;34500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;awards&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;🏆&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
             &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1979-04-01 09:46:28&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;291807988156&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CFO&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="mi"&gt;17400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;awards&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;🦄&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
             &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2017-11-19 15:22:38&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1511104958197&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CPU&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;9800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2017-12-05 01:01:11&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1512435671573&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;EIR&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="mi"&gt;18400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2018-03-18 18:44:12&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1521398652483&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ETC&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;9200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2018-05-01 13:47:26&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1525182446891&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SOS&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="mi"&gt;24700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2018-01-05 01:01:11&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1515114071923&lt;/span&gt;&lt;span class="p"&gt;}]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In the code section above, the context is enhanced to a further depth so that a 🏆 award is initialized if it doesn’t exist, then incremented. You need to have a path leading to an element, so simply incrementing without initializing it would risk a failure. Notice that the context path doesn’t have to be a physical set of direction changes. Here the 🏆 is given to the element with the highest rank (-1).&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
Hand out another top score 🏆award, then display the top three scores





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;EIR&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;18400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2018-03-18 18:44:12&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1521398652483&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SOS&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;24700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2018-01-05 01:01:11&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1515114071923&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ACE&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="mi"&gt;34500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;awards&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;🏆&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1979-04-01 09:46:28&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;291807988156&lt;/span&gt;&lt;span class="p"&gt;}]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/aerospike-examples/aerospike-modeling/blob/master/nested_cdts.py"&gt;This code&lt;/a&gt; lives in the &lt;a href="https://github.com/aerospike-examples/aerospike-modeling"&gt;aerospike-examples/aerospike-modeling&lt;/a&gt; repo on GitHub.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on Medium (Aerospike Developer Blog), November 2 2019&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aerospike</category>
      <category>database</category>
      <category>data</category>
    </item>
  </channel>
</rss>
