2021-03-16

Apache Camel 3.9 - No more saw tooth JVM garbage collection

We continue our effort to optimize Apache Camel. This is blog post part 7 which covers are latest effort on dramatically reducing the object allocations caused by Camel while routing messages.

The good news is that we have overachieved and was able to reduce object allocations to ZERO!!! - so no more JVM memory usage graphs with saw tooth (note: in real world use-cases there will always be user data causing object allocations - but I wanted to have a click-bait blog title).

To help identify potential areas of improvement in the core Camel, we put together a small performance application, which has only a single route triggered by a timer every producing 1000 msg/sec. These messages are routed to 10 different log endpoints (logging turned off). This allows us to focus on the internals of Camel only and what code paths is executed and what objects are being allocated and in-use by the internal routing engine. There are no message data (body or headers), or network communication etc. 

Running the example (JVM heap size set to max 32mb) for 10 minutes profiled by JFR and browsed in JDK mission control we can see the dramatic difference. 

In Camel 3.8 597mb of objects is allocated by Camel in total.


And in Camel 3.9 that is ZERO.


How did we get to zero?

That is a long journey that started about a year ago, and we have gradually optimised Camel which I have blogged about in the 6 parts preceding this post.

All this work is like pealing an onion, layer after layer. As one layer has been optimised, then the profiler reveals another layer, and so on. This time we could identify 5 areas for improvements:

  • consumers
  • core EIP patterns
  • internal routing processor
  • error handler
  • exchange and message
The consumers are the source of incoming messages into Apache Camel. And so that is a great place to start. It's the consumers that allocate a new exchange, populate the exchange with message data such as body and headers. 

After that it's the internal routing engine that routes the exchange via EIP patterns. And here we identified several spots where we could eliminate object allocations, or reduce allocations when some features are not in use etc. Error handling is one of the most complex part in the core Camel, and it uses objects to keep state in case of exceptions to handle redeliveries and whatnot. We were able to split the error handling into two tasks that operate either as a simplified or complex task. In the core EIP patterns we were able to optimize code that reduces object allocations.

The 5th area we optimized is the exchange object. EIPs and the Camel routing engine store state per exchange on the exchange instance itself as exchange properties. That data is stored in a Map which means for each entry both a key is allocated in the java.util.Map. We optimized this to use an internal object array where each key is hardcoded as an index entry in the array. That means read/write is very fast and simple as its just an array index. 

And then we ..... cheated ... instead of allocating new objects (via new constructor) we recycle existing objects from the previous exchange to the next. In other words we are using a sort of object pooling - this feature is called exchange pooling in Camel.

Exchange Pooling

The diagram above with ZERO object allocation is in fact with exchange pooling enabled. If exchange pooling is turned off (default), then the diagram should have been as below:


As you can see there is saw-tooth graph. However the total object allocation is gone down from 597mb to 492mb (18% reduction).

Awesome this is fantastic. And yes indeed it is. However when using anything there are both pros and cons, and so with object pooling. There is tiny tiny overhead of Camel to manage the object pools, and to "scrub" objects before they can be recused. That is a possibly a very very tiny CPU overhead compared to the JVM allocate and initialise new objects; instead of pool reuse. The biggest con is object leaks .. if objects are no returned back in the pool. Therefore you can turn on statistics which will report a WARN if a leak is detected when you stop Camel. The objects must be manually returned back into the pool, which we have coded in all the Camel components, and of course in core Camel. Now object leaks in this situation is not severe as you just have a situation as if there are no pooling, the JVM will create a new object - so the object allocations goes up, but its not severe like a database pool leaking TCP network connections.

Upcoming work

There are a few very complex EIP patterns and Camel component which does not yet support object pooling. We have this on the roadmap for Camel 3.10.

Camel 3.9 is planned for release in March 2021.


2021-01-20

Apache Camel 3.8 and Java Flight Recorder

In the upcoming Apache Camel 3.8 release we have a new Camel component to integrate with Java Flight Recorder.

Camel is now capable of capturing "work steps" during startup that can be recorded with Java Flight Recorder. This can be used to better diagnose and find where your Camel applications may be slow to startup, for example due to a misbehaving component or custom user code.

The screenshot below shows a recording that has captured a Camel application that takes about 3 seconds to startup. Its a very tiny application so we expected it to be faster. 


If we sort the events by duration in the JDK mission control, we can see that there are 4 events that take over 2 seconds.

The sequence is a sequence of the following step (sub step):

Initializing context -> Initializing routes -> Creating route (route2) -> Creating Bean processor (bean1)

What we can see is that the step with the highest depth is "Creating Bean processor" which takes about 2 seconds. This is the culprit of the bottleneck.

If we check the Camel route for where bean1 is in use, its in route2 at:

        from("direct:slow")
            .to("log:slow?level=OFF")
            .bean(MyBean.class, "hello");

Here we can see the bean is using MyBean class, which we can then look at next:

    public MyBean() {
        // force slow startup
        try {
            LOG.warn("Forcing 2 sec delay to have slow startup");
            Thread.sleep(2000);
        } catch (Exception e) {
            // ignore
        }
    }

Ah okay here is the problem. The bean is sleeping for 2 seconds. Yes of course this is a made up example, but it does affect the recording and allow us to find it via the JDK mission control tool.

We also offer a logging recorder where you can "see" some of the same information as in JDK mission control. However when using JDK mission control, you have the entire JFR recording that also captures alot of JVM information about CPU and memory use and whatnot.

To use Java Flight Recorder with Camel, all you have to do is to add camel-jfr on the classpath. Then Camel will auto-detect this and enable it. You can configure the recorder with various options which will be documented as part of the common options.

But for quickly finding startup bottlenecks for Camel applications then the logging recorder is a good start. The screenshot below shows the logging output, and as you can see from the red square we have identified where the "2 second" problem is.


The logging recorder comes out of the box in camel-core, and you can just use it by configuring:

camel.main.startup-recorder = logging

If you are using Camel Main, Camel Quarkus etc. And for Spring Boot, you can enable it with

camel.springboot.startup-recorder = logging

You can also set a custom recorder, or one of the out of the box implementation via Java code:

camelContext.adapt(ExtendedCamelContext.class)
  .setStartupStepRecorder(...);


You can try this example (camel-example-flight-recorder) from the Camel Examples git repository. From command line you can run 

mvn camel:run

And Camel will automatic capture a JFR recording and save to disk. The output of the file is shown in the log, which you can then open from JDK mission control.  

2020-12-21

Apache Camel 3.7 (LTS) Released - The fastest Camel ever

The Apache Camel 3.7 was released some days ago.

This is a LTS release which means we will provide patch releases for one year. The next planned LTS release is 3.10 scheduled towards summer 2021.



So what's in this release

This release introduces a set of new features and noticeable improvements that we will cover in this blog post.


Pre compiled languages

We continued our avenue of making Camel faster and smaller. This time we focused on the built-in Simple scripting language.

First we added the jOOR language. jOOR is a small Java tool for performing runtime compilation of Java source code in-memory. It has some limitations but generally works well for small scripting code (requires Java 11 onwards).

Then we worked on compiled simple.


Compiled Simple

The csimple language is parsed into regular Java source code and compiled together with all the other source code, or compiled once during bootstrap via jOOR.

In a nutshell, compiled simple language excels over simple language when using dynamic Object-Graph Navigation Language (OGNL) method calls.

For example profiling the following simple expression

    <simple>${exchangeProperty.user.getName} != null && ${exchangeProperty.user.getAge} > 11</simple>

with the equivalent csimple expression:

    <csimple>${exchangeProperty.user} != null && 

             ${exchangeProperty.user.getName()} != null && 

             ${exchangeProperty.user.getAge()} > 11</csimple>

yields a dramatic 100 times performance improvement in reduced cpu usage as shown in the screenshot:


For more information about the compiled simple language and further break down of performance improvements then read my recent blog post introducing the csimple language.

We have provided two small examples that demonstrate csimple as pre compiled and as runtime compiled during bootstrap.

You can find these two examples from the official Apache Camel examples repository at:


Optimized core

We have continued the effort to optimize camel-core. This time a number of smaller improvements in various areas such as replacing regular expressions with regular Java code when regular expressions were overkill (regexp take up sizeable heap memory).

The direct component has been enhanced to avoid synchronisation when the producer calls the consumer.

We also enhanced the internals of the event notifier separating startup/stop events from routing events, gaining a small performance improvement during routing.

We also reduced the number of objects used during routing which reduced the memory usage.

Another significant win was to bulk together all the type converters from the core, into two classes (source generated). This avoids registering individually each type converter into the type converter registry which saves 20kb of heap memory.

If you are more curious about how we did these optimisations and with some performance numbers, then read another of my recent blog posts.


Optimized components startup

The camel core has been optimized in Camel 3 to be small, slim, and fast on startup. This benefits Camel Quarkus which can do built time optimizations that take advantage of the optimized camel core.

We have continued this effort in the Camel components where whenever possible initialization is moved ahead to an earlier phase during startup, that allows enhanced built time optimizations. As there are a lot of Camel components then this work will progress over the next couple of Camel releases.


Separating Model and EIP processors

In this release we untangled model, reifier and processors.

This is a great achievement which allows us to take this even further with design time vs runtime.

    Model    ->    Reifier   ->   Processor

    (startup)      (startup)      (runtime)

The model is the structure of the DSL which you can think of as _design time_ specifying your Camel routes. The model is executed once during startup and via the reifier (factory) the runtime EIP processors is created. After this work is done, the model is essentially not needed anymore.

By separating this into different JARs (camel-core-model, camel-core-reifier, camel-core-processor) then we ensure they are separated and this allows us to better do built time optimizations and dead code elimination via Quarkus and/or GraalVM.

This brings up to lightweight mode.


Lightweight mode

We started an experiment earlier with a lightweight mode. With the separation of the model from the processors, then we have a great step forward, which allowed us to make the lightweight mode available for end users to turn on.

In lightweight mode Camel removes all references to the model after startup which causes the JVM to be able to garbage collect all model objects and unload classes, freeing up memory.

After this it's no longer possible to dynamically add new Camel routes. The lightweight mode is intended for microservice/serverless architectures, with a closed world assumption.


Autowiring components

The Camel components is now capable of autowiring by type. For example the AWS SQS components can automatically lookup in the registry if there is a single instance of SqsClient, and then pre configure itself. 

We have marked up in the Camel documentation which component options supports this by showing Autowired in bold in the description.


Salesforce fixes

Our recent Camel committer Jeremy Ross did great work to improve and fix bugs in the camel-salesforce component. We expect more to come from him.


VertX Kafka Component

A new Kafka component has been developed that uses the Vert.X Kafka Java Client which allows us to use all of its features, and also its robustness and stability.

The camel-vertx-kafka component is intended to be (more) feature complete with the existing camel-kafka component. We will continue this work for the next couple of Camel releases.


DataSonnet

The new camel-datasonnet component, is to be used for data transformation using the DataSonnet.

DataSonnet is an open source JSON-centric, template-based data transformation standard built to rival proprietary options available in the market.


Spring Boot

We have upgraded to Spring Boot 2.4.


New components

This release has 7 new components, data formats or languages:

  • AtlasMap: Transforms the message using an [AtlasMap](https://www.atlasmap.io/) transformation
  • Kubernetes Custom Resources: Perform operations on Kubernetes Custom Resources and get notified on Deployment changes
  • Vert.X Kafka: Sent and receive messages to/from an Apache Kafka broker using vert.x Kafka client
  • JSON JSON-B: Marshal POJOs to JSON and back using JSON-B
  • CSimple: Evaluate a compile simple expression language
  • DataSonnet: To use DataSonnet scripts in Camel expressions or predicates
  • jOOR: Evaluate a jOOR (Java compiled once at runtime) expression language


Upgrading

Make sure to read the upgrade guide if you are upgrading to this release from a previous Camel version.


More details

The previous LTS release was Camel 3.4. We have blog posts for what's new in Camel 3.5 and Camel 3.6 you may want to read to cover all news between the two LTS releases.


Release Notes

You can find more information about this release in the release notes, with a list of JIRA tickets resolved in the release.



2020-12-09

Apache Camel 3.7 - Compiled Simple Language (Part 6)

I have previously blogged about the optimizations we are doing in the Apache Camel core. The first 3 blogs (part1, part2, part3) were a while back leading up to the 3.4 LTS release.

We have done more work (part4, part5) and this (part 6) that will be included in the next Camel 3.7 LTS release (to be released this month). 

This time we worked on a new variation of the Camel simple language, called csimple.


Compiled Simple (csimple)

The csimple language is parsed into regular Java source code and compiled together with all the other source code, or compiled once during bootstrap via the camel-csimple-joor module.

To better understand why we created csimple then you can read on about the difference between simple and csimple (in the section further below). But first let me show you some numbers.

I profiled a Camel application that processes 1 million messages, which are triggered in-memory via a timer, and calls a bean to select a random User object that contains user information. The message is then multicasted and processed concurrently by 10 threads, which does some content based routing based on information on the User object.

The Camel route is from a Spring XML file, and then a few Java beans to represent the User object and the bean to select a random user.

The application is profiled running with simple and csimple language until all messages has been processed.

The main focus is the difference between the following simple and csimple expression (XML DSL)

<simple>${exchangeProperty.user.getName} != null &&
        ${exchangeProperty.user.getAge} > 11
</simple>

<csimple>${exchangeProperty.user} != null &&      
         ${exchangeProperty.user.getName()} != null &&
         ${exchangeProperty.user.getAge()} > 11
</csimple>

At first glance they may look identical, but the csimple language has an additional not null check whether the user object exists or not. You may think that the csimple language contains type information but it does actually not. We have "cheated" by using an alias (a feature in csimple) which can be configured in camel-csimple.properties file as shown:

# import our user so csimple language can use the shorthand classname
import org.example.User;

# alias to make it shorter to type this
exchangeProperty.user = exchangePropertyAs('user', User.class)

Here we can see the alias is referring to the exchangePropertyAs function that takes the property name as first input, and then the class name as 2nd input. And because we have a Java import statement in the top of the properties file, we can type the local classname User.class instead of org.example.User.

The csimple script gets parsed into the following Java source code, which is then compiled by the regular Java compiler together with the rest of the application source code:

    @Override

    public Object evaluate(CamelContext context, Exchange exchange, Message message, Object body) throws Exception {

        return isNotEqualTo(exchange, exchangePropertyAs(exchange, "user", User.class), null) && isNotEqualTo(exchange, exchangePropertyAs(exchange, "user", User.class).getName(), null) && isGreaterThan(exchange, exchangePropertyAs(exchange, "user", User.class).getAge(), 11);

    }

Performance numbers

Okay lets get back to the performance numbers. The raw data is presented below as screenshot and table.




CPU usage

simple            814815 millis
csimple             7854 millis


Memory usage

simple               123 objects         5328 bytes
bean                3171 objects       177680 bytes

csimple               3 objects           792 bytes


As we can see the cpu usages is dramatically reduced by a factor of 100 (one hundred).

The memory usage is also reduced. The simple language uses OGNL expression with the bean language and hence we should calculate the combined usage which then is roughly 3294 objects taking up about 183kb of heap memory. (the bean language has introspection cache and other things). The csimple language is very very tiny with just 3 objects taking up 792 bytes of heap memory. The memory usage is dramatically reduced by a factor of 231

The memory screenshot includes simple language for both runs, the reason is that there are some basic simple expressions in the route which was not changed to csimple. Only the script that performed the most complex expression with OGNL on the User object.

So all together is a very dramatic reduction in both cpu and memory. How can this be?

Very low footprint, why?

The low footprint is because of mainly two reasons

1)
The script is compiled as Java code by the Java compiler either at build time or during bootstrap.

2)
The script is not using bean language / bean introspection with reflection for OGNL paths. However this requires the script to include type information so the Java compiler knows the types to compile the OGNL paths as regular java method calls. This is the main driver of the reduced footprint on both memory and cpu. Basic scripts such as ${header.zipCode} != null would have similar footprint. However csimple with pre compiled would have lower footprint as the script is pre parsed which otherwise would have to happen during bootstrap to generate the Java source code for the Java compiler to do an in-memory compilation; which will impact the startup performance.

Are they any limitations?

Yes the csimple language is not a 100% replacement for simple (we will continue to improve the feature parity). In the Camel 3.7 release csimple is in preview mode and have the following limitations

- nested functions is currently not supported
- null safe operator is not supported

And for OGNL paths, then as previously mentioned, then csimple requires to be type safe, by including the types of the objects.

Difference between simple and csimple

The simple language is a dynamic expression language which is runtime parsed into a set of Camel Expressions or Predicates.

The csimple language is parsed into regular Java source code and compiled together with all the other source code, or compiled once during bootstrap via the camel-csimple-joor module.

The simple langauge is generally very lightweight and fast, however for some use-cases with dynamic method calls via OGNL paths, then the simple language does runtime introspection and reflection calls. This has an overhead on performance, and was one of the reasons why csimple was created.

The csimple language requires to be typesafe and method calls via OGNL paths requires to know the type during parsing. This means for csimple languages expressions you would need to provide the class type in the script, where as simple introspects this at runtime.

In other words the simple language is using duck typing (if it looks like a duck, and quacks like a duck, then it is a duck) and csimple is using Java type (typesafety). If there is a type error then simple will report this at runtime, and with csimple there will be a Java compilation error.


Any examples for me to try?

We have provide two small examples that demonstrate csimple as pre compiled and as runtime compiled during bootstrap. You can find these two examples from the official Apache Camel examples repository at:


Whats Next

We want to implement the missing feature for nested functions and the null safe operator. We are also working on camel-quarkus to make csimple optimized for Quarkus and GraalVM. This effort is already started and Camel 3.7 will come with the first work in this area.

We also want to work on speeding up the runtime compilation to be able to do batch compilation. Currently each csimple script is compiled sequentially. 

And we want to take a look at if we can make runtime compilation work better with Spring Boot in its tar jar mode.

However at first enjoy csimple in the upcoming Camel 3.7 LTS release and as always we want your feedback and love contributions.


2020-11-04

Apache Camel 3.7 - More camel-core optimizations coming (Part 5)

I have previously blogged about the optimziations we are doing in the Apache Camel core. The first 3 blogs (part1, part2, part3) were a while back leading up to the 3.4 LTS release.

Now we have done some more work (part4) and this part 5 that is coming up in the next 3.7 LTS release. 

This time we have mainly been focusing on reducing the footprint of Camel after bootstrapping. 

separating design time model vs runtime processors

We have continued the modularisation and have separated the design time vs runtime parts in the core. 

The route model is now in camel-core-model, and the runtime EIP implementations are in camel-core-processor. And in between we have camel-core-reifier which transforms the model into processors. This separation plays a significant part of continued effort of making Camel even smaller, such as Quarkus and GraalVM runtimes. This will essentially allow us to pre build from the route model the runtime processors, and then at runtime not even include the model and reifier classes. There is still some work to get to the finish line, but Camel 3.7 is a major step forward.

Lightweight mode

For runtimes that are not Quarkus or GraalVM based, then we have a new lightweight switch in camel-main / CamelContext that can be turned on, that does some internal optimizations by null'ing the model and reifiers from the runtime processors, which then aids the JVM garbage collector, so it can reduce memory. 

The following two screenshots shows the camel-example-main-tiny running with Camel 3.4.4 and 3.7.0 (lightweight mode).



The top screenshots shows that there are 731 Camel objects and that they take up about 58kb of memory in the heap. Now in Camel 3.7 this has been significant improved as there are only 340 objects and they take up 25kb. This means Camel has shrunk to half the size in memory footprint. The memory referred to here is the memory used by Camel to bootstrap and start itself. 

Improved type converter

The type converter system have been optimized to be quicker for common convertions, and as well reducing the number of method calls during a conversion. The core converters are now bulked together and source code generated into 1 class per maven artefacts (2 in total for core). Before this there was about 10 different converter classes. This helps reduce the footprint of the type converter registry significantly. Now all the core converters are referenced by just 2 classes. Before each converter method was registered via a double keyed map which leads to a map containing 200 converters (takes up 20kb of heap memory). And on top of that then each converter method is invoked via lambda call that the JVM compiles into a separate class (so there was also 100 extra classes loaded). So all together this greatly reduces the footprint by lowering the heap memory usage by 20kb and 100 less classes loaded. And if you are using any of the XML components then camel-core-jaxb/camel-core-jaxp comes with 100 converters as well, which means if they were loaded then that would double the memory footprint. And the type converter is now also faster as the lookup of the converter is quicker and uses just Java primitives, where as before new objects was created as keys to lookup in the internal registry map. So there is also less garbage for GC.

The following screenshot shows the same example as before with Camel 3.7.0 (no lightweight mode):


Here we can see that there are 346 objects and they take up 28kb of memory. That is only +6 objects and +3kb more memory. So the lightweight mode did not in this example reduce as much memory. That is because there is only 1 route and 4 different models in use. If the application has more routes and more EIP models and reifiers, then more memory would be reduced. 

The cost is however that the application cannot dynamically add new routes (via the model). The lightweight mode should be used with caution and it is only for "static Camel applications".

Optimized direct component

The direct component has been optimized to wire up its producers and consumers during startup. Before the direct producer would for each message then lookup its associated consumer before sending the message to the consumer. We have moved this to an earlier phase. And then in case a consumer is stopped / removed / or re-created etc then the producer is capable of invalidating its old wiring and re-associate to the new consumer. This improved avoids internal lookup and thread synchronisation.

Optimized event notifier

Another performance improvement was to divide event notifiers into lifecycle vs messaging events. As lifecycle events such as staritng and stopping Camel does not affect what happens during routing messages. We use this knowledge to avoid checking for runtime events, in the routing engine, if there isn't any custom lifecycle added to Camel.

Parsing without regular expressions

Some parts in Camel uses regular expressions to parse, such as a string to long time converter, so you could convert 10m30s (as 10 minutes and 30 seconds). This was using several regular expressions. However when they get compiled by JVM they objects in memory are rather large. There are other places internal in Camel that regular expression was used for more basic parsing. So we rewrote those using plain basic Java and this reduced the memory and has better performance. As using regular expression in the first place was a bit overkill. 

BootstrapCloseable

Naming in IT is hard, so we named a new marker interface BootstrapCloseable, after java.io.Closeable. We use this to mark services and other entities in Camel as something that are only used during bootstrapping Camel. Then after Camel has been started, we will then invoke those so they can free up resources and memory. 

More to come

There will be more to come for Camel 3.7. But at this point we wanted to share the good news that Camel is on a diet and have shrunk to half the size for its memory footprint (startup). 

We have plans to look at a compiled simple language which would greatly improve performance for some simple expressions that are dynamic and use method calls. One area of inspiration is the new camel-joor language which gets compiled to Java during bootstrap. 



2020-10-09

Apache Camel 3.6 - More camel-core optimizations coming (Part 4)

I have previously blogged about the optimziations we are doing in the Apache Camel core. The first 3 blogs (part1, part2, part3) were a while back leading up to the 3.4 LTS release.

Now we have done some more work that is coming in Camel 3.6 leading up to the next 3.7 LTS release.

To speedup startup we switched to a new uuid generator. The old (classic) generator was inherited from Apache ActiveMQ which needed to ensure its ids were unique in a network of brokers, and therefore to ensure this the generator was using the hostname as prefix in the id. This required on startup to do a network access to obtain this information which costs a little time. Also depending on networks this can be more restrictive and delay the startup. The new generator is a pure in-memory fast generator that was used by Camel K and Camel Quarkus.

We also identified a few other spots during route initialization. For example one small change was to avoid doing some regular expression masking on route endpoints which wasn't necessary anymore.

Now the bigger improvements are in the following areas

Avoid throwing exceptions

We identified on spring runtimes that Camel would query the spring bean registry for known beans by id, which the Spring framework would throw a NoSuchBeanDefinitionException if the bean is not present. As Camel does a bit of optional bean discovery during bootstrap, we found a way to avoid this which prevents this.

Singleton languages

Another related problem is that in Camel 3 due to the modularization then some of the languages (bean, simple, and others) have been changed from being a singleton to prototype scoped. This is in fact one of the biggest problems and we had a Camel user report a problem with thread contention in a high concurrent use-case would race for resolving languages (they are prototype scoped). So you would have this problem, and because the language resolver would query the registry first then Spring would throw that no such bean exception, and then Camel would resolve the language via its own classpath resolver. So all together this cost performance. We can see this in the screenshots from the profiler in the following.




The top screenshot is using Camel 3.5 and the bottom 3.6. In the top we can see the threads are blocked in Camels resolveLanguage method. And in 3.6 then its actually the log4j logger that is blocking for writing to the log file. Both applications are using the same Camel application and have been running for about 8 minutes.

Reduce object allocations

The next screenshots are showing a sample of the object allocations.



With Camel 3.5 we are average about 1000 obj/sec and with 3.6 we are down to about a 1/3th.

One of the improvements to help reduce the object allocations was how parameters to languages was changed from using a Map to a plain object array. The Map takes up more memory and object allocations than a single fixed object array. 

Do as much init as possible

Another performance improvement that aids during runtime was that we moved as much we could from the evaluation to the initialization phase in the Camel languages (simple, bean, etc.). We did this by introducing the init phase and ensuring CamelContext was carried around in the interns so we can use the context during the init phase, where its really needed. This ensures the runtime evaluation is as fast as possible.

Other smaller optimizations

We also improved the simple language to be a bit smarter in its binary operators (such as header.foo > 100). Now the simple language has stronger types for numeric and boolean types during its parsing, which allows us to know better from the right and left hand side of the binary operator to do type coercion so the types are comparable by the JVM. Before we may end up with falling back to converting to string types on both sides. And there is more to come, I have some ideas how to work on a compiled simple language.

The screenshots below shows a chart with the CPU, object allocations and thrown exceptions.



As we can see this summarise what was mentioned was done to optimize. The number of exceptions has been reduced to 0 at runtime. There is about 3500 thrown during bootstrap (that is Java JAXB which is used for loading the spring XML file with the Camel routes used for the sample application). We do have a fast XML loader in Camel that is not using JAXB.

Another improvement we did was to build a source code generator for a new UriFactory which allows each component to quickly build dynamic endpoint URIs from a Map of parameters. The previous solution was to use RuntimeCamelCatalog that was more generic and required loading component metadata from json descriptor files. A few components use this to optimize the toD (such as http components). By this change we avoid the runtime catalog as dependency (reduce JAR size) and the source code generated uri factory is much faster (its speedy plain Java). However the sample application used for this blog did not use toD nor the UriFactory.

Apache Camel 3.6 is scheduled for release later this month of October. It's going to be the fastest Camel ever ;)


2020-07-27

BarcelonaJUG talk on Tuesday July 28th about Camel 3 in the era of Kubernetes and Serverless

Just back from PTO and myself and Andrea Cosentino are on the spot tomorrow where we have been invited by Barcelona JUG to give a talk about the Camel 3 and its latest innovation around Kubernetes and Serverless (and Kafka).


It's a free and online event, in English, tomorrow Tuesday 28th at 19.00 CEST
https://www.meetup.com/es-ES/BarcelonaJUG/events/271746564/

The session runs for 45 min with Q and A at the end.

PS: Just updated my platform to use latest Camel K 1.1.0 release, so hope the demo gods are on our side tomorrow.