I used to work at Pyra, so maybe some of you know me from my six years at Pyra company.
Now I work at Omnifish, where we, with our co-founders and employees, were a support
glassfish server, so back to the roots, kind of. But this time I'd like to talk about Java,
plain Java and Jakarta E, and how it all fits together when we combine that with AWS.
So first, before I talked about AWS, let's ask, why do we want to have Java fast, or
do we want to have Java start fast? I think everybody wants that, but why? Because it's
cool, or because we need it. So there were times when we really didn't need that, when
we had the application servers, it was a pain that it took a while to start, but in production
it was already running, so there was no real business need for that, only to make developers
happy and be more productive with developing codes. But now we have several use cases where
it's really needed, because the more time it takes for Java program to start, it costs
more money, and it's not user-friendly. And one example, a perfect example of this is
AWS Lambda. So now, what is AWS Lambda? It's basically a service to which you can deploy
your code, and this service runs your code only when it's needed, and it also charges
you, because we need to pay for the cloud environment. But if we run the code in Lambda,
we are charged only for the time when the code is running. And that's pretty nice, especially
if we have code that usually just sits there and responds to users just once in a while,
or only during certain periods of time, especially during the day or in the morning when there
is some business activity. So how does AWS Lambda do that? It basically creates environment
and deploys our code when it needs to be executed. And for that, if the code is not already deployed,
it needs to create the runtime and initialize our so-called function, because this is how
our code is called. It's called a function because it's basically just called by the
runtime, it gives some result, and then it's thrown away. In reality, it's not always thrown
away because AWS Lambda tries to cache our code so that it doesn't have to re-initialize
it every time when it's run more frequently. So sometimes it stays there, and then AWS
Lambda can skip the initialization phase. This is called warm start, because the code
is already prepared to serve things. But if this doesn't happen, and the code is not available,
it has to initialize everything. And this is usually referred to as cold start, just
start from scratch. So the whole lifecycle of AWS Lambda is as on the slide, you can
see there's init phase. This is only when the code or the function is not initialized.
So in case of cold start, then there is a warm phase, which happens even for warm start-ups.
There is this invoke phase, which actually is the only productive phase from these three.
It actually does some job. The first phase only initialized gets some things ready before
the application can process requests. Then the invoke phase does the job. And then when
AWS Lambda service decides that it doesn't no longer needs our application running, because
it's not doing anything right now, and they need AWS wants to use resources in some other
way, it will tear everything away. So it will shut down the environment. And then we'll
add square at square zero. And next invocation needs to go through the initial initialization
phase. So let's not go back to the roots with plain Java application. And let's see or let's
think about how fast we can get with Java on AWS Lambda. Can we start Java really fast?
I tried to start a very simple Java program on my local machine. And if you do that too
on your computers, you will see that Java really starts fast. In my case, it was 50
milliseconds, 0.05 seconds. So very small fraction of a second, where JVM started, printed
something on output and finished. So we see on a local computer, plain Java doesn't start,
doesn't take very long to start. If we compare the exactly what's going on in the AWS Lambda,
because AWS Lambda needs to initialize the environment and only then it can run Java
function. It takes a bit longer in reality. But when we compare it to other languages,
I haven't done this. This is done by some other guy who is more experienced with AWS
Lambda than me and compared performance in a more sophisticated way than just running
on the computer or just several measurements. He did a lot of measurements across all the
or various different languages, various different runtimes provided by AWS Lambda. And he found
out that Java basically is on the same level as JavaScript, Python and a lot of other languages
that there's not much difference. There's a small difference that at that time C sharp
was a bit slower. But as AWS improves continually, the AWS Lambda, even these numbers would
be probably better now. And C sharp and Docker will be maybe more even with the with the rest
because the technology running AWS Lambda is continuously improving. But this is just
to compare and show that Java itself or even the implementation of Java AWS function or
the environment isn't worse than other languages. So now what is the problem actually? Why a
lot of people perceive that Java starts very slow. The problem is how I see it is that
many people don't think about Java in this simple way that it's a simple application.
A lot of people think about Java as a language that runs enterprise applications. And with
enterprise applications, we're used to use frameworks that do a lot of job for us. We
run the applications on application servers, which are start to which are slow to start.
And this is what we think about when we think when we say Java or when we talk about Java.
So now we're coming to that. That thing that if we basically can run our applications that
are similar to what we were used to before, but if we can start them fast, we could solve
a problem with Java call starts as least as we use Java now. So the question I have now
is Jakarta EE or some other frameworks like Springboard or something like that. Can that
be as fast as plain Java? Can we run that in AWS Lambda to get good performance and
fast startup? And the answer is there are such frameworks and solutions to that. There
are several ones. I don't have much time to talk about all of them. So I picked one that
I personally like. And it's called Piranha Cloud Framework. And this one is based on
entirely Jakarta EE APIs. Previously, it was called Java EE. So it's a very well-known
API that a lot of people already know, a lot of tools out there already use. So it's interoperable
with existing codebase. But the thing with Piranha Cloud is that the implementation actually
the engine of the framework is new, very flexible, and allows our application to stop, start
very fast. Piranha Cloud is based on a lot of existing components. A lot of them come
from the Glassfish server, which actually sort of proves that the server is not a problem
or Jakarta EE is not a problem. The components are there, they are quite fast. But the problem
how they are assembled in traditional Jakarta EE servers, Java EE application servers,
that is the problem. Because an application server usually has a lot of other things that
we don't need in Lambda, like monitoring a lot of vendor features and go on an administration
console and a lot of other things. So here is an example, it's basically nothing
else than a servlet. But this is an application using already some Jakarta EE APIs. And this
application, this servlet, you can run on any Jakarta EE server. You can run it on Tomcat,
you can run it on Glassfish, you can run it on anything that supports servlets. So the
only difference if we run it with Piranha Cloud is that it starts fast and it uses Piranha's
own servlet container, which was designed from scratch. And it's very flexible and fast.
What is also nice about Piranha's servlet container is that it can be embedded very easily.
And that's the crucial point. When we want to use Jakarta EE in Lambda, we need to basically
shave off everything that we don't need. And in AWS Lambda, we don't even need an HTTP
listener. Because AWS Lambda basically only wants a method from us that will be called,
returns some response. And then AWS Lambda is responsible for mapping the HTTP request
to an object that it passes to our method. And then the returned object should be mapped
to an HTTP response. And not only HTTP requests and responses, but Lambda can handle any type
of basically JSON messages, JSON events. So the only thing that our application needs
is to parse some input object and return some output event. And with Piranha, we can create
an engine and map our servlet onto it and just listen on some object. This object is
usually called or the request response cycle is invoked by a service method, which accepts
a request object and returns the response object. And this is exactly how we can use
it in AWS Lambda. We just need to add one additional layer to map AWS request object
to Piranha request object and back. If we run Piranha Cloud, this simple servlet, which
is comparable to our plain Java, we were running before. If you remember with plain Java on
my computer, I had startup times. Actually, it was not only startup times, but until the
program ended and printed some message and finished, it was around 50 milliseconds. With
Piranha, it's a bit longer time. But this already includes the first request. So it's
very similar to the plain Java application. It's not only that the engine starts, but
it actually serves the request response with text message through HTTP stack. And with
that, it takes still comparable time around 130 milliseconds. Now we can compare how it
works in AWS Lambda. And in AWS Lambda, I have a picture, but I hope I will be able to
show you in a minute. As I said before, it takes a bit longer when we start the other
function first time. Because this doesn't really matter if we run Java or any other
runtime. AWS Lambda first needs to create some environment to execute our code in. And
it takes a little bit of time. But together with creating this environment and running
our code, our example Piranha function, it takes under one second to serve the request.
Even if nothing was ready before, even on the first time we tried to run the function,
it still serves the response under one second. If we tried it again, again, again, then the
response times are much faster. This is on the right side here. It's under two milliseconds.
Because this is only the code that needs to serve the request. Everything was initialized.
Environment was initialized. The Piranha engine was initialized. It's cached in a static variable.
So it's part of the process that is already live. AWS just executes a method basically
on the Piranha engine that goes through the servlet and creates the server response. And
that's it. That's why it takes only two milliseconds. This is only the time required to serve the
actual response. So I'll try. I think I have a link here. How it works.
Okay. So this is the actual AWS console where I already deployed the application, the function.
And AWS console has a nice feature called tester or test button. With that, we can directly
invoke the Lambda. Normally, we would have to create an API gateway and map it to Lambda
so that we can access Lambda via HTTP from outside. AWS can also generate some URL that
we can use to invoke the Lambda. But this is like directly execute the Lambda without actually
invoking an HTTP request. So with this, yeah, there is some examples, but the application
doesn't read anything from the request. It just responds with some hello world message.
And if we execute it, you see it takes a bit of a time. And this is what I had in my slide.
Here it's even shorter, 850 milliseconds. But if we try it again, it's already pre-warmed
because AWS caches. Where is it here? Caches the environment. And now it's just two milliseconds.
So now the question is when the cold starts happen. They happen. I don't have any experience.
How much they have an impact. I heard that it's not much of an impact because they happen
normally once in a while. So the response is once in a while, takes one more second on
top of request processing. But if it takes five seconds, which can happen with normal
spring boot application or traditional frameworks or even application service, I don't know,
sometimes some application service can be embedded, then you can run them in AWS Lambda.
But some of them really are hard to basically map to the method call. So you have to install
application server. And for that, it's not even possible application to run application
servers in Lambda. But if you did, it would take 10, 20 seconds with some application
servers. And that's really a difference. You pay for the execution time, but you also
have exposure users to waiting for a couple of seconds. If it's a user facing Lambda.
If it's not, you maybe don't care so much. If it's something that's some bad job that
takes two, three minutes to finish, then couple of seconds don't really matter.
So here's a slide about Piranha Cloud. In short, Piranha Cloud is basically, as I said,
based on a new servlet container designed from scratch, and a lot of components built
on top of it. The servlet container being servlet implementation can run any servlet
out there. And a lot of Jakarta technologies are created as servlets. So for example, Jersey
as a servlet can be deployed on Piranha. And that's quite an easy way how to get rest endpoints
or rest library on Piranha to deploy Jersey as a servlet. And then we have everything
that Jersey provides. We can embed Piranha as I did in my demo, but we can also build
a war application and run the war application with Piranha on command line. This is using
Jakarta distributions, which already contain this distribution of packages, distribution
of functionality of Piranha that are mostly used. And the last thing, it's plain Java.
There's no real magic. There's no generated code. Everything is just clean code written
by clever people, I think. At least judging on the code, when I looked at the code, it
looks like the people were very clever. So with Piranha Cloud, we were able to achieve
quite fast startup times, but it still takes a couple of milliseconds, 100, 200. It depends
on how our application is complex. It may end up to two seconds even if we add all the
Jakarta functionality that Piranha Cloud provides. If we want to reduce that even further,
we have some general Java options to do that. We can first increase the CPU and RAM on the
Lambda, which we can always do with any language. But we can also use a faster JVM. On the last
slide, I have a table where I compared running the same application with Java 11 and Java
17. If you look at the numbers, Java 17 is mostly most of the time a bit faster. So just
by deciding which Java version we use, we can get a bit better startup time.
Then the last option here is basically a combination. I did some experiments which options work
well regarding to startup time or reducing the startup time. And in the end, not many
things matter. But what matters is class data sharing, which basically caches class information.
So it doesn't have to be loaded and processed in the beginning. It's already pre-computed
before cold start. And tinkering with compiler, we can disable second level just-in-time compiler
if we want to really focus on startup time.
And then there are other more magical options, but they can even reduce performance or reduce
startup time almost to zero, either compiling the code to Gravium, with Gravium to a native
binary which runs the application almost instantly. Or we can use Crack, which is a
co-ordinated restore and checkpoint mechanism. The next talk will be about it also. And yeah,
which is also nice is that AWS Lambda integrated that basically in one of their Java run times.
And it's called snap start. So you can get it for free, but only with Java 11. But hopefully
Java 17 support will be coming soon. And this works in a way that your application
basically stores, or you at the build time can store a checkpoint of your application
with all the memory or all the information basically like hibernates, you can hibernate
your application. And then it started again and again and cold start and warm start in
that case basically don't make a difference because they start from the same point.
That's all from me. If you have any questions, let me know. Thank you for watching.