Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refine Spring profile support with AOT/native #29844

Open
sdeleuze opened this issue Jan 18, 2023 · 9 comments
Open

Refine Spring profile support with AOT/native #29844

sdeleuze opened this issue Jan 18, 2023 · 9 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) theme: aot An issue related to Ahead-of-time processing type: enhancement A general enhancement

Comments

@sdeleuze
Copy link
Contributor

sdeleuze commented Jan 18, 2023

Developers of Spring applications on the JVM are typically accustomed to being able to do advanced configuration at runtime with mechanisms like Spring profiles. Ahead-Of-Time transformations and GraalVM native image by design require one to define at build time the various beans. What can and cannot change at runtime is usually not well understood.

This issue is an attempt to clarify the current state of affairs (could materialize as a documentation update in 6.0.x), discuss what could be improved, and what will likely not be supported. What is discussed here will likely require related Spring Boot issue(s) since changes are likely to be required on both Spring Framework and Spring Boot projects, but let's start the discussion here.

I have created this https://github.com/sdeleuze/demo-profile-aot repository to illustrate the behavior of a generated Spring Boot native executable. It is possible at runtime:

  • To enable the Spring Boot debug flag with demo-profile-aot --debug
  • To customize the log level with demo-profile-aot -Dlogging.level.org.springframework.beans.factory.support.DefaultListableBeanFactory=debug
  • To enable a profile that does not impact the beans created with demo-profile-aot -Dspring.profiles.active=local
  • You can also customize a configuration property value specifying directly the property value with demo-profile-aot -Dsample.message="Customized message from the command line"
    What you can't do is enabling at runtime a profile that involves changing the bean configuration (for example adding a new bean specific to a profile). See the repository above for more details.

While this limitation can be initially frustrating, I think it makes sense if we take a step back on what the purpose of AOT transformation is and the goal of native application to ship only what is necessary in the native executable. It is important to have in mind that it is recommended to create the native executable (from sources or from the Spring Boot executable JAR) as late as possible in the deployment process, as close as possible to the target server when it will be deployed, usually as a container. In that sense, the generated native executable is not the strict equivalent of the executable JAR: it is CPU architecture specific and has a predefined set of beans. Removing this limitation is technically possible but would involve a huge refactoring of how conditions are evaluated AOT and would probably end up with increased footprint, build time, and executable size since more code would be shipped. I tend to think our time is better spent on other topics.

That said, I am in favor of adding the capability for people to specify the profiles to use with AOT/Native at build time. Once supported on Spring Framework AOT engine, this could translate on Spring Boot side to adding the capability to specify, for example (the way it is specified would be decided by the Spring Boot team):

  • With Gradle: gradle nativeCompile -Dspring.profiles.active=prod
  • With Maven: mvn -Pnative native:compile -Dspring.profiles.active=prod

This would allow developers to deal for example with different databases (H2 versus PostgreSQL for example) using distinct profiles, with the difference that here this needs to specified Ahead-Of-Time.

I am open to move forward on this proposal in the Spring Framework 6.0.x or 6.1.0 time frame, depending on the feedback from the Spring Boot team and the community.

@sdeleuze sdeleuze added in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement theme: aot An issue related to Ahead-of-time processing labels Jan 18, 2023
@sdeleuze sdeleuze added this to the 6.1.0-M1 milestone Jan 18, 2023
@sdeleuze sdeleuze self-assigned this Jan 18, 2023
@bclozel
Copy link
Member

bclozel commented Jan 18, 2023

I'm also in favor of making it easier to activate a profile for the AOT processing phase. We should discuss how this would translate for the generated code, I guess we should generate a Java statement that adds that profile automatically when the AOT sources are executed otherwise we could end up in an inconsistent state if the profile is somehow not activated at runtime.

Back to the broader topic of @Profile support in AOT/native apps. Our current AOT infrastructure relies on BeanDefinition already parsed and processed and we "write out" those definitions as functional bean registrations. @Profile is implemented as a Condition and this means we need to support all conditions as a result. For example a @ConditionalOnMissingBean condition needs to adapt to the fact that a @Profile annotated configuration class could contribute such a bean, etc.

If we wanted to have dynamic @Profile support at runtime, we would need to:

  • execute some Condition instances during the AOT phase (like the classpath detection ones)
  • turn other Condition into conditional statements in the AOT generated code.

This feature is not trivial and would require important changes in the AOT and the core container itself. I think this would require #29553 to work.

In general, I think the build configuration flag is a nice first step and I'm still not convinced that we need full profile support; supporting it would mean that the footprint of native images would increase significantly and they would lose one of their security advantages: not shipping code that's not use and protecting against an entire class of attacks. More, I think that in a native world it's expected to ship different binaries for separate target environments. As Java developers we might be used to shipping a single artifact for multiple environments (a JAR) and I think we should keep doing that. As @sdeleuze said, the native compilation phase should be the very last step before deployment and should not be the reference artifact.

@sdeleuze
Copy link
Contributor Author

Related to that, I think the workflow for source-based deployment is already ok, but it may potentially be interesting to explore first class options to create native executable from a Spring Boot executable JAR that does not contain AOT generated code / metadata.

@sdeleuze
Copy link
Contributor Author

sdeleuze commented Jan 19, 2023

Notice Spring Framework reference documentation mentions:

@Profile, in particular profile-specific configuration needs to be chosen at build time

I think this is consistent with the refinement proposed in this issue.

@Azbesciak
Copy link

Azbesciak commented Jan 24, 2023

Hello,
First of all, thank you for that effort - for the whole spring, and for years of work under that AOT processing. From my experience with Graal - since it started, and now, I know that it may be painful for many reasons (great project anyway :) ) .

To the topic - the important thing for me is to have @ConditionalOnProperty - at least. I got your idea that it would increase the build time, but look - from the other side, If I have multiple customers/need to change parameters depending on some conditions, should I build the image for every option, and maintain it? Not to mention that I can have some configuration changes, did not notice it and see that only on production, after (hopefully) testing the native image (which is unfortunately still not obvious for me, because the flag spring.aot.enabled does not work for me - it runs, whereas the app in the runtime fails somewhere).

AOT exists for years in development - just see JS apps like angular. The purpose, for me, is to remove the dead code, mostly.
The result is still very good.

If something is under the condition, it should not be perceived as a dead code; to say more, the user is rather aware that it can impact the runtime (they want to enable or disable it) - and probably also the build time, and they agree with that (otherwise why they would use that?).

To say more, I could understand that the code was removed if the condition was falsy during the build. But why the bean was still provided when it was truly during the build, and then disabled during the runtime - I do not know.

I suppose also that most of the build time is probably consumed on the lib/framework side code, not the user side - which is mostly under the configuration.

I think it should be provided somehow - with the flag or by default - anyway. Faster or later users would have to provide some workarounds anyway. One of it I see now is to register beans (did not test) in a functional way, just checking (possible) conditions in the runtime. Please, save me that effort, I am already a bit confused during my migration, which already took me 50 hours of watching that something fails (hopefully) during the build, or (most often) in runtime (reflection/jackson mostly). Note that I have only 5 small/medium services, which do not use very complex features, only reactive web/reactive mongo/jackson/redis/security and cloud gateway.

@sdeleuze
Copy link
Contributor Author

sdeleuze commented May 4, 2023

As shown in https://github.com/sdeleuze/demo-profile-aot, based on @snicoll insights I figured out we already support defining Spring profile AOT, it was just not easy to figure out based on the documentation. For the various use cases I have seen, this was the biggest need.

To be discuss with the wider team, but the fact it is support IMO make less critical the need for more advanced AOT profile management, like begin to have a more advancec mechamsim that evaluate some condition at build-time and some other at runtime. This is not trivial, introduce extra complexity and will likely impact the resulting footprint.

If we do something, I tend to think it would have to be explicit (like specifying that a profile or a property could change at runtime so don't remove it AOT), but I am not yet convince it is worth the time we would have spend on this. Maybe we could just make specifying profiles at build time a bit more first class at Boot level.

@sdeleuze sdeleuze changed the title Add AOT/native support for Spring profiles set at build time Refine Spring profile support with AOT/native May 4, 2023
@sdeleuze sdeleuze modified the milestones: 6.1.0-M1, 6.x Backlog Jun 6, 2023
@javapapo
Copy link

Hello, I came here after I got burned - by not reading the manual - and I kind of agree with @Azbesciak on its comments.

In business heavy applications - where Spring/SpringBoot dominates, we kind of rely a lot on

@conditional and on things like

@Profile({"x | y | z"})

Which are very handy.

I am wondering if the framework can compensate a bit on the above .

For example when it detects code with the following conditional Profile

  @Profile({"x | y | z"})

If I define all the available profiles on the spring-maven-plugin and the aot step - could spring - still behave the way we are used to?

So I wondering if we can provide to the aot processor ALL the available profiles that we use and then during the runtime Spring to pick - the beans or components that are annotated with the conditionals.

Obviously this is not code elimination - but the proposed way forward now - to define the specific profiles during build time - is not scaling a lot -especially if you use Profiles for definining behaviour or other aspects and is not just an indicator for an environment

@snicoll
Copy link
Member

snicoll commented Jul 26, 2024

So I wondering if we can provide to the aot processor ALL the available profiles that we use and then during the runtime Spring to pick - the beans or components that are annotated with the conditionals.

It's not as easy as it sound as we have to make sure that there is no side effect in doing so. For instance, let's assume that a x or y or z contributes a bean of type X that is later on injected somewhere else. At runtime it's all good as you'll enable x or y or z but processing all of them at build time does not let us figure that out.

It's one example of many. Code elimination is not really the problem and Spring AOT is not meant to do that.

If you ignore AOT and focus on native image, which is the primary target of this work, changing behavior at runtime is really something that doesn't work well as it has to go through all paths at build time.

@javapapo
Copy link

Yes I understand. I dont really know what is the best answer. For example in my case because I did not want the headache to re-work my build pipeline and introduce extra build steps, I just disabled aot for the time being, and trying to figure out if I can re-work my Profile conditional bean loading - which is one of the best Spring features by the way.

thanks for your reply I would be following the updates and how the framework will approach this

@alex-kormukhin
Copy link

alex-kormukhin commented Aug 22, 2024

Hello.

If @ConditionalOnProperty calculates at build time:

  1. we can't make one build for many environments;
  2. we can't make one build ("box" distribution) for many customers.

In such case spring native has a very narrow niche. Only for organizations wich deploy apps on 1-3 environments.

Spring native applications can't compete with go lang native applications (which is fully configurable at runtime).

  1. If we make one build for testing environment and another build for production environment, than production build is not tested, because we test testing build. (⊙_⊙)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) theme: aot An issue related to Ahead-of-time processing type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

7 participants