datomic

2025-10-08T13:14:14.935459Z

Morning, folks, we are using the ensure-transactor command to set up our local dev environments. We are getting a timeout for this command and I am trying to debug why. I noticed that the datomic peer tutorial doesn’t seem to reference this command anymore: https://docs.datomic.com/peer-tutorial/transactor.html. I also believe this command does not allow us to set a timeout value. What’s your suggestion on how to debug this?

✅ 1
2025-10-08T13:25:48.824459Z

We solved it, it was an issue with our Docker DynamoDB system.

cch1 2025-10-08T19:38:50.381759Z

The ion client and the cloud client behave differently when pulling. Specifically, pulling when the entity exists but the pull expression does not intersect with the entity's attributes returns nil with the ion client and and empty map with the cloud client. This is really dangerous behavior if one is, per idiomatic Clojure, using nil-versus-anything-but-false as a condition. Is this a known bug?

favila 2025-10-09T15:11:32.550309Z

I am triggered whenever someone says "when the entity exists"

favila 2025-10-09T15:12:31.913659Z

entities only have identity, not existence, and join facts about themselves

favila 2025-10-09T15:13:01.471259Z

testing for entity "existence" should always be testing for an assertion of a specific attribute you use to signal existence

favila 2025-10-09T15:14:43.835109Z

I'm not saying that the pull return behavior is correct, but I am saying that pull (indeed no datomic function) can never provide an "entity does not exist" signal because the information model doesn't contain that notion.

favila 2025-10-09T15:17:49.781889Z

With one exception: entity lookup refs and idents inherently test for a matching datom, and may not find one; so these could be transmitted as a "could not find a matching entity" (different from "entity not existing").

cch1 2025-10-09T18:07:43.361739Z

I'm happy to restate the problem to avoid that imprecise wording: The ion client and the cloud client behave differently when pulling. Specifically, pulling when the provided lookup ref refers to an entity (=> the provided AV yields an E), but the pull expression does not intersect with the entity's attributes returns nil with the ion client and and empty map with the cloud client. This is really dangerous behavior if one is, per idiomatic Clojure, using nil-versus-anything-but-false as a condition. Is this a known bug?

cch1 2025-10-09T18:10:28.171729Z

So while I agree that proving an entity doesn't exist is difficult, that's not the situation here. Given that an entity does exist (which is easy to prove), the behavior of pull differs across the clients. That is the problem.

favila 2025-10-09T18:11:37.316719Z

> This is really dangerous behavior if one is, per idiomatic Clojure, using nil-versus-anything-but-false as a condition. My contention is this idiom should never be used for the result of pull. What does "nil" mean?

cch1 2025-10-09T18:12:07.901069Z

That the lookup ref found no entity.

favila 2025-10-09T18:13:03.505159Z

and only that? Not "the pull expression does not intersect with the entity's attributes"?

cch1 2025-10-09T18:13:12.913689Z

That is a separate concern.

cch1 2025-10-09T18:13:39.698649Z

But, even if you can't see daylight between those two concerns, there should at least be consistency across the clients.

favila 2025-10-09T18:15:12.620679Z

Not disagreeing; but I am noting that d/pull doesn't guarantee when it returns nil; if anything, the bug is that it returns nil

favila 2025-10-09T18:15:25.094449Z

"Returns a map."

cch1 2025-10-09T18:16:03.340049Z

I can accept that, and if there had been consistency across the clients in returning an empty map, I would not have had a production bug.

cch1 2025-10-09T18:16:31.307899Z

IOW, IMO that is a reasonable model.

cch1 2025-10-09T18:17:08.183879Z

(I'm curious what peer does, but I don't have access to a peer installation)

favila 2025-10-09T18:17:35.308239Z

> This is really dangerous behavior if one is, per idiomatic Clojure, using nil-versus-anything-but-false as a condition. This is the part I'm harping on: one should never use "nil" in this situation as a signal of anything

favila 2025-10-09T18:18:09.518459Z

the reason is multilayered; usually it's a misguided notion of "entity existence"

favila 2025-10-09T18:19:30.634699Z

peer version (also d/entity) sometimes returns nil; docstring doesn't say when it returns nil

cch1 2025-10-09T18:21:13.360339Z

Perhaps at the database level the concept of entity existence is misguided, but at the domain level it is perfect valid and idiomatic. For a US State to exist in my domain, it must have a USPS two-letter abbreviation. Thus it's reasonable to ask the database, using a lookup ref, "does there exist a state with this USPS abbreviation". BUT THIS IS ALL A SECONDARY CONCERN!! Can we please have consistency across ion, db-local and cloud clients?

cch1 2025-10-09T18:22:21.464549Z

(or, if not, loud warnings)

favila 2025-10-09T18:26:52.638299Z

devil's advocate here: why is the inconsistency important if nil puns to empty map?

favila 2025-10-09T18:28:09.765849Z

if it was for the sake of a falsy-testing idiom, we've already said why that shouldn't be done even if the return values were consistent.

favila 2025-10-09T18:35:08.831949Z

FWIW, If I were writing the spec for d/pull and d/entity, I'd have this behavior:

(d/pull db [:some-attr] eid-without-assertions) => {}
(d/pull db [:db/id :some-attr] eid-without-assertions) => {:db/id eid}
(d/pull db [*] eid) => {:db/id eid-without-assertions}

(d/pull db [:some-attr] not-found-lookup-ref-or-ident) => nil
(d/pull db [:db/id :some-attr] not-found-lookup-ref-or-ident) => nil
(d/pull db [*] not-found-lookup-ref-or-ident) => nil
However that is not the behavior guaranteed by the docs

favila 2025-10-09T18:36:52.374929Z

and it still has the "gotcha" of "entity existence". I fully expect someone to go (d/pull db [:something] eid-they-just-retractEntitied) and expect nil

cch1 2025-10-09T18:36:56.276819Z

FWIW, that ^ is exactly the behavior I would like to see. And, with the inconsistent (across clients) and undocumented behavior it is very easy to let nil represent an unresolvable lookup ref (domain level: the entity does not exist).

favila 2025-10-09T18:37:50.123989Z

is the case where you saw "nil" the result of an unresolved lookup? Your OP didn't mention that, just said "when the entity exists but the pull expression does not intersect with the entity's attributes"

cch1 2025-10-09T18:39:18.285719Z

I saw nil where the lookup ref resolved (==> domain entity exists) but the pull attrs did not intersect. But only in the ion client.

cch1 2025-10-09T18:40:42.140599Z

My test was for a map and it failed unexpectedly.

favila 2025-10-09T18:44:25.605209Z

getting specific here, is this what you see?

;; :server-type :ion or :cloud
(d/pull db [:identifier] [:identifier "foo"]) => {:identifier "foo"}

;; :ion
(d/pull db [:unasserted] [:identifier "foo"]) => nil

;; :cloud
(d/pull db [:unasserted] [:identifier "foo"]) => {}

cch1 2025-10-09T18:44:47.082819Z

Yes.

favila 2025-10-09T18:46:16.056829Z

are there any other behaviors you don't expect?

cch1 2025-10-09T18:46:44.882579Z

IIRC, yes. Defaults come to mind.

favila 2025-10-09T18:46:55.782739Z

I mean about the "empty" case

favila 2025-10-09T18:47:06.558069Z

or is this still about the empty case?

cch1 2025-10-09T18:47:29.275359Z

empty case -> empty map as return when the lookup ref does not resolve?

cch1 2025-10-09T18:48:52.744759Z

I don't think so. Just a two level concern about empty: inconsistent across clients and not matching your (and my) preferred semantics (without which I have to make assumptions about pull attr intersection to ascertain existence of a domain entity).

favila 2025-10-09T18:49:59.450899Z

> (without which I have to make assumptions about pull attr intersection to ascertain existence of a domain entity). Don't do that. Doc says "returns a map"; treat result like a map, and nil like an empty map (as one does in clojure idioms). Don't treat it like a value you can test for truthiness.

cch1 2025-10-09T18:50:25.876899Z

How do I test if the state of XX exists?

cch1 2025-10-09T18:50:49.663729Z

(use case: validating user input)

favila 2025-10-09T18:51:11.240379Z

(let [x (d/pull db [:identifier] [:identifier "foo"])]
 (if (:identifier x) ;; exists
 )

favila 2025-10-09T18:51:15.773759Z

or go through query

cch1 2025-10-09T18:51:56.742209Z

Sadly, that convolution is the conclusion I come to.

favila 2025-10-09T18:52:05.398309Z

(d/q '[:find (pull ?e ,,,) :in $ ?id :where [?e :identifier ?id]])

cch1 2025-10-09T18:53:24.123329Z

And I only come to that conclusion through experience. (missing) Documentation and working with ion apps could lead you to believe there is a better world.

favila 2025-10-09T18:53:30.035409Z

question, what should these do?

(d/pull db [(:unasserted :default "unasserted")] [:identifier "foo-doesnt-exist"])
(d/pull db [(:unasserted :default "unasserted")] eid-with-no-assertions)

cch1 2025-10-09T18:54:13.074149Z

First should return nil. Second can return {:db/id eid-with-no-assertions}.

favila 2025-10-09T18:54:17.837329Z

> Documentation and working with ion apps could lead you to believe there is a better world. This bit I don't understand; the docs for d.client.api/pull clearly say "Returns a map". Peer is less forgiveable because the docs don't say

favila 2025-10-09T18:55:15.141589Z

So, you get a different result depending on what input you supplied? You realize how that may surprise people who expect it not to matter what entity reference type you use?

favila 2025-10-09T18:56:05.433099Z

One could easily argue behavior should always be as if you "found" an entity with no assertions on it

favila 2025-10-09T18:56:27.212319Z

(which is what "returns a map" seems to imply)

favila 2025-10-09T18:56:43.649909Z

(FWIW I don't know what actually happens for those :default cases above)

cch1 2025-10-09T18:57:28.150649Z

At the domain level, that makes no sense when you have an identity attribute. A state without a two letter USPS abbreviation is not a state.

cch1 2025-10-09T18:58:04.723299Z

And if I assert it does exist (with a lookup ref) and that is proven false, it would sure be useful if the API signalled that my assertion was false.

cch1 2025-10-09T18:59:08.603939Z

IOW, I think lookup refs (and idents) deserve special treatment.

cch1 2025-10-09T18:59:35.107759Z

And the ion client lulls you into a false sense that the special treatment is real.

cch1 2025-10-09T19:01:05.366419Z

First priority: consistently across the clients, or clear documentation to the contrary (lack of documentation is not sufficience). Second priority: an API that makes it "easy" to ascertain existence of a domain entity by a pull expression that is also pulling attributes of that entity (if found).

favila 2025-10-09T19:01:15.732089Z

Suppose this function exists:

(defn do-something [db id]
  (let [r (d/pull db [:get-stuff] id)]
    ;; do stuff with r
    ))

favila 2025-10-09T19:01:39.819709Z

Suppose you have one caller that calls it like this:

(defn http-handler [db {:keys [identifier]}]
  (let [id [:identifier identifier]]

    (do-something db id)))

favila 2025-10-09T19:02:27.789969Z

Then change to this:

(defn http-handler2 [db {:keys [identifier]}]
  (let [{eid :db/id} (do-other-stuff db identifier)]
    (do-something db eid)))

favila 2025-10-09T19:03:03.053509Z

what should do-something assume about what d/pull returns? It didn't control whether id was a lookup ref or an eid

favila 2025-10-09T19:03:46.937689Z

If lookup ref and eid should do return something different, you've put additional burden on caller of d/pull to check two different things

favila 2025-10-09T19:04:17.717189Z

if they do the same thing, then it can consistently do the "convolution"

cch1 2025-10-09T19:05:06.050639Z

In many situations, the value of ascertaining-existence-and-pulling-useful-attrs-for-further-work in one step is more valuable than abstracting of the eid form. In such situations, you would not write code that allows an arbitrary eid like you have shown.

cch1 2025-10-09T19:05:36.572499Z

A function that takes an arbitrary pull selector and a lookup ref is fine in that world.

favila 2025-10-09T19:06:18.955209Z

that means lookup refs and eids are not substitutable, which seems bad

cch1 2025-10-09T19:06:22.465239Z

We rarely pull with db/ids... and we very frequently want to pull-and-ascertain-existence

favila 2025-10-09T19:06:50.839869Z

well, maybe in your codebase; not so in other's I've used (which eagerly resolve to an eid and flow it)

cch1 2025-10-09T19:06:53.243729Z

I would vastly prefer a pull that supported lookup refs and idents only and returned nil on failure to resolve.

cch1 2025-10-09T19:07:17.944269Z

But even without that, your proposal above is a very close second.

favila 2025-10-09T19:07:17.969199Z

But I hope you understand now that it is a preference, and not without downsides?

cch1 2025-10-09T19:07:41.696329Z

I totally appreciate the db/ids behave differently.

favila 2025-10-09T19:07:43.281859Z

(and definitely not behavior guaranteed by the api)

cch1 2025-10-09T19:07:57.206669Z

And using them interchangeably has risks (and not just in pull).

cch1 2025-10-09T19:08:41.073749Z

But I maintain that behavior of pull should be consistent across clients and there is high value in being able to pull and ascertain existence in one step (particularly in cloud).

favila 2025-10-09T19:10:58.071169Z

"that behavior of pull should be consistent across clients" yes, I'll get that filed; but this bug is not "dangerous behavior" if you don't assign nil a special meaning (which you shouldn't). "high value in being able to pull and ascertain existence in one step" agree, but this is almost certainly a feature request.

cch1 2025-10-09T19:11:13.679709Z

The ability of pull to synthesize an entity (via defaults) that does not exist in the db (=> not found via the eid assertion) is, IMO, nearly worthless.

cch1 2025-10-09T19:12:03.986729Z

Somehow, I feel that the ability ascertain existence and pull attrs in one API was sacrificed to support default. If so, that was a bad tradeoff.

cch1 2025-10-09T19:12:13.820969Z

(but maybe I'm just wrong)

cch1 2025-10-09T19:14:52.037899Z

Thanks for filing the bug. Long ago I internalized the disappointment over the inability to ascertain existence while pulling attrs. But the client mismatch caught me off guard yesterday in production.

favila 2025-10-09T19:16:54.286929Z

> Somehow, I feel that the ability ascertain existence and pull attrs in one API was sacrificed to support default. If so, that was a bad tradeoff. I doubt this was conscious, and I think whether this tradeoff was bad is very debatable, even though I have my preference. (I also don't know if there's actually a tradeoff--I still don't know what :defaults does in the failed-lookup case.)

cch1 2025-10-09T19:20:01.873779Z

It gets very very confusing as to what will be returned with the eid type and defaults (not to mention client!). In my namespace providing existence-while-pulling semantics, there are a lot of caveats. Defaults and db/ids require special treatment.

favila 2025-10-09T19:30:21.536269Z

Here's peer (I don't have a cloud handy):

(d/pull db '[:db/id] [:identifier "doesnt-exist"])
;; => {:db/id nil}
(d/pull db '[:db/id :db/doc] [:identifier "doesnt-exist"])
;; => {:db/id nil}
(d/pull db '[:db/doc] [:identifier "doesnt-exist"])
;; => nil

(d/pull db '[(:db/id :default "db/id")] [:identifier "doesnt-exist"])
;; => {:db/id nil}
(d/pull db '[(:db/doc :default "db/doc")] [:identifier "doesnt-exist"])
;; => {:db/doc "db/doc"}

cch1 2025-10-09T19:31:58.780799Z

I have to step out, but I'll run that same thing on cloud and db-local. I might even get around to testing on ion.

favila 2025-10-09T19:32:22.823759Z

Schema for :identifier

{:db/ident :identifier
                    :db/valueType :db.type/string
                    :db/cardinality :db.cardinality/one
                    :db/index true
                    :db/unique :db.unique/value}

cch1 2025-10-09T19:33:05.245689Z

I assume db.unique/value and db.unique/identity are treated the same... I don't have any db.unique/value in my cloud db.

cch1 2025-10-08T19:59:03.150339Z

FWIW, db-local behaves like the ion client AFAICT.

cch1 2025-10-08T19:59:40.892659Z

(I am aware that there are other sharp edges around pull with an entity id versus a lookup ref, but I don't want to muddy the waters of this discussion with that bug).

cch1 2025-10-09T02:37:40.665389Z

@jaret, any thoughts?

enn 2025-10-08T21:35:31.904949Z

Can someone confirm my understanding of the values received by a metrics callback? For TransactionDatoms, the count value is counting transactions? So a value like {:lo 1, :hi 6, :sum 15, :count 10} means there have been 10 transactions since the last metric emission, with a total of 15 datoms, and of those 10, the smallest had 1 datom and the largest 6 datoms?

favila 2025-10-08T21:48:10.593539Z

correct. All metrics with a map value are statistical samples summaries

enn 2025-10-08T21:48:30.060089Z

Thank you