Choosing a data model: migrate from Aerospike to KiviDB

Name each key <namespace>:<set>:<user key>, for example app:users:u42 or app:orders:1001. The loader below does this. Keep the user key in the name; it is…

Key names

Name each key <namespace>:<set>:<user key>, for example app:users:u42 or app:orders:1001. The loader below does this. Keep the user key in the name; it is how the application finds the record.

Integer and string keys become the same text: Aerospike keeps the integer 7 and the string "7" as two records (the key type is part of the digest), and both would be named app:orders:7. If one set uses both, add a marker, for example with --prefix, or change the key format in the loader.

Hash or JSON

Hash (--mode hash)JSON document (--mode json)
One record isHSET app:users:u42 name Ana age 31 ...JSON.SET app:users:u42 $ '{"name":"Ana","age":31,...}'
Field typesStrings: numbers are stored as their textNumbers, strings, booleans, arrays, objects, null
List and map binsStored as JSON text in one field; changed by rewriting the fieldReal arrays and objects; changed in place by path
IncrementHINCRBY, HINCRBYFLOATJSON.NUMINCRBY
Read some binsHMGETJSON.GET key $.a $.b
SearchFT.CREATE ... ON HASHFT.CREATE ... ON JSON, including array elements
MemorySmallerLarger

Use hashes when records are flat (scalars only). Use JSON when you use list or map bins and their operations, or nested values.

Bin types

Aerospike binHash fieldJSON
IntegerDecimal text: 31Number
FloatShortest text that reads back as the same double: 18.957Number
StringAs isString
Bytes (blob)The raw bytes{"$b64": "..."} (JSON has no bytes)
Boolean1 or 0true or false
ListJSON textArray
MapJSON text; integer and bytes map keys become stringsObject; same
GeoJSONThe GeoJSON text; points also go to a geo set with --geoObject; same
HyperLogLogNot copiedNot copied

Map ordering (KEY_ORDERED, KEY_VALUE_ORDERED) is not kept as a property of the value: JSON objects keep the order the keys were written in, and sorting is done by the application or a search index.

TTL

Aerospike reports the seconds a record has left; KiviDB keys have an expiry time. The loader converts one to the other (EXPIREAT at the time of the copy plus the remaining TTL), so a slow copy does not lengthen a record's life. Records that never expire get no expiry.

The rule on update differs:

AerospikeKiviDB
Write without a TTLResets the TTL to the namespace's default-ttl (or the client's default)HSET, HINCRBY, JSON.SET on a path and the other updates keep the TTL; SET, and DEL then write, remove it
Keep the TTLPass TTL -2 (TTL_DONT_UPDATE)Nothing to do
Never expireTTL -1No EXPIRE, or PERSIST
Reset the TTLtouch()EXPIRE key seconds

Code that relied on every write extending the TTL has to call EXPIRE after the write.

Generation checks

Aerospike's check-and-set (GEN_EQ / EXPECT_GEN_EQUAL) has no direct equivalent. Two ways to do the same:

  • WATCH, read, MULTI, write, EXEC. EXEC fails if the key changed after WATCH. Use this when the read and the write happen in one function.
  • A version field. Keep a field such as _v in the record, and increase it on every write. To write only if the record still has the version the caller read earlier (for example, in a previous HTTP request), compare it under WATCH and write in MULTI. The loader's --gen-field _v copies the current generation into that field, so existing versions carry over.

Both are shown in Generation checks in code.