What is the best way of doing paginated queries for chat messages for Datomic Cloud in 2025?
Interesting approach, thank you for sharing!
you can always use the linked list pattern, something like this:
(def schema
[{:db/ident :conv/id
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/unique :db.unique/identity}
{:db/ident :conv/head ;;newest message in conv
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/one}
{:db/ident :msg/text
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one}
{:db/ident :msg/from
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one}
{:db/ident :msg/prev
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/one}])
(d/transact conn {:tx-data schema})
(d/transact conn
{:tx-data
[
{:db/id "m1"
:msg/text "(first msg)"
:msg/from "Alice"}
{:db/id "m2"
:msg/text "(second msg)"
:msg/from "Bob"
:msg/prev "m1"}
{:db/id "m3"
:msg/text "(last msg)"
:msg/from "Alice"
:msg/prev "m2"}
{:conv/id "conv1"
:conv/head "m3"}]})
(let [db (d/db conn)]
(->>
(d/pull db
[:conv/id
{:conv/head
[:msg/text
:msg/from
;;recursion limit
{:msg/prev 10}]}]
[:conv/id "conv1"])
:conv/head
(iterate :msg/prev)
(take-while some?)
(map :msg/text)))
;; ("(last msg)" "(second msg)" "(first msg)")
this will get you ordered messages and you can use recursion limit + message id to do pagination. (edit: performance will be worse but it might not matter)Query has where clauses that restrict messages by account, user etc.
The main thing I’m asking seems to be: do I still need to sort on the server side? is there any difference between “q” and “qseq”?
Are chat messages inside of some container like “conversation”? Is the design intended to be chat messages between 1 user and some AI backend (eg many messages are scoped to 1 person and 1 conversation) or is this a message platform like slack or discord (many messages from many users all within the same channel).
Yes, messages are wrapped in a conversation. Messages are only between two users.
Maybe I’ve provided a bad example. In case of messages I can index them incrementally and then just fetch ranges, right? But I cannot do the same for fetching the conversations. So what would be the best approach for conversations?
I think your choices are to sort the results of q or qseq (sorting will fully realize, so, not super valuable here) or leverage sorted indexes, possibly with tuples and index-pull using key-based pagination
Thank you
Index pull is the only api currently which supports reverse index traversal so you don’t need to sort yourself. That being said, if N is small, sorting is microseconds usually so it’s no big deal
I'm not sure I understood the thing about the reverse index traversal. Is there a way to get results in natural order using “q” or “qseq”?
Datomic query is set based, so the order is not deterministic
Okay, thank you Joe. Merry Christmas!