Optimize startup time: make validation initialization lazy
dsyer opened this issue · comments
If I'm not really using validation except in the web tier it's wasteful to initialize it on startup, and it's expensive: 200-300ms out of 3s to start a Pet Clinic.
Will target to 1.5 to see what are options are. It might slip.
I think this should be DevTools-specific or opt-in. In production, I'd prefer to pay the cost of validation initialisation at startup and be safe in the knowledge that it's initialised successfully rather than deferring the cost and potential failure until a request is routed to the app.
It's also used in @ConfigurationProperties
processing, at least optionally, so I'd want that to be on if I'm not in dev mode. It's a tricky one. It does seem inordinately expensive to bootstrap a local validator, compared to the value it provides. I guess maybe we should stick to explicit Spring validators for our own config properties, or something cheaper than Hibernate if it exists.
for our own config properties
We can't do that. Validation is an advertized feature so users are free to use it as advertized (read in their config properties). I don't think that changing something for our own code is something we should ever do if the related feature is public.
I did a bit of digging with this, it seems like the majority of the time is spent trying to work out of a @ConfigurationProperties
instance has any validator constraints. The logic is quite complicated, scanning all method and parameters for annotations and working up the type hierarchy.
95% of our properties classes don't use JSR-330 constraints, I think if we switch validation off for our classes, but leave it on for user defined configuration classes, we should be able to improve startup time.
I think it would be easier to require @Validated
on the class to enable validation. That way we can have a quick check to figure out if we need to enable it or not.
Experimental branch https://github.com/philwebb/spring-boot/tree/gh-7579
@snicoll I like that idea, but is it a sensible change for 1.5? My approach so far has been to add a vaidate
attribute to @ConfigurationProperties
and set that to false
for all our own instances.
Change isn't noticeable in SampleTomcatApplication
:
before
2017-01-16 23:31:40.055 INFO 91149 --- [ main] sample.tomcat.SampleTomcatApplication : Started SampleTomcatApplication in 1.494 seconds (JVM running for 1.744)
2017-01-16 23:32:05.928 INFO 91185 --- [ main] sample.tomcat.SampleTomcatApplication : Started SampleTomcatApplication in 1.494 seconds (JVM running for 1.743)
2017-01-16 23:32:21.487 INFO 91207 --- [ main] sample.tomcat.SampleTomcatApplication : Started SampleTomcatApplication in 1.493 seconds (JVM running for 1.746)
after
2017-01-16 23:27:27.669 INFO 90702 --- [ main] sample.tomcat.SampleTomcatApplication : Started SampleTomcatApplication in 1.764 seconds (JVM running for 2.23)
2017-01-16 23:27:44.806 INFO 90731 --- [ main] sample.tomcat.SampleTomcatApplication : Started SampleTomcatApplication in 1.498 seconds (JVM running for 1.744)
2017-01-16 23:27:55.165 INFO 90746 --- [ main] sample.tomcat.SampleTomcatApplication : Started SampleTomcatApplication in 1.518 seconds (JVM running for 1.771)
But it is for Pet Clinic:
before
2017-01-16 23:30:28.164 INFO 91031 --- [ restartedMain] o.s.s.petclinic.PetClinicApplication : Started PetClinicApplication in 8.411 seconds (JVM running for 8.841)
2017-01-16 23:30:52.690 INFO 91068 --- [ restartedMain] o.s.s.petclinic.PetClinicApplication : Started PetClinicApplication in 7.988 seconds (JVM running for 8.424)
2017-01-16 23:31:14.828 INFO 91101 --- [ restartedMain] o.s.s.petclinic.PetClinicApplication : Started PetClinicApplication in 7.928 seconds (JVM running for 8.359)
after
2017-01-16 23:28:31.642 INFO 90792 --- [ restartedMain] o.s.s.petclinic.PetClinicApplication : Started PetClinicApplication in 7.654 seconds (JVM running for 8.143)
2017-01-16 23:28:48.329 INFO 90815 --- [ restartedMain] o.s.s.petclinic.PetClinicApplication : Started PetClinicApplication in 7.437 seconds (JVM running for 7.863)
2017-01-16 23:29:04.505 INFO 90841 --- [ restartedMain] o.s.s.petclinic.PetClinicApplication : Started PetClinicApplication in 7.624 seconds (JVM running for 8.106)
That flag is better if you want this to be made 100% transparent but it's a bit weird. Someone might wonder why you wouldn't want validation in the first place. Our reasoning here is performance so making it that opt-in (i.e. you need the relevant annotation now) makes more sense to me.
Surely the current state is awesome because nobody has to change anything but I fear we'll regret that design pretty soon.
I like the idea of requiring @Validated
and that feels like where we want to get to. I'm not sure how to get there from where we are today, but adding a validate
attribute to @ConfigurationProperties
doesn't feel like the right way to me. I'm not keen on adding an attribute in 1.5 that we'd then, presumably, want to remove in 2.0 in favour of requiring @Validated
.
I think we either need to jump straight into @Validated
in 1.5 or we should wait for 2.0 where requiring @Validated
will be less of a shock.
+1 for @Validated
(in 1.5 if possible).
Seems like @Validated
is the winner. Will try for 1.5.
For 1.5 we'll still validate all configuration classes (with the exception of Spring Boot's own) even if there's not a @Validated
annotation. We'll log a warning telling people what to do. We can flip the default in 2.0.
Hi @philwebb ,
I tried this feature using latest snapshot version of 1.5.
But same warning log are output twice if not specify the @Validated
on configuration properties class as follow:
2017-01-21 01:44:59.434 WARN 15563 --- [ main] figurationPropertiesBindingPostProcessor : The @ConfigurationProperties bean class com.example.AppProperties contains validation constraints but had not been annotated with @Validated.
2017-01-21 01:45:00.201 WARN 15563 --- [ main] figurationPropertiesBindingPostProcessor : The @ConfigurationProperties bean class com.example.AppProperties contains validation constraints but had not been annotated with @Validated.
Is this behavior work as design ?
I think better that it output only just once. What do you think ?
@kazuki43zoo Once only would be better
@philwebb quick question regarding the use of @PostConstruct
annotated methods to validate properties.
Is this how it should be done on all properties classes from now on?
Is it strictly limited to validation code (or general initialization code is accepted)?
Why are setters not considered for this?
@bclozel I think setters might actually be better. I tried them at one point but hit some issues (I can't remember why). I'd say try setters first.
I'll try to fix the ones I changed.
@philwebb the only use case I have in mind is if you set the value to null
for some reason and later (before the bean is initalized) you provide a valid value. If we put the validation in the setter it will fail hard as soon as we attempt to set null
. Not sure if that's a legit use case but I wish we wouldn't use @PostConstruct
to replace the validation.
We're going to live with the double log message. I've replaced the @PostConstruct
-based validation with setter-based validation in f823599.