Problem/Motivation
- Tests are slow and installation takes a long time (both for d.org test runs and local development)
- Backdrop found out that installation is deterministic and can easily be cached per profile
Credit: https://github.com/backdrop/backdrop/pull/1366
Their patch works, but is preparing for a known set of profiles instead of demand.
Proposed resolution
- Introduce --cache option for BC compatibility
- Cache database tables and files after installation
- Re-use cache when it exists
- Prevent race conditions with transactions
- (optional) Convert tables to MyISAM to speed up DB copy and test runs
99% of people don't change the database data or schema during development, so the cache will work fine.
Remaining tasks
- Upload patch
- Check that tests pass
- Port to Drupal 8 as trait and implement in BrowserTestBase as well
- Get it committed to Drupal 7
User interface changes
- None, commandline only. UI change can be follow-up
API changes
- None
Comment | File | Size | Author |
---|---|---|---|
#35 | faster-web-tests-faster-installation.png | 119.42 KB | Fabianx |
#35 | faster-web-tests--normal-cache.png | 118.98 KB | Fabianx |
#35 | faster-web-tests--minimum-and-aggressive-cache.png | 121.27 KB | Fabianx |
#32 | interdiff.txt | 1.3 KB | Fabianx |
#28 | improve_webtestcase-2747075-27.patch | 9.29 KB | Fabianx |
Comments
Comment #2
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedEnabled the caching by default for this patch and disabled MyISAM conversion for a first test.
Comment #3
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedComment #4
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedAnd with MyISAM just for testing.
Comment #7
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedUhm, Drupal CI applies https://www.drupal.org/files/issues/2551981-21-add-directory-option-to-r..., so this won't work. I am the 0.001% that runs into that issue.
So without nice arguments for now.
Comment #8
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedAnd again with MyISAM enabled.
Comment #11
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedOkay, I forgot to update the file paths after retrieving from cache ...
Comment #12
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedComment #14
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedThe test fails are a core bug, where the schema is wrongly installed (0) during installation of more than one module due to a static cache not being cleared properly.
Last test run showed from:
~ 10 min down to 6 min
Locally I see test times of 7 seconds instead of 28 seconds.
Comment #15
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedAnd test again with MyISAM
Edit: Down to 4 min 40 seconds!
Locally one test completes in 3 seconds! (with aggressive cache: 1 sec)
Comment #16
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedTest advanced cache (with more variations).
Comment #19
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedLets try that once more a little less aggressively.
Comment #20
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedComment #21
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedComment #22
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedThat worked, but the aggressive cache was way slower (16 min) - due to the many table copying.
One last check for a MySQL only optimization and then forward to Drupal 8.
@todo Need to check how to do this database agnostic anyway ...
Comment #23
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedAnd to 8.2.x
Comment #24
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedA first try, locally I only get 43 -> 32 s speed up, but lets see how many errors we get.
Comment #26
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedNow not _all_ using the same DB prefix and config dir ... uhm ...
Comment #28
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedThere is a rootUser that needs to have the password known by the testBase. Also better method to write the settings that is not incompatible with UpdateTestBase.
Comment #29
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedComment #31
catchThis was originally proposed in #323477: Increase simpletest speed by running on a simplified profile (see issue summary and discussion up to #55 where the issue switched to introducing the testing profile). Still a good idea, testing of full installs isn't going away and the numbers here show that the testing profile only gets us so far.
Comment #32
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commented#31: That is too bad, results show that the testing profile was efficient (28 min vs. 30 / 35 min), but that now with the cache in place, the opposite problem exists:
Because testing is so minimal, the speed-up is not as much as expected.
I have the three test fails fixed locally and attached is the interdiff.
For one test the caching just needs to be disabled.
I will try to run tests with a bloated testing profile next, but un-assigning for now.
My main goal here was to decrease my own local testing speed for faster development and that has been kinda-reached and I can always cache aggressively including modules for local dev.
Comment #33
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedComment #34
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedThis is a core bug.
--
Short comment to the D7 version. The above is a legitimate core bug that I still need to file separately.
Comment #35
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedI am just leaving three screenshots here for motivation for the Drupal\comment\Tests\CommentPreviewTest, which uses standard profile. The XHProf is just for the setUp() of the test.
The following includes a lot of other optimization issues I have been testing out and is just a sneak preview:
The absolute minimum that I was able to achieve so far, without changing cache backends:
10 seconds (!) - This uses the aggressive cache of caching the whole setUp() and module installation().
This does not use aggressive caching and this does only cache the standard profile once (!) - 18 seconds
This last one does not do any installation caching at all, but optimizes e.g. the Config FileStorage and Container re-builds.
Memory might be an issue here however.
Comment #36
klausiSimpleTest is going to be deprecated, so this change should be implemented for BrowserTestBase first. See #2735005: Convert all Simpletest web tests to BrowserTestBase (or UnitTestBase/KernelTestBase).
why is this public? Shouldn't it be protected? Please add a comment.
Why are we accessing globals here and cannot use class properties? Please add a comment.
data type missing (bool).
should be just ::setUp(), DrupalWebTestCase does not exist in D8.
why do we need the substr() here? What does it cut off? Please add a comment.
@param data types missing.
why do we need this? PHP does not have a standard library function to copy the whole directory? Really? Then we should put this on some core file helper class as static method, right?
data types missing.
Why do we skip exactly those tables? Please add a comment.
space missing after if()
What about the public files directory in the simpletest directory? Is that also copied to the next test? Shouldn't we clear public file uploads before running the next test? Maybe I overlooked that in the patch.
So all in all just minor points, I have to admit that I don't fully understand the changes yet. The idea makes a lot of sense though!
Comment #37
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commented#36: Thank you for the review:
1. Indeed the functionality most likely will live in a trait as there is lots of duplicate code still.
2. Yes, this needs isCacheable() / setCacheable() functions.
3. Those globals pre-exist for config overrides, this just accesses them, but needs a comment - yes.
4/5. Yes, needs to be fixed. Agree.
6. This is pre-existing code how to get the ID - should probably add a helper.
7. Thanks!
8. Agree, and yes PHP does not have that. This function as copied from php.net help pages ... Any candidates known in Symfony or elesewhere? If not could live in Util ...
9. Thanks!
10. Will be completely removed, not sustainable (was from backdrop backport).
11. Thanks.
--
Yes, this will also cache the public files directory, but as we only cache the setup, we always have the same state.
Comment #39
Fabianx CreditAttribution: Fabianx for Drupal Association commentedI am making this a meta issue (which will get complete patches), so that child issues can be created for relevant milestones.
By analyzing the D8 test performance I found that the D7 approach of #2759197: [D7] Improve WebTestCase performance by 50% is not as impactful on Drupal 8, because of the minimal profile.
An analysis revealed the following important things:
- a) Container compilation is slow - mainly the Symfony steps [NEEDS ISSUE CREATED] - likely the biggest bang for the buck in Drupal 8 right now.
- b) BrowserTestBase needs a port now that WebTestBase is de-facto deprecated, likely the best approach is either a trait that shares common functionality or a Cacher class, which might allow D8 and D7 to share code more easily. Needs to be checked.
- c) Many tests do additional setup, which can be cached, so make it easy to cache for complex setUp(),s the whole step
- d) Not using minimal is partially slower, because DB copying is still slow, however it can be heavily parallelized => explore making DB copy from the snapshot faster by using mysqli directly and async queries.
- e) There is still some heavy writes happening after installation during module installation, e.g. the chained fast backend, cache tags invalidation and "state" setting. Consider moving temporarily to memory. (in general using db_merge() is slow in Drupal (as its delete and create always) - KeyValueStore could profit from using upsert's or try / catch instead.
That is MySQL only, but around 98% of our test time spent is on MySQL anyway.
Comment #40
Fabianx CreditAttribution: Fabianx for Drupal Association commentedChild issues from today (to be created):
- Use FileCache for infoParserDynamic.php, 5 loc, but lots of CPU saved, because system_rebuild_module_data() was calling
- Disable fast chained backend during installation of modules in tests (saves a lot of DB writes, similar to !drupal_installation_attempted() in ChainedFastBackendFactory)
e.g. a potential solution would look like:
- Use a ChainedFileCacheBackend + a FileStorageFileCacheBackend to (atomically) create FileCache entries on disk that are shared by all test processes.
I use that locally already and it works great - though I have to fix the atomicity for it still.
- Use pre-hashed passwords during tests for user creation to avoid calling crypt() again and again (saves around 100-200 ms per user creation call)
- Idea: Consider to create a buffering / queued chained cache backend + buffering cache tags invalidator:
The idea is to buffer all writes and invalidations, serve requests for the cache from static memory.
Have a flush() method to flush the memory backend contents to the database backend at the end, which disables the special functionality and it acts like a normal DB backend afterwards.
- Idea: Play more with caching the full container based only on "container.modules" and the normal container cache key as input. See if it can pass tests and how much memory is used.
Found out that:
Merge queries now use the Select before Insert, fallback to Update mechanism! (at least for SQlite) :) - YEAH - that makes K/V store updates faster!
Comment #41
Fabianx CreditAttribution: Fabianx for Drupal Association commentedCreated #2796105: Move similar methods in BrowserTestBase / WebTestBase to a trait; untangle installDrupal() after talking with larowlan and dawehner.
That will make the speed enhancements of --cache and --cache-modules available to both WebTestBase and BrowserTestBase.
Comment #43
andypostanything left here?
Comment #44
larowlanbump
Comment #45
hestenet@Fabianx - Anything we can do to help move this along? Perhaps help break out the child issues in: #40?
Between the increasing number of tests and single-threaded behavior of the phantomjs tests, we're close to exceeding the 1 spot-instance-hour threshold for a single core test.
I'm hoping with 8.3.0 out the door there'll be a bit of breathing room to move this along.
Comment #47
Mile23Agree with #36.1: Make BTB faster and then convert WTB to BTB instead of working on WTB.
This should only cache per test class, and not per test run. So
BTB::setUpBeforeClass()
caches everything and then that cache is discarded inBTB::tearDownAfterClass()
.Implement as a trait that can be included per test class, or as a subclass, for opt-in. That way we don't make BTB more complex, and it's completely clear to everyone that the test expects a cached fixture.
We do not change any tests to make this work.
Did we ever see follow-ups from #34, #39, and #40?
Comment #49
larowlanThe child issues look to reference all the follow ups mentioned in 34, 39, 40
Comment #50
larowlanIn testing #19 on a client D7 project, I'm finding Exceptions around max key length when switching to myisam
Comment #51
Fabianx CreditAttribution: Fabianx for Drupal Association commented#50 Yeah, I don't think switching to MyISAM is necessarily that good of an idea anymore. And yes especially with the UFT8-MB4 changes, this won't work at all anymore - I think.
I think in the D7 issue is a patch, which does not have the MyISAM change:
#2759197-40: [D7] Improve WebTestCase performance by 50% is what I use personally.
Comment #57
marcelovaniSince Simpletest was moved to its own project (https://www.drupal.org/project/simpletest) do we still need a similiar caching solution to speed up BrowserTestBase tests?
Comment #58
marcelovaniComment #64
larowlan