tkruthoff

tkruthoff

Is an app with 3000 Microservices a fit for Elixir/OTP over Kubernetes/GRPC

Elixir and Phoenix are awesome and I’m hoping I can use them on my next project, however I’m an Elixir/OTP newbie and I’m not finding real examples of how Elixir apps with lots of moving parts are written, maintained and deployed.

For context, elixir-in-times-of-microservices by Jose is what first peeked my interest and I’ve bought/read every Elixir book I can get my hands on and this what we’ve come up thus far:

Umbrella OTP App containing:
API: phoenix and ecto serving REST and GraphQL – originally ecto was in separate app called DB, but then we settled on the API (and not the DB) is what the other apps/services interface with
FrontEnd: Main webapp our customers use (phoenix)
Admin: Backoffice webapp for staff (phoenix)
Integrations: This is where things go off the rails, we injest and transmit data, mostly in XML via HTTPS with over 3000 sources. An App per seems out of place, in most cases we have a REST or Soap client that listens to a queue and takes action, much like a background task. A lot of them will also spin up a GenServer per source (and this should be singleton across the entire cluster) and poll the source for data. We need to be able to control start/stop/pause per source as the sources like to add/remove data elements from time to time, best scenario would be able to update and deploy new code for a source without affecting the rest of the system. So we are thinking most of the 3000 integrations will be a Supervisor, with a GenServer (receiver) and a Module (sender).

Historically, each source ran as a windows service on a beefy single box, manually deployed, paused and updated separate from the web apps. (new services will run on Linux) The benefits/joy of being able to pull up :observer and peer into the source processes with state, or to remote iex and tinker would be epic.

Is anyone doing something like this in production that is willing to share how they structured the many integrations and deploying and upgrading a system like this. It’s possible we could be a trailblazer, but an existing production story would be invaluable.

Thank you

Most Liked

NobbZ

NobbZ

With 3k services this would result in 3k nodes, which would mean roughly 9M node-interconnections, since the BEAM wants to interconnect all nodes with each other. This won’t work well. You need to replace the messaging by another mechanism that does not require the full mesh.

Also you’d load the BEAM 3k times, while by putting some if not all of those modules alltogether on a single bare machine without docker, you can still update the modules one by one…

sasajuric

sasajuric

Author of Elixir In Action

Running 3000 services in a single BEAM node should not be a problem. You basically start one (or more) processes per each service, and that’s it.

The second part of your requirement is indeed tricky. If you can afford to restart everything, your life will be much simpler. If not, then you must enter the realm of code reloading. Some basic instructions are available here.

In some simpler cases, this might work out of the box, If you actually have 3000 different modules (which I somehow doubt, but who knows ¯\(ツ)/¯ ), and cache previous release builds on the build server, then I think (not 100% sure though) that distillery will be able to detect the change and generate a correct appup automatically.

In more complicated cases, you might need to hand code an appup file. You can find some basic examples here. As far as I understand, appup is quite flexible. Among the low-level instructions there is apply which allows you to invoke a series of functions in an arbitrary order, so I it should be possible to do perform any kind of upgrade logic, no matter how complex it is.

peerreynders

peerreynders

I think that article was triggered by an earlier StackOverflow topic - where I found that one particular passage stood out:

So far I haven’t talked about microservices. That’s because, up to this point, they don’t really matter.

i.e. building (potentially distributed) applications with Elixir/OTP can leverage some of the benefits associated with microservices architecture without having to accept the overhead of supporting microservices architecture - but that doesn’t mean Elixir/OTP solutions necessarily would classify as realizations of microservices architecture - and that’s OK because microservices architecture isn’t supposed to be an end in itself. Meanwhile there are lots of principles that microservices architecture is based on that can also benefit Elixir/OTP solutions.

Of your post, to me, this immediately stood out. Because this kind of implies that at least on a conceptual level you currently have 3000 “beefy single boxes” processing your sources and yet:

An App per seems out of place,

… which kind of sounds like what you are doing right now. From my current research, still incomplete and ongoing - so I’m sure somebody will correct me if I’m wrong:

  • Node: An executing Erlang run-time system (ERTS) which can communicate with other Erlang run-time systems.
  • While a single hardware server (or virtualized OS) can run multiple Nodes, a Node itself cannot be distributed.
  • Given that a Node executes an ERTS and that a Release includes an ERTS it seems that a Node can at most run one Release (???)
  • While a Release can include multiple “applications” it seems to only support one primary application while the remainder are simply included applications. Therefore a Release seems to only support one supervision tree (???). So far my search for a release supporting multiple primary applications and supervision trees has come up empty.
  • The point being is that a Node is executing one single primary application and it’s supervision tree.
  • It would be tempting to equate (one Node == one microservice) - but that overlooks the level of isolation and decoupling that is possible within the same supervision tree.
  • While the Node itself can’t be distributed, the application executing within it can spawn processes on other Nodes - though design-wise I would prefer an explicit (primary) application spawning and managing processes on that second Node on behalf of the first Node.

Essentially it seems the current starting point of your “Windows service” (per source) is roughly equivalent to a single primary application on a dedicated Node.

Now I suspect it’s unlikely that cramming all 3000 sources into one single Node/Application is that great an idea either - given that a single Node doesn’t distribute - so it’s more likely you are looking for an application that can be deployed on to multiple distinct Nodes, each Node listening to any number of sources via configuration - which all forward their pre-processed/normalized results to yet another Node which executes the application that concentrates/aggregates the results.

minhajuddin

minhajuddin

We are building an API which talks to around 50 services which is not huge, but they are all different providers which understand SOAP, XML, JSON, Rest etc,. And, we have had good success with just putting them in different modules. I think just having 3000 genservers with supervisors may work out without a lot of complications. Like @NobbZ mentioned creating 3K nodes is definitely not a good idea. You will also have to tune your :hackney settings (if you are using HTTPoison) as it sets the max connection limit to a default value of 50.

I would personally do the simplest thing possible, by building out some common utilities which the 3000 services can use. And run them on a single node.

peerreynders

peerreynders

This is the potential misunderstanding I wanted to address in my first reply. As far as I can tell a single node runs exactly one “primary application” - the other included applications act as libraries to the primary application by becoming part of the primary application’s supervision tree.

It’s a peculiarity in the OTP naming convention - a library application does not implement the Application callbacks and therefore cannot be started or stopped (as an application). So for example Poison is an “OTP application” but it’s a library application for use by a primary application.

$ mix new app_name

creates a library application - not a primary application. For a primary application

$ mix new app_name --sup

is required - that will include the application callback to create the supervision tree.

An umbrella project still contains just a single primary application - one of the “applications” implements the application callbacks and starts the supervision tree while the other (included) “applications” simply become part of the first application’s supervision tree.

but the question would be how to stop, start, pause and upgrade each service individually. Each service seems it would be a GenServer, but maybe an application?

In this situation the term service could cause some confusion/ambiguity. Handling a single source may require a number of processes, possibly even a small library application that could be designed to have it’s processes handling the source started, paused, and stopped. Upgrades could be trickier. In general hot code reloading targets code at the module level - and in many use cases the recommendation is to avoid using/supporting it because it tends to make the overall design much more challenging.

The work does not have to be distributed, so all 3000 on a single node is no problem.

The issue is

  • it’s not exactly clear how severe a workload handling a single source is
  • how capable the physical/virtual CPU is that the node resides on.

While a node will spread all its processes over all the cores of the CPU - it can’t scale by utilizing additional CPU’s (short of spawning processes on other nodes that reside on a different CPU). So while the total number of processes shouldn’t be a problem for a single node, the actual workload of handling 3000 concurrent sources could potentially be too much work for the CPU the node is executing on. If that is the case the solution design will have to account for the eventuality of distributing the workload over separate nodes each executing on their own CPU.

Taking service oriented design principles into account it may make sense to avoid sharing a node configuration database across multiple nodes but instead have a separate “configuration node” which supplies the other nodes with their configuration information when they start up (which could also route start, pause and stop requests to the appropriate node).

Ultimately the design details are affected by the expected workload and expected capability of your deployment platform.

Where Next?

Popular in Questions Top

senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
pgiesin
This should be a simple problem but I just can’t seem to figure it out. I have a standalone Elixir app that won’t find the database. Dep...
New
logicmason
Hi there, I'm working through my first release with elixir/phoenix. I've built a release with distillery and found that it crashes when I...
New
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
beno
I will often find my self writing things similar to: case some_value do nil -> something() "" -> something() _ -> someth...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New

Other popular topics Top

vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
William
I would like to know that is there any online source for learning Phoenix Framework for building E-Commerce Store? Any advantage on build...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

We're in Beta

About us Mission Statement