datomic

2026-06-04T10:19:32.112489Z

Just exploring and learning the AVET index. I currently have uuid7s for every entity id (:note/id, :project/id...) across all tenants. Entities are partitioned per tenant. But since partitioning doesn’t directly affect the AVET index, are there any common patterns to optimise for AVET locality? Do you use tuples with the first element as the tenant id/ref? Or for my uuid case, encode the tenant partition info in the uuid?

favila 2026-06-04T21:09:52.765479Z

Both these work

👍 1
favila 2026-06-04T21:10:30.078579Z

fundamentally you want the left side of the value of V to cluster around your desired locality

favila 2026-06-04T21:11:40.654929Z

do remember that you can't actually leverage locality fully unless you are also making sure peers query with similar locality.

favila 2026-06-04T21:11:59.730849Z

for the example of a tenant, your peers need tenant affinity

2026-06-05T06:53:55.473009Z

Yea, that's essential. I’m thinking of routing my request based on the same or derived hashing function as the one I use for forcing partitions in datomic.

2026-06-05T10:34:24.585629Z

Same hashing function was wrong obviously. I guess it can be as simple as a tenant id mod number of peers. Frequent auto scaling would destroy a lot of the benefits though. Region based clustering of tenant id routing is also something I might have to think about. But those are at scale and independent of the schema which I'd like to remain as stable as possible.

enn 2026-06-04T20:37:06.777169Z

I'm trying to understand as-of. I see that I can give it basically any numeric value (including negative values) and I will get back a db for which d/as-of-t faithfully returns whatever I passed. However, only for some values does d/t->tx actually return a transaction (it always returns a tx ID, but only some of those are "real" in that I can pull a :db/txInstant for them). If I pass a value that does not map to a real transaction, what are the semantics of the resulting database? In a non-filtered db, (->> db d/basis-t d/t->tx (d/pull db [:db/txInstant])) will reliably give me a Date representing the latest transaction in the database, but with an as-of db, it won't if the passed in as-of value does not map to a real transaction. For such a database, is there some other way to understand when its most recent transaction occurred? Is it even valid or meaningful to use such a database?

enn 2026-06-04T20:38:21.961489Z

I see that there is similar behavior if you pass as-of a date that does not map exactly to a specific transaction's :db/txInstant.

enn 2026-06-04T20:43:45.305509Z

I sort of expected that maybe as-of-t would return whatever value was passed to as-of but basis-t would return a value corresponding to the actual latest tx in the database. however, it seems like basis-t ignores the filtering completely and always returns the basis-t of the unfiltered db.

favila 2026-06-04T21:04:53.145549Z

as-of is a filter: you are filtering out datoms newer than the as-of-t

favila 2026-06-04T21:06:30.954219Z

if you want actual transaction ids (or corresponding Ts), range over :db/txInstant. This is what d/as-of with an instant is doing under the hood.

favila 2026-06-04T21:07:25.019289Z

If you want to know the details of what T actually corresponds to: https://favila.github.io/2024-05-16/datomic-entity-id-structure/

favila 2026-06-04T21:13:41.854649Z

For such a database, is there some other way to understand when its most recent transaction occurred?The basis-t is always the most-recent transaction the database holds.

enn 2026-06-04T21:15:43.735979Z

That doesn't seem to be true in my testing, or else I'm not understanding what it means.

user> (d/basis-t db)
66
user> (def adb (d/as-of db 1))
#'user/adb
user> (d/basis-t adb)
66
user> (d/as-of-t adb)
1

enn 2026-06-04T21:16:18.304049Z

Shouldn't anything after t = 1 be filtered out?

favila 2026-06-04T21:16:29.891139Z

filtered out != database basis/database "holding"

enn 2026-06-04T21:16:35.882469Z

I see

favila 2026-06-04T21:17:03.810299Z

e.g., you could change the as-of and make anything up to basis-t visible

favila 2026-06-04T21:17:32.588299Z

if it were not a filter, but actually restricting what was in the database value itself, then you couldn't do that.

favila 2026-06-04T21:17:48.365249Z

changing as-of would be the same as changing basis

enn 2026-06-04T21:24:41.852879Z

So something like (:e (first (d/rseek-datoms adb :aevt :db/txInstant))) is the way to get the most-recent TX?

favila 2026-06-04T21:25:16.508339Z

yeah, with a bit more caution to make sure you stay in :a

favila 2026-06-04T21:27:03.437829Z

if you specifically want the highest tx < as-of-t, you can add (d/t->tx (d/as-of-t db)) as the starting e. It might be a bit faster, not sure

enn 2026-06-04T21:28:35.642889Z

Yeah, but that doesn't necessarily correlate to a real TX, right? that's my core issue. The background here is I want to give the user (this is basically a Datomic admin UI/browser) some kind of relevant wall time, regardless of what they typed into the as-of field

favila 2026-06-04T21:30:03.389039Z

Whatever you get from that rseek-datoms will be a real transaction

enn 2026-06-04T21:31:13.688699Z

Right. Oh, I see, you mean as the starting point of the rseek-datoms, not an alternative. Sorry, I misunderstood.

favila 2026-06-04T21:31:54.005009Z

specifically (first (d/rseek-datoms adb :aevt :db/txInstant (d/as-of-t adb))) . If returned datom has an :a of :db/txInstant, then that is the highest-visible real TX for given as-of-t. (If it doesn't then no TXs are visible.)

favila 2026-06-04T21:32:18.811109Z

(first (d/rseek-datoms adb :aevt :db/txInstant)) should always produce the same result, I'm just not sure if it does it as quickly