6 ms·
> copy and paste a higher-level construct like an Agent or Genserver and add the 1 line of code to this root supervisor that was just a file auto-generated in y
by nickjj 6y ago
> copy and paste a higher-level construct like an Agent or Genserver and add the 1 line of code to this root supervisor that was just a file auto-generated in your project. But that'll get you a) introduced to the actor model and b) thinking about messaging while c) not ever worrying about or messing up concurrency.
Isn't it well known that GenServers can become severe bottlenecks unless you know the inner workings of everything to the point where you're an expert?
I'm not an Elixir expert or even used a GenServer in practice but I remember reading some warnings about using GenServers around performance because they can only handle 1 request at a time and it's super easy to bring down your whole system if you don't know what you're doing.
This blog post explains how that happens: https://www.cogini.com/blog/avoiding-genserver-bottlenecks/ https://www.cogini.com/blog/avoiding-genserver-bottlenecks/
And I remember seeing a lot of forum posts around the dangers of using GenServers (unless you know what you're doing).
It's not really as easy as just copy / pasting something, adding 1 line and you're done. You need to put in serious time and effort to understand the intricacies of a very complex system (BEAM, OTP) if you plan to leave the world of only caring about local function execution.
And as that blog post mentions, it recommends using ETS but Google says ETS isn't distributed. So now suddenly you're stuck only being able to work with 1 machine. This is a bit more limiting than using Python or Ruby and deciding to share your state in Redis. This really does typically require adding a few lines of code and now your state is saved in an external service and now you're free to scale to as many web servers you want until Redis becomes a bottleneck (which it likely never will). You can also freely restart your web servers without losing what's in Redis.
I know you can do distributed state in Elixir too, but it doesn't seem as easy as it is in other languages. And it's especially more complicated / less pragmatic than other tech stacks because almost every other tech stack all use the same tools to share external state so it's a super documented and well thought out problem.
- ericmj 6y ago> And I remember seeing a lot of forum posts around the dangers of using GenServers (unless you know what you're doing). The danger is using a single process of the GenServer instead of multiple, so you can get single process bottleneck that won’t use multiple cores. You don’t have to know any intricacies of BEAM or OTP to know and design around using multiple process instances of the GenServer. > I know you can do distributed state in Elixir too, but it doesn't seem as easy as it is in other languages. You can use Redis in Elixir as well. Saying that Elixir is worse at distribution than Python/Ruby because ETS isn’t distributed is a bit like saying Python is bad at distribution because objects are not distributed. It’s especially strange since Elixir ships with a distribution system (so you can access ETS from other machines) while your other example languages do not.
- nickjj 6y ago> You can use Redis in Elixir as well. Totally but a lot of folks say "but Elixir is good / easier because you don't need tools like Redis". But then when you try to do it without Redis you need to account for many things yourself and end up re-inventing the wheel. This is time spent developing library'ish code instead of your business logic. It's sort like background job processing tools. Sure you can do everything in Elixir, but when you want uniqueness guarantees, queues, priorities, exponential back-off retries, periodic tasks, etc. you end up using a dedicated background processing tool in the end because it's very difficult and time consuming to write all of that from scratch. But in practice almost all of those things end up being a requirement in a production grade system. You end up in the same boat as Python and Ruby.
- querulous 6y agoi would take most of these claims about elixir with a grain of salt distribution, for example, is a much lauded feature of elixir/erlang but if you look into the implementation it's really just a persistent tcp connection with a function that evals code it's sent on the other end. you could easily write the equivalent in ruby or python or java but you probably wouldn't because it's not actually a very good idea. there's no security model, the persistent connections won't scale past a modest cluster size and the whole design ignores 30 years of experience in designing machine to machine protocols similarly, people will mention mnesia or ets as a replacement for a database or redis. these are both very crude key/value stores with only very limited query capabilities. you should use them where you would use an in process cache in another language and not as a replacement for an out of process shared cache. they were never designed as such. and as in process caches they are really nothing special in fact, a lot of elixir's marketing comes down to "do more with less" with a lot of focus on how you can do on a single machine what other languages take whole clusters to do. this is (partially) true. elixir/erlang are excellent for software that runs on appliance style hardware where you can't simply add machines to a cluster. it is, in fact, what erlang was designed to do. what this ignores though is that this is a terrible model for a service exposed over the internet that can run on any arbitrary machine in any data center you want. no one will advise you to run a single vm in aws or a single dyno on heroku for anything that matters. elixir/erlang's features that increase it's reliability on a single machine are a cost you pay not an added benefit. the message passing actor model erlang built it's supervision tree features around are a set of restrictions that are imposed so you can build more reliable stateful services on machines that don't have access to more conventional approaches to reliability (like being stateless and pushing state out to purpose built reliable stores) if you're building systems that need to run in isolation or can't avoid being stateful then perhaps elixir/erlang has some features that may be of interest. the idea that these features are appropriate for a totally standard http api running in aws or digital ocean or whatever backed by a postgres database and a memcache/redis cluster is not really born out by reality however. if it were surely other languages would have incorporated these features by now? they've been around for 30 years and the complexity (particularly of distribution and ets) is low enough you could probably implement them in a weekend
- josevalim 6y agoA GenServer may become a concurrency bottleneck as any other language with concurrent data access may become a bottleneck depending on your abstraction of choice. This is nothing specific to Elixir. What Erlang/Elixir bring to the table is that there is a good vocabulary and introspection tools to observe these issues. For example, if you have a GenServer as a bottleneck, you can start Observer (or the Phoenix LiveDashboard or similar), order processes by message queue, and find which one is having trouble to catch-up with requests. So we end-up talking about it quite frequently - it is easier to talk about what you see! If you need distributed data, then by all means, use Redis or PostgreSQL or similar. ETS is not going to replace it. What ETS helps is with sharing data within the same node. For example, if you have a machine with 8 cores, you may start 8 Ruby/Python instances, one for each core. If the cache is stored on Redis, you will do a network roundtrip to Redis every time you need the data. Of course you can also cache inside each instance but that can lead to large memory usage, given each instance has its own memory space. This may be accentuated depending on the data, such as caching geoip lookups, as mentioned in the post you linked. In Elixir, if you have 8 cores, it is a single instance. Therefore, you could cache geoip lookups in ETS and it can be shared across all cores. This has three important benefits: lower memory usage, reduced latency, and increased cache-hit ratio in local storages. At this point, you may choose to not use Redis/DB at all and skip the additional operational complexity. Or, if you prefer, you can still fallback to Redis, which is something I would consider doing if the geoip lookups are expensive (either in terms of time or money). In any case, ETS is completely optional. If you just want to go to Redis on every request, you can just do that too! And for what is worth, if I need distributed state, I just use to the database too.
- nickjj 6y ago> Or, if you prefer, you can still fallback to Redis, which is something I would consider doing if the geoip lookups are expensive (either in terms of time or money). What if the geoip lookup took 1.5 seconds to look up from a remote API? Is ETS still the right choice? Based on your statement, it sounds like you wouldn't since that's a long time (relative to a 25ms response). But if ETS is meant to be used as a cache, wouldn't that defeat the purpose of what it's meant to be used for? Like, if I wanted to cache a PostgreSQL query that took 1 second to finish. Isn't ETS the primary place for such a thing? But 1 second is a long execution time. I know Cachex (the Elixir lib) uses ETS to cache things, so now I'm wondering if I've been using it for the wrong thing (caching database calls, API call results, etc.). Normally in Python or Ruby I would have cached those things in Redis and lookups are on the order of microseconds when Redis is running on the same $5 / month VPS as my web server. It's also quite speedy over a local network connection too for a multi-server deploy. Even with a distributed data store in Elixir, you'd hit the same network overheard right? > if I need distributed state, I just use to the database too. This part throws me off because I remember hearing various things in Phoenix work in a distributed fashion without needing Redis.
- lostcolony 6y agoCouple of other solid responses here, but going to add my own - GenServer is immaterial - it's the single process that can be a bottleneck. Just like a single thread can be in other languages. If you need multiple threads in other languages, you'll need multiple processes in Erlang. The nice thing here being the cost of spinning up more (even one per request) is negligible, the syntax is trivial, and the error model in the event something goes wrong is powerful, whereas threads in other languages are so heavy weight you have to size the pool, they can be complicated to work with, and if things go wrong your error handling options tend to be limited. Use ETS in the places you'd use in memory caching. That's it. That's what it's meant for. If you need distributed consistency, it's not the right answer. If you need a single source of truth, it's not the right answer. But if you need a local cache, that does not require strong consistency(and that's very, very common in distributed systems), it works great.
- nickjj 6y ago> The nice thing here being the cost of spinning up more (even one per request) is negligible, the syntax is trivial Do you have an example of the syntax? Basically, how would you wire things up so it becomes difficult or maybe even impossible to shoot yourself in the foot with GenServer bottlenecks? Also if you had to guess, why would the blog post author of the post I link not do this trivial syntax adjustment instead of rewriting everything to use ETS? Unfortunately posts like theirs are what comes up first when you Google for things like GenServer vs ETS.
- sodapopcan 6y agoAn Elixir/Erlang process may be used to both do work and store state. This is what a GenServer is for. I don't have experience building large systems (or even production code) in these languages but have been doing tons of reading and playing in the past months. The intuition I've built so far is that holding state in a process (GenServer being an abstraction around a process) is reserved for stuff like "The current state of a games table" where you would have one process per game being played. This state is only being read and manipulated by itself and will be thrown away when the game is over (which might make it sound like an object!). If you suddenly had a requirement to show live stats from thousands of games being played, then one option would be to start sending it to ETS. One big difference to point out is that to store state in a process you need to use the language's data structures (lists, maps, keyword lists) which can get slow when they grow HUGE. ETS is an actual key-value store with incredibly fast lookup (and can easily be read by multiple processes). I hope that makes sense—I'm also testing my own knowledge here :)
- dragonwriter 6y ago> And as that blog post mentions, it recommends using ETS but Google says ETS isn't distributed. So now suddenly you're stuck only being able to work with 1 machine. There is a mostly API-compatible distributed version of ETS in OTP, called DETS. And a higher-level distributed database built on top of ETS/DETS called Mnesia, again, in OTP. So, no, you aren't. > I know you can do distributed state in Elixir too, but it doesn't seem as easy as it is in other languages. And it's especially more complicated / less pragmatic than other tech stacks because almost every other tech stack all use the same tools to share external state so it's a super documented and well thought out problem. You can use the same external tools in Elixir as on platforms that don't have a full distributed database built in as it is in the OTP, so, I don't see how the fact that those external tools are widely used on other platforms makes Elixir harder.
- b3orn 6y ago> There is a mostly API-compatible distributed version of ETS in OTP, called DETS. And a higher-level distributed database built on top of ETS/DETS called Mnesia, again, in OTP. So, no, you aren't. DETS is the disk based term storage, it is as distributed as ETS.
- strmpnk 6y agoI believe the distribution layer built on top of ETS and DETS you’re trying to name is mnesia. It supports distribution and allows a variety of interesting topologies. It’s not the only distributed data store available on the BEAM but it’s well tested, mature, and comes as part of the OTP libraries.