chrism2671
Let’s a consider a theoretical case of building an in-memory stock exchange on Elixir.
A stock exchange has a limit order book, which is a list of all the orders submitted, and then it has to match buyers & sellers.
In this case:
- The main activity is going to be people submitting and cancelling orders, where we just need to look up by the order_id. Changes here need to be atomic.
- We need to be able to continuously scan the orders and find the ones with the best prices, so we can match those.
How do we store this order book to get the best performance?
Notes:
- According to the docs, Map KV lookup is O(log N), whereas ETS is O(1).
- Everything I’ve read in benchmarks indicate that Map is approximately 2x faster than ETS for a r/w cycle, but then they’re probably wrong.
- Maps are enumerable, which should help with (2), whereas I don’t believe ETS tables are (but can we query them in a clever way?). An Enum.filter/reduce would be O(N)
- Let’s assume there are N=1,000,000 orders, and we are receiving 1000 new orders/cancellations per second.
I’m curious to know what the right way to structure such a thing would be. Does keeping all the orders in a single GenServer process create a bottleneck? Does it really matter whether we use ETS or Map?
Trending in Questions
Hello!
Suppose you are building workflow (order / task / payment) processing system with the following requirements:
Each workflow con...
New
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
Before I dive in myself, did anyone successfully sprinkle Hologram into their existing LiveView app?
Looking for hints regarding:
Addi...
New
Hi all, I wanted to ask how the community is dealing with post-release steps.
Today we have Ecto migrations, which make sure that the db...
New
Kia ora,
We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
Hello,
I have an Elixir backend that implements a custom protocol over TCP. I want to load test the backend and assess the performance o...
New
Other Trending Topics
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
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
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #phoenix_html
- #iex
- #graphql
- #ai
- #genstage
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex










First 10 of 20 Posts
dimitarvp
Why limit yourself only between those two options? A member of this community created a K/V store (@lucaong: CubDB). Additionally, you can go all-in SQL with an in-memory sqlite3 DB as well.
chrism2671
The key thing is performance; obviously if you’re going to step outside the existing tools, you’d use something built bespoke or on top of kdb+ that’s fit for purpose.
The question really surrounds the suitability of elixir in-memory storage mechanisms for certain tasks.
I would add, CubDB does look interesting in its own right, thanks for posting it!
rvirding
One thing to take into account is the size of the datastore, the number of orders. If you keep it in a map then the size of the process heap could become very large will can noticeably affect the gc. With ETS the data is stored off-process heap so its size will never affect a process. This one reason to use ETS.
ETS lookup can be O(1) but it does depend on how you define the table and how you are searching in it. There are different ways to search through a ETS table,
:ets.matchand:ets.select, so speed again depends on how you search. ETS tables are stored off process heaps which this means data will be copied from ETS memory to/form process heaps when the tables are accessed which affects access times. But ETS works with BIG tables.Read the ETS docs for more info.
dimitarvp
Do not underestimate sqlite3. That thing is fast. It obviously cannot be faster than ETS in this case but as @rvirding said, please be aware of the tradeoffs: ETS always copies data when you fetch data from it, and its searching capabilities are much inferior to those of an SQL database.
I’d say that in your case you have to take into account exactly this one thing: how complex will your queries be? Don’t get worried about storage speed just yet, IMO.
EDIT: Check out Ane. It’s a mix of several in-memory storage capabilities of Erlang/Elixir and I found it quite nice to work with when I needed it once.
chrism2671
In this case, would gc on a process cause the process to pause while cleanup took place?
It does seem like ETS really is the only native solution here, with a pool of GenServers adding/cancelling orders.
rupurt
Howdy @chrism2671
I would also love to know the answer to this!!
As you might know
I’ve built an order book mirror using a
Structin aGenServerprocess where the price points for bids/asks are stored in 2 separate maps. It works reasonably well for small order books (<= 25 price points on each bid/ask). But has terrible performance as the size of the order book increases (both memory usage & CPU usage causing back pressure problems).I’ve been meaning to do a deep dive to compare & contrast the various approaches and data structures available within the Erlang/Elixir/OTP ecosystem so thank you @dimitarvp & @rvirding for some of the new suggestions that I didn’t know exist.
I did a quick investigation into what might be the cause of the problem in my architecture as the order book grows, and narrowed it down to a GC problem as I pass the full order book to another process which takes a smaller snapshot so that insert/update/delete performance remains fast.
My assumption is that the best approach will be highly dependent on how it’s used. i.e.
do you want async/sync readsdo you want reads? or do you want to pass the result to another process as a message?do you need to transform the data in some way after insert/update/delete?It’ll be very interesting to see whether or not that assumption holds with some data!
rupurt
@chrism2671 I replaced my
Mapbased order book with anETS ordered setimplementation and the difference in performance is huge.The burst and large order book performance is far more consistent.
The scheduler utilization is consistently far lower (which I assume is due to the lower overhead from garbage collection).
The standard deviation during worst case performance is ~100x lower
Old
Mapbased order bookNew
ETS ordered setorder book:https://github.com/fremantle-capital/tai/pull/113
dimitarvp
I am glad you are making progress and are finding ways to make your storage needs faster.
That being said, if you have to reach for ETS then I’d say that it’s possible that you might also be better served by a native map structure implemented in Rust; integrating that with Elixir (with
rustler) is quite easy.I’ll also remind that sqlite3 is always used in-process so there’s no inter-process communication overhead. Depending on your usage there might not even be data copying involved (although that strongly depends on the queries).
But hey, I am not criticising your hard work; I also used ETS with crushing success in the past.
rupurt
Cheers @dimitarvp
I’m going to add support for order book adapters for exactly this reason! I’d like to be able to configure the different order book implementations so that I can benchmark them together easily + track their performance over time as I make changes to the rest of the codebase. I don’t know
Rustbut it’s on the list of things to learn…I’d like to also try out this approach but the lack of ecto 3 adapter support has kept me away so far. I would assume that the overhead of writing to disk would be slower than ETS though.
dimitarvp
I am working on that every now and then.
I also think the Elixir ecosystem will gain a lot when I am ready with the Ecto 3 sqlite adapter.
Not necessarily. sqlite3 is very mature. But it also has an in-memory mode.