Scaling Constraints of Languages
First published at Tuesday, 2 August 2016
Warning: This blog post is more then 7 years old – read and use with care.
Scaling Constraints of Languages
Micro-Services or any set of small services are common again right now. While it can make a lot of sense to use a dedicated service for a well defined problem those services are sometimes used just to play with a different server software. While it is pretty obvious for most that selecting the right database is important the same is true for selecting the right language (virtual machine) for the job.
There are different types of services or server applications where different types of virtual machines (executing the opcodes / bytecode of the compiled source code) make more or less sense. What are the criteria we should base such a decision on and which language should we choose when?
When coming from a PHP background there are a couple of technology stacks you come across regularly:
PHP (Connected to your webserver through FPM or mod_php with Apache)
Go (different server paradigms possible, most is also true for stuff like Elixir/OTP or Erlang/OTP)
Java (doing the heavy crunching)
Why did we start using PHP or how did it get so popular after all? One reason is LCoDC$SS as described in Roy T. Fieldings dissertation[#]_. The abbreviations which describes the network architectural properties of HTTP / REST stands for:
We are using a protocol which allows layering. You can put a reverse caching proxy or a load balancer in front of your application servers.
Code on Demand (CoD)
The Browser (Client) interacts with a Server.
The result of certain HTTP verbs (
HEAD) can be cached. This speeds up the web especially for static resources (images, …).
The client always transmits the full state which is required to generate the response by the server. This might include session cookies. If your application is implemented correctly any of your frontend servers (serving the same application) can answer any request – no matter if the same client was connected to a different server in an earlier request.
PHP was built for this. With it's so called shared-nothing architecture it handles stateless HTTP requests perfectly. The PHP engine throws away any state (variables, file pointers, database connections, …) after returning the response. It is almost impossible to build shared state between multiple requests (it is, of course, but don't do this).
This way PHP does not only use every CPU core on a server but usually it is even trivial to add additional servers and just scale your frontend this way. Only works if you respect the statelessness of HTTP requests, though.
So, Why Not PHP?
We just learned that PHP seems perfect for the web – does it serve all use cases perfectly? No.
Besides HTTP browsers also speak other protocols like WebSocket. With WebSockets you do not have the HTTP request response cycle but bi-directional permanent connections. PHP is not built for this.
Let's start with a use case where WebSockets are commonly used: A chat. You want that a message written by some user is immediately passed on to every other user in the same chat. This requires state on the server (who is in the chat) as well as passing messages between multiple connections. The incoming message should be passed on to all connected users.
You can implement this with HTTP (long polling), PHP and some software managing the shared state (database, in-memory storage) but it will be complicated and doesn't make much sense. This is where technologies like Node.js come into play.
With Node.js it is really simple to develop your own server software. Using HTTP, Websockets or anything else is pretty straight forward and you probably already know the language from some frontend development. Using asynchronous IO Node.js allows to do some work while waiting for the network or file system. This allows to answer many requests per second while sharing some state – like for a chat.
On the other hand does Node.js not really support multiple cores in a sensible way. You can fork multiple processes or start multiple Node.js servers but you are loosing the benefit of shared state immediately. And managing this state will get hard and cumbersome again.
What I like to use Node.js for is quickly developing simple servers. For services which require state and can easily be handled by a single CPU core, Node.js might even be ready to serve production ready services.
Developed by Google Go as a language is not as easy to pick up as Node.js when you come from a pure web development background. This is even worse for languages like Elixir or Erlang.
On the other it is fairly easy to write servers with those languages and they even utilize multiple CPU cores and still maintain shared state. If you need to scale a WebSocket-based application you might want to take a look at those.
When a single server is not large enough any more you'll have to come up with intelligent ideas to distribute your service - but this is far beyond "micro" then.
Java is commonly used for complex backend services or to integrate with existing (legacy) stacks in companies. The Java VM actually allows to build both fast applications embracing the shared nothing nature of HTTP, but also servers with a shared state. Since state is so easy to share, Java applications are often not scalable beyond a single server. The Java VM on the other hand scales really well on a single server.
Choosing the language (virtual machine) is not just a matter of taste. Because of different paradigms the languages / virtual machines are build with the decision has architectural consequences. There are also other factors like developer experience, license costs or similar when it comes to choosing the right technology for your team & application.
- Architectural Styles and the Design of Network-based Software Architectures by Roy T. Fielding (2000)