53:19Yeah, I think for me, this is
actually kind of like a, a definition
53:24of a kind of project category.
53:26Like not every project needs
to like prettier lived on and
53:30now it's like super common.
53:32And like, I think that's one very
successful way how a project can evolve.
53:37But I think with absurd-sql, you've built
something it's even more than a proof of
53:41concept because you used it in production.
53:44I, I use it for a while in production.
53:46Other people use it for a while in
production, but, uh, like you said, like
53:50it became a very influential project.
53:53And I think it showed to the world,
like, Hey, we don't need to settle for
53:57saving six megabytes or 50 megabytes
for every little change as you do.
54:02Want to do persistence in the web before
people would just like ride, like.
54:0780 megabyte JSON files, like all the
time to, to index to be, and then we're
54:12then wonder why their, the app is slow
and their, their CPU is going crazy.
54:16So you aiming for higher, I
think that has a massive impact.
54:20And I think that's now those
ideas are still like leveraged
54:24now in other projects.
54:25Whether I think.
54:26The wa-sqlite, um, project that is,
I think also using some, some similar
54:32approaches and by now we also have a
different storage mechanism and the web
54:37that is more and more common, namely
OPFS, original private file system.
54:43Which also already gives you a file
system representation where you can
54:47lock individual files, et cetera.
54:49I think the details are still being
figured out right now, but that's, for
54:53example, what I'm using for Overtone and
in production, that's already quite nice.
54:57But I think you've really, you, you went
super early on that and very daring from
55:03first principles and that's so admirable
and that, that like very inspiring.
55:07Thank you very much.
55:07I appreciate that.
55:08Sometimes I, I don't know.
55:10I, I don't really track.
55:12The, like some of the fallout sometimes,
like I, I just had to invest in my job
55:16a lot for the next like six months.
55:18And so sometimes I'm, I'm not sure.
55:20I don't, I don't know how much influence
it has, but it's, it's good to hear
55:22feedback that work is impactful.
55:25I, I would love to hear about your
experience with, with it, with OPFS.
55:29And, and, uh, are you using
the file system native access
55:31stuff as well and in your app?
55:33How's that going?
55:34The latter, not yet.
55:35I'm also planning to, since like
for Overtone, which is a music
55:39player, I do want to also support
you bringing your own music that
55:43you might have like in a download
folder on a music folder somewhere.
55:47Right now I don't use that
particular browser APIs yet.
55:51But I'm using OPFS for many purposes.
55:54I'm using OPFS for persistence
of a SQLite database, where you
55:59would have used IndexedDB before.
56:01And I think some people still
use IndexedDB for targeting
56:05older browsers as well.
56:06But I'm also using OPFS for like what
you would use a file system for as
56:11well, which is like storing files.
56:13So for example, Before displaying
images in the app, I actually don't
56:18use like just an image tag and
then point to an external URL since
56:22those URLs might go away, et cetera.
56:24So I actually download those images, store
them in OPFS and then pre process them.
56:30Um, on a worker and sent them
over on a, on an off screen
56:34canvas to the main thread.
56:35So I'm like using some, some more
like native development practices here
56:40and trying to bring them to the web.
56:41Very, very much sharing the same
opinion, like your spicy take from,
56:46for me, not so spicy take that
we should aim higher in the web.
56:49And I think we can learn quite a lot from.
56:52More native development backgrounds.
56:54And this is, I think this is how we
get, uh, after all pretty fast web apps.
57:00Figma is another notable example
there where how you get actually a
57:04really high performance app that feels
nice is by aiming higher and, uh,
57:10bringing some of those methodologies
from other environments to the web.
57:14So I think.
57:16There's still a couple of
interesting aspects in absurd-sql
57:19that we haven't yet gotten into.
57:21So you've mentioned that you're
using index to be, uh, with, uh, the
57:26block, um, the, the block storage
and the index to be transactions,
57:30but to actually write that you could.
57:33I guess either do, could you do
that on the main thread since you've
57:37chosen to do that on a worker?
57:38So maybe you can talk a little bit
about the, the threading model and
57:42also how you even went beyond IndexedDB
transactions and using atomics and
57:49shared array buffers to still slay some
dragons that needed to be slayed here.
57:54Yeah, there is one little trick that I
discovered and I think I was actually
57:59a live stream and I think I have a
recording of it, which was kind of fun.
58:01It's like a fun little blew
my mind at, um, at the time.
58:04And I think this is a trick
that was independently
58:07discovered by several people.
58:08I think there's a, uh, there's another
library that I think from is from some,
58:12some Google folks about how, like,
like loading in third party things
58:15on, you know, In like an iframe or
something to kind of like sandbox things
58:19that that uses this same technique.
58:21But essentially here's,
here's the problem.
58:24When you compile SQLite through
WebAssembly, you can intercept
58:28the API calls that it makes.
58:30So you can intercept the like read
The read and write command, like
58:33there's a C API is reading right
for reading and writing files.
58:37And so you can say, okay, I'm going to
implement the read command for like,
58:41through WebAssembly, the WebAssembly
compiled version of this SQLite.
58:45I'm going to implement the
read command and like read some
58:47data for it and give it back.
58:48So I can just read from index, from index
db and everything is going to work right.
58:52The, the problem is, is that
the read command from C is
58:55completely synchronous, right?
58:56So the WebAssembly compiled binary
expects that to be synchronous.
59:00Like you can, it'll call out to
JavaScript and you can implement the read
59:03command, but you cannot await in there.
59:06Like you literally have to, in that event
loop, synchronously return some data.
59:10And so, uh, when I hit that, I was
like, I thought I was dead in the water.
59:13I was like this, this.
59:14I just can't do this.
59:15Maybe, maybe it wasn't that bad
actually, because I think I did
59:18see, so one option is you can in
WebAssembly, I think it has a way to
59:22convert synchronous APIs to async.
59:24I think it's called like
asynchronify or something like that.
59:27I was terrified of it to be honest.
59:29And so I thought it just
wasn't going to work.
59:30I was terrified about the performance.
59:32I've had experiences in some
codebases that overuse of asynchronous
59:36APIs really killed performance
because waiting one promise tick.
59:41In every single layer of the
abstraction is very not noticeable
59:46at a small scale, but it very
becomes noticeable at a large scale.
59:50And the really pernicious thing about it
is that it's death by not a thousand cuts.
59:54It's death by a million cuts.
59:55Like, it's, you can't
go in there and remove.
59:5820 by 20 awaits, right?
59:59And like, it'll be fine.
1:00:00The thing that I don't like about
async code is that as you abstract
1:00:03things away, so if you take it from
two abstractions to 10 abstract, like
1:00:06you split a two async functions into
10 async functions, well, every one
1:00:10of them has to await on each other.
1:00:12And so it's literally making it slower.
1:00:14And that's not the
case , with synchronous stuff.
1:00:16You can make it two functions.
1:00:17You can make it 500 functions.
1:00:19You can abstract things
the way that you want to.
1:00:21The shape of your abstraction
literally impacts performance when
1:00:25you're in, in, um, async world.
1:00:26So I have experience with code bases that
like, that did things in a really weird
1:00:31way, like awaited literally everything.
1:00:33And it was, it was causing
all sorts of perf problems.
1:00:36And so that, For that reason, from my
experience, I was terrified of it because
1:00:40I thought it made every single function
in C in the WebAssembly binary async.
1:00:44It turns out that I was probably wrong
and I probably should have done my
1:00:48own investigation in like performance
profiling because I got some feedback.
1:00:52Later on, like months after I released
absurd-sql, I think from some people
1:00:56that I respected pretty well that
were saying, uh, the async stuff is
1:01:00actually fine, like it actually has
a very negligible performance impact.
1:01:04So there's probably some tricks
that they, they, they do that.
1:01:07So it's probably fine.
1:01:07And I think that WA SQLite
project that you mentioned, I
1:01:11think it actually uses this mode.
1:01:14And the good thing about that is
that it doesn't need atomics, which
1:01:17means it doesn't require HTTPS.
1:01:19So I'm probably.
1:01:21Let me jump back a little bit to
explain why atomics are even needed.
1:01:24So let's go back to that
synchronous method, right?
1:01:26So I need to call into IndexedDB
and get and do an async API.
1:01:30How am I going to do that?
1:01:31Well, there's a little trick that
you can do by making an async call.
1:01:37And I believe that it's Has
to be on a different thread.
1:01:42So there's like two
background threads, right?
1:01:44There's like the normal backend thread.
1:01:46And then there's another separate thread.
1:01:48That's like the thread that reads and
writes from index CB like asynchronously.
1:01:51Right.
1:01:52Between those two threads, you
share a shared array buffer, which
1:01:55is this really low level, really
interesting thing on the web, which
1:01:59is shared memory across threads.
1:02:01And on top of that, you, uh, there's
APIs you can use called atomics, which
1:02:05allow you to interact with this shared
array buffer, and you can actually
1:02:08coordinate across the threads and do
some really, really powerful things.
1:02:12So one of those things that you can
do is you can write to that shared
1:02:15array buffer in a certain way.
1:02:17And then in another thread,
you can call atomics.Wait.
1:02:20And what atomics.wait does is it literally
synchronously blocks that thread from
1:02:24running like it, you call that thread.
1:02:26And if it does not wake up,
then it won't wake up ever.
1:02:29Like you call atomic weight and you
tell it, wait for this bit to be
1:02:33flipped in the shared array buffer.
1:02:34And then the other thread can
flip that bit when it's not.
1:02:37done, and then the other
thing can continue executing.
1:02:40Using that technique, we can read from an
asynchronous thing in the second thread,
1:02:44do whatever we need to, and store that
data in some sort of buffer somewhere.
1:02:49And then while the first thread is
blocked on that atomic set, wait.
1:02:52And then once the, once that
bit is flipped and it continues
1:02:55executing, then it can read from that
buffer and actually get the data.
1:02:58And so using that technique, Um, I
use that technique in every single API
1:03:02in, in Upstairs SQL to turn an async
function into a synchronous function.
1:03:06And that's how it can
interface with IndexedDB.
1:03:09The downside though, is that to use
atomic set weight, it's one of those
1:03:13newer APIs that Chrome and other browsers
force HTTPS a secure context on you.
1:03:18And so your app has to be running under
HTTPS, which I've always said, like.
1:03:22Who cares?
1:03:23Like, of course you're going to
be running under that anyway.
1:03:25It's been enough of a,
kind of a pain point.
1:03:27Like people are using like weird
reverse proxies or doing their
1:03:30own things where suppose, I guess
some people just don't really care
1:03:32and they just want to run HTTP.
1:03:33And so Actual, like the open source
version of Actual can't run under HTTP.
1:03:37And that's like, you always
have to be setting up to me.
1:03:40It's, I don't know, I still am a
little bit like, I don't really care.
1:03:42Like you can just like set up
HTTPS, but it's enough of a source
1:03:45of a pain that I can see benefits
and not, not having to require it.
1:03:49Right.
1:03:49Yeah, I think this was also related
to the, uh, Spectre exploit at
1:03:53some point, uh, vulnerability.
1:03:55And I think it's not just that you need
to run on HTTPS by now, but I think you
1:03:59also need to have a few HTTP headers set.
1:04:03I think like cross origin, uh,
open policy and embedders policy.
1:04:07And I think there was also like
a limitation that Safari didn't
1:04:10support it well for, for some time.
1:04:12So yeah, there it's, Still, browsers
are still growing up, but I think at
1:04:17some point this can be assumed that, uh,
this will just work, but on the other
1:04:21side, I think those tricks might also,
you mentioned that asyncify approach.
1:04:26So that is also another option.
1:04:28And I think there's even a new
approach stabilizing right now.
1:04:32Where WebAssembly natively
can integrate with Promises.
1:04:36So I think that's a, that's a new, um,
development around WASM and browsers.
1:04:42So I think there's, you, you certainly
use quite the bleeding edge there.
1:04:47And so, yeah, right now it's
getting more stabilized.
1:04:51But I do think there is some truth
to what you've mentioned in regards
1:04:55to avoiding asynchronous code when
possible, since it's not just like
1:04:59a potential performance overhead.
1:05:02So, and I think some people go even
as far as saying that going from
1:05:06callbacks to async await and promises.
1:05:09Was one of the biggest
mistakes in JavaScript.
1:05:12I think that can also be
considered a hot take.
1:05:14Let's see where we'll end up
in a couple of years on that.
1:05:17But, uh, aside from the performance,
I think another common downside
1:05:22of asynchronous code is that it
basically introduces distributed
1:05:25systems problems into, into your code.
1:05:28In this case, where we basically
just wrap an API, I think it's okay.
1:05:32Um, But, uh, that's a, that's another
notable difference of like how, what
1:05:37we've been exploring with LiveStore
and, and Riffle is by really making
1:05:42the, like in a browser context, still
from the main thread, allowing for
1:05:46synchronous SQL queries, which return
very fast and therefore like you can just
1:05:52write your normal JavaScript as you do.
1:05:54comes at a risk of potentially
blocking the main thread.
1:05:56That's a, that's a different
challenge, but if you, if you have
1:06:01performance under control, it gives
you a much simpler programming model.
1:06:04So that's another weak one.
1:06:05Yeah, that's, I'm all here
for synchronous SQL queries.
1:06:09I think it's crazy.
1:06:11I think there's been no libraries
before that integrated with SQLite 3 and
1:06:14like made all of the APIs, um, async.
1:06:16It was crazy.
1:06:17Like it's such a in
memory super close local.
1:06:20thing and , you really want at the
low level to, to provide the at
1:06:24least option to be synchronous.
1:06:25And it, so that I just remembered
actually, it's not just
1:06:28wrapping an async function.
1:06:30So like it's, it's an internal
implementation detail.
1:06:33If you go the asynchronous by route,
which turns C functions into asynchronous
1:06:37functions, therefore you can interact
with like asynchronous stuff.
1:06:41Believe that that, that forces you
when you call a SQLite by like method,
1:06:47that method is an asynchronous method.
1:06:49You cannot synchronously execute a SQL.
1:06:51That was a big reason why I also really
did not want to use WI SQL because I
1:06:56wanted my functions to be synchronous.
1:06:58My app depends on many of them being
synchronous and just my workflows.
1:07:01And it just, it just greatly
simplifies the entire workflow.
1:07:04There's no reason for
me to make this async.
1:07:06This is a single client.
1:07:08App, there's one request
coming through at a time.
1:07:11I can control it entirely, right?
1:07:13This is not a web server handling
thousands of requests at a time
1:07:17to take on the complexities of
asynchronous code with the performance
1:07:21hits was just ridiculous to me.
1:07:23And so I, This technique I still think
has has merit, actually, the more I think
1:07:28about it, and it's something that I owe
this community like a great deal of, of
1:07:34like blog posts and and and research.
1:07:36I need to sit down and really
like go through what the latest
1:07:39and greatest is and really.
1:07:40Vetted and see, see what people
are landing on, because if it's
1:07:44still not possible to do that,
I think that's a huge downside.
1:07:47I do.
1:07:47I did just see that the, the
file system access APIs, I
1:07:51believe they finally converted.
1:07:53Originally there were some of them
in the, in a worker thread that
1:07:56were supposed to be synchronous, but
in the spec, we're actually async.
1:08:00And that made me really, really not happy.
1:08:03And I filed like a GitHub ticket.
1:08:05I think they finally, if I.
1:08:07Just checked earlier this morning.
1:08:08They finally have on the MDN
page says that they are now
1:08:11synchronous, which is great.
1:08:12So hopefully we're moving
in the right direction.
1:08:14But I think it's, yeah, I think it's
really, I'm all about synchronous 'cause
1:08:17it means that you can use them in context
that like are synchronous, like there you
1:08:21might be removing a lot of functionality
because once something is async, the thing
1:08:27that uses it has to be async as well.
1:08:29And a lot of these cases for local-first
apps especially, it just is, there's,
1:08:32there's literally no benefit to it.
1:08:33, it's a local app.
1:08:35There's not a second user that
can come be querying this.
1:08:38I don't know.
1:08:38I think I get it.
1:08:40I think we've, we've over
indexed a little bit on this.
1:08:42And I do think that I
forgot about the headers.
1:08:45That is because like reverse
proxies can like drop those headers
1:08:48and then like a user is like,
why isn't this app even loading?
1:08:50So I, I don't know.
1:08:51It's a trade off.
1:08:52I still probably lean a little
bit towards it's worth it.
1:08:55But yeah, I completely agree with, uh,
I think this is the same theme again,
1:09:00where in web development, we've kind
of gotten so used to some practices.
1:09:06And I think it's one is, uh,
being efficient and performance
1:09:09minded, but another is just
like the programming models, how
1:09:13they've kind of eroded over time.
1:09:15we need to deal with distributed
systems problems where they're
1:09:18just completely accidental.
1:09:20And , we're just so used to, to like
so many things being asynchronous,
1:09:25which doesn't need to be asynchronous.
1:09:27We kind of went from callback
hell to async hell in a way.
1:09:30And in React we use, useEffect for
so many things where we shouldn't.
1:09:35And is just so wild
that like most of our.
1:09:38Data interactions are asynchronous,
like in a way it's almost like if
1:09:43react would not just give you a value
right away, but would give you like a
1:09:48tuple of like either it's loading or
a value is like we're already halfway
1:09:52there just in, in such a bad direction.
1:09:55So I, uh, I think this is kind of a
subtle difference for people to understand
1:10:00how much synchronous code execution
can simplify your app development.
1:10:05But I think I'm, I'm preaching
to, to the choir here.
1:10:09Yeah, sure.
1:10:10Totally.
1:10:11I mean, it helps with debugging.
1:10:12Like if you're stepping over code,
whenever you're hitting like async
1:10:15code, Chrome tries to do this thing
where it like will step over the await,
1:10:19but it only works like half the time.
1:10:21Like it's yeah, it's super annoying.
1:10:23I do get this needed probably most of
the time, but it makes me sad when I just
1:10:27see it like applied without any thought.
1:10:29So after fulfilling your goal with
bringing Actual to the web through
1:10:35absurd-sql, which I think is like
just to look back, like how much
1:10:39pioneering work you've really
done to make this app happen.
1:10:42Like you started this journey before.
1:10:45The term local-first was there.
1:10:47You've built one of the first credible
local-first apps and really invented
1:10:51so many things along the way, like all
by yourself with the help from, from
1:10:56some of your, your friends, but you,
like you figured out like how to use
1:11:00SQLite in like even in a web context in
a reactive way, but then also made it
1:11:05collaborative through your sync engine and
ultimately brought all of this to the web.
1:11:09That is an impressive journey.
1:11:11And I think you've been on the journey,
not just building all of this full
1:11:15time, but you actually had like some,
some, some Actual full time job next
1:11:20to that, which I think at some point
was just too much, which led you to,
1:11:24to at some point, uh, hand over the
project to the broader community.