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 is | HSET app:users:u42 name Ana age 31 ... | JSON.SET app:users:u42 $ '{"name":"Ana","age":31,...}' |
| Field types | Strings: numbers are stored as their text | Numbers, strings, booleans, arrays, objects, null |
| List and map bins | Stored as JSON text in one field; changed by rewriting the field | Real arrays and objects; changed in place by path |
| Increment | HINCRBY, HINCRBYFLOAT | JSON.NUMINCRBY |
| Read some bins | HMGET | JSON.GET key $.a $.b |
| Search | FT.CREATE ... ON HASH | FT.CREATE ... ON JSON, including array elements |
| Memory | Smaller | Larger |
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 bin | Hash field | JSON |
|---|---|---|
| Integer | Decimal text: 31 | Number |
| Float | Shortest text that reads back as the same double: 18.957 | Number |
| String | As is | String |
| Bytes (blob) | The raw bytes | {"$b64": "..."} (JSON has no bytes) |
| Boolean | 1 or 0 | true or false |
| List | JSON text | Array |
| Map | JSON text; integer and bytes map keys become strings | Object; same |
| GeoJSON | The GeoJSON text; points also go to a geo set with --geo | Object; same |
| HyperLogLog | Not copied | Not 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:
| Aerospike | KiviDB | |
|---|---|---|
| Write without a TTL | Resets 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 TTL | Pass TTL -2 (TTL_DONT_UPDATE) | Nothing to do |
| Never expire | TTL -1 | No EXPIRE, or PERSIST |
| Reset the TTL | touch() | 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.EXECfails if the key changed afterWATCH. Use this when the read and the write happen in one function.- A version field. Keep a field such as
_vin 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 underWATCHand write inMULTI. The loader's--gen-field _vcopies the current generation into that field, so existing versions carry over.
Both are shown in Generation checks in code.