Hisako1337

Hisako1337

Good morning fellow alchemists!

I have a question about quite a low level implementation topic regarding copy/concurrency semantics, let me explain the situation briefly first:

  • I have some (~10) ETS tables (set/unordered) that contain a few 100k records each, where the primary keys are either straight integers or tuples of integers (mimicking compound keys)
  • the values in the tables mostly are HashSet of integers (“IDs”)
  • the tables are owned by dedicated GenServer processes, so that only the owning process can update data within its ETS table, but all other processes can read in parallel from it
  • the owning processes subscribe to phoenix PubSub events in order to update individual ETS records in realtime as needed
  • there is a general “search” function that contains several keys to look up within the ETS tables, each returning a HashSet of IDs, and the final result set is the intersection of all these IDs in the hashsets (meaning IDs that are in all returned lists) + some business logic in between
  • this is already a performance optimization on precomputed data in memory, since the database chokes on this calculations (we optimized as much as we could before), therefore “use SQL” is not an option here
  • this search function is also a hot code path throughout our whole application and must be as fast as technically possible (few miliseconds, tops)
  • it worked quite good so far but now that the amount of records in the tables (and the lenght of the result sets) go up over time, this “algorithm” is becoming noticeably slower
  • we don’t want to introduce additional infrastructure components like search engines

so this is the situation right now. the precomputation of data and holding it in ETS tables already brought us a great performance boost, but it seems the “intersection” logic with custom rules is the new bottleneck now.

What we already use is Rustler for a different problem elsewhere in the project, and it basically “solved” another complicated computational problem. But here I think the actual problem is the “copying” of increasing amounts of data (few dozen megabytes) between BEAM processes, plus the computation afterwards.

Now my idea right now is to move the precomputation-code into a Rustler NIF, maybe even store it all in a single Map structure instead of multiple ETS tables, and on request use this map as a parameter + the search parameters to calculate the resulting list of IDs also in the NIF and return it.

The three questions I have right now (couldn’t find details so far):

  1. Can I pass (and receive!) data back and forth into the NIF via pointer/reference instead of copying it to eliminate any overhead here?
  2. Can I hold the precomputed data in a Genserver and make it accessible in a concurrent way similar how ETS can be configured for parallel read access? Without too much copying?
  3. Can I modify parts of the data or replace the whole data container from within Rust to speed up things?

AFAIK a GenServer can not handle true concurrency (it iterates on its single linear inbox), and Task creates new processes that copy memory into it, even ETS copies memory on querying but at least updates are “in-place”.

Thanks in advance!

Showing Posts 1 to 10

dimitarvp

dimitarvp

As a start, do you want that cache to be multi-node or single-node?

cpud36

cpud36

In Rustler you can use resource/ResourceArc to pass a reference into Elixir code. The documentation is quite lacking, but you can look at this test.

If you want to have mutable resource, you must manually synchronise on the Rust side. E.g. you can use RwLock, or an rcu.

Beware, that resource references cannot be shared across nodes AFAIK.

Hisako1337

Hisako1337 OP

single node, as in every node in the cluster has its own local cache, but every node in the cluster has the cache :slight_smile:

Hisako1337

Hisako1337 OP

oh cool, that already helps a bit, thanks!

filmor

filmor

Resource references can be shared across nodes, but they can only be used (in a NIF) on the node that created them initially and only if it’s still alive when accessed. E.g. if you generate a resource object on your node from a process, send it to another node, let the process die and receive the answer back, the resource object will not “work” anymore.

I’m currently working on refactoring resources (and now that you say it, @cpud36, the docs for this one are indeed very much lacking) as they are using a mechanism that will produce warnings and later errors in rustc.

Data is always passed by reference into NIFs as they run in the same process environment as the calling process. You could (as an alternative to using a resource object) also store the data in a binary object (e.g. using something like flatbuffers). If it’s larger than 64 bytes, its data will not be stored directly in the process environment and will not be copied either.

dimitarvp

dimitarvp

My Rust chops have eroded a little bit since I haven’t used it actively in like half a year now but last I remember I have used DashMap successfully before in an application that had a very heavy usage of read and write caching. It’s single-node.

Sadly this was predating the times I became obsessed with telemetry so no clue if it’s doing better than ETS but I’d think since it doesn’t copy data that it should be faster.

Others have already pointed you at how you can create a resource inside the Rust code and return a reference to it in Elixir.

andyleclair

andyleclair

If you’re precomputing the data, you could look into using :persistent_term but I’m not sure the viability of updating that data, since you’d force a global GC on every update. How frequent are the updates?

Hisako1337

Hisako1337 OP

Data is always passed by reference into NIFs as they run in the same process environment as the calling process

this is hugely valuable information for me already, thanks for that!

so this would solve part of my problem already, passing the data blob into rust for further computations is “free”!

Hisako1337

Hisako1337 OP

I have used DashMap successfully before in an application that had a very heavy usage of read and write caching.

this looks nice indeed, but my problem around it is still: where to store that data in Elixir land? a GenServer afaik does not allow for truly concurrent reads

Hisako1337

Hisako1337 OP

How frequent are the updates?

unpredictable unfortunately - could be nothing for an hour but also some cronjob somewhere could trigger thousands of updates in a few seconds…

also I think (mis-) using persistent term for that is kind of dirty, isn’t it?

Where Next? Top

Trending in Questions Top

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
Onor.io
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New
Trolleger
What approach to take when sending live updates to “random” users Hi! I have a question, I have a little chat app, and when I create a DM...
New
matt-savvy
Anyone here using Honeybadger? My Honeybadger account is being overwhelmed with noise from some bots. Seeing a lot of Bandit.HTTPError...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New

Other Trending Topics Top

webofbits
I’ve released BeamConsole 0.1.0, an embeddable, read-only process flight recorder and process map for Phoenix and BEAM applications. Its...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews