You’re right about the weaknesses of Versionstamps (really autoincrementing keys in general). I should probably make that more clear in the docs.
Another missing feature of FDB Versionstamps: you can’t set the versionstamp on both the key and value at the same time. This makes maintaining an index key while supporting GetMappedRange awkward, but still doable by constructing a more complex mapper tuple:
defp mapper(tenant, idx_len) do
offset_fun = fn offset ->
[
"{V[#{offset}]}", # prefix
"{V[#{offset + 1}]}", # source
"{V[#{offset + 2}]}", # namespace
# pk: get it from the index_key because the versionstamp lives there, not in the value
"{K[#{offset + 5 + idx_len}]}",
"{...}" # rest
]
end
If you allow me to speculate a bit, I suspect this stems from the “set versionstamp” ops being atomic ops. It seems unnatural to allow keys related to atomic ops on the RYW tx to be readable from the cache (for example, an “add” operation on a key does not have a meaningful RYW cached value). In the case of set_versionstamp_key, I suppose it could be feasible and useful, but your transaction logic would still have to understand that portions of that cached key are not in their final form. And if your transaction needs to code around this defensively anyway, I figure you’re better off doing your own in-transaction cache.
Your point still stands about versionstamps making transaction composition harder - they sure do!
Guilty as charged, but the API I landed on is acceptable, IMO. I may yet propose a postprocess callback in Ecto to allow the adapter to participate in the struct creation, if time allows.
Confession: I’m finding myself having to deviate from the standard Ecto API more often as the implementation increases in scope. (all the async/await functions, transaction vs transact vs transactional). FDB is unique in a lot of ways, though, so I don’t presume that a lot of these ideas are generalizable to other DB clients - yet
.
Agreed, this is the most / compelling use case for Versionstamps
. I’ll update the docs to reflect this, too.
I’d like to explore this area a little bit myself. My undeveloped idea is to use the modules from riak_dt as Ecto.Type fields and implement the Ecto Adapter to perform merging upon an update. That is, to allow 2 FDB clusters to evolve separately and then have them merge at a later time.