Performance: validateOnMigrate slows exponentially as the number of migration scripts increases
Background
I'm working on improve CI build times on a database project that has over 4400 migration scripts across dozens of sub-directories, and with validateOnMigrate = True the validation step accounts for over 50 seconds of the runtime.
I thought that seemed odd, given the operations I would expect it to be performing, so I cloned the source code and started profiling it.
Looking through the code and the profiling, it looks like there are a number of places that are using streaming concepts (which is quite readable) that are unfortunately resulting in rescanning the enumeration stream in multiple places per migration script. In particular, it looks like the call to validation migration scripts refreshes the configuration, and then the calling code immediate refreshes the configuration metadata again.
So, pulling on the thread of hotspots in the profiling, it looks like highestSHTVersion & hasFutureUndo via calculateNoSHTStates are significant contributors to this exponential slowness:
By CPU % over 2880 migration scripts:
highestSHTVersion 93.1%
hasFutureUndo 92.6%
calculateNoSHTStates 93.4%
insertResolvedMigrations 1.9%
scanForResources 1.0%
ResourceNameParser 0.6%
PluginRegister 0.5%
ChecksumCalculator 0.3%Example of the current pattern from CoreMigrationStateCalculator.java:
private static boolean hasFutureUndo(final Pair<ResolvedSchemaHistoryItem, LoadableResourceMetadata> migration,
final Collection<? extends Pair<ResolvedSchemaHistoryItem, LoadableResourceMetadata>> sortedMigrations) {
return sortedMigrations.stream()
.filter(x -> x.getLeft() != null)
.filter(x -> x.getLeft().getType().isUndo())
.filter(x -> x.getLeft().getInstalledRank() > migration.getLeft().getInstalledRank())
.anyMatch(x -> x.getLeft().getVersion().equals(migration.getLeft().getVersion()));
}I propose refactoring the repeated stream -> filter patterns in this class to essentially process once and cache the results for re-use in the same execution. From what I can tell, this is only needed once per execution, and if fast shouldn't need to be recalculated by later steps.
I plan to work on implementing the necessary changes along with a relevant benchmark, as I keep pulling on the profiler thread in the code paths for validating the migration scripts.
Source: flyway/flyway