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

CommentFileSizeAuthor
#35 faster-web-tests-faster-installation.png119.42 KBFabianx
#35 faster-web-tests--normal-cache.png118.98 KBFabianx
#35 faster-web-tests--minimum-and-aggressive-cache.png121.27 KBFabianx
#32 interdiff.txt1.3 KBFabianx
#28 improve_webtestcase-2747075-27.patch9.29 KBFabianx
#28 interdiff.txt3.95 KBFabianx
#26 improve_webtestcase-2747075-26.patch8.56 KBFabianx
#26 interdiff.txt3.07 KBFabianx
#24 improve_webtestcase-2747075-24.patch7.44 KBFabianx
#19 improve_webtestcase-2747075-18.patch12.03 KBFabianx
#16 improve_webtestcase-2747075-15--interdiff.txt4.14 KBFabianx
#16 improve_webtestcase-2747075-16.patch11.89 KBFabianx
#15 improve_webtestcase-2747075-15.patch9.07 KBFabianx
#14 improve_webtestcase-2747075-14--interdiff.txt1.24 KBFabianx
#14 improve_webtestcase-2747075-14.patch9.07 KBFabianx
#11 improve_webtestcase-2747075-9.patch8.59 KBFabianx
#8 improve_webtestcase-2747075-8.patch8.3 KBFabianx
#7 improve_webtestcase-2747075-7.patch8.3 KBFabianx
#4 improve_webtestcase-2747075-3.patch9.23 KBFabianx
#2 improve_webtestcase-2747075-2.patch9.23 KBFabianx
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Fabianx created an issue. See original summary.

Fabianx’s picture

Enabled the caching by default for this patch and disabled MyISAM conversion for a first test.

Fabianx’s picture

Status: Active » Needs review
Fabianx’s picture

And with MyISAM just for testing.

The last submitted patch, 2: improve_webtestcase-2747075-2.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 4: improve_webtestcase-2747075-3.patch, failed testing.

Fabianx’s picture

Status: Needs work » Needs review
FileSize
8.3 KB

Uhm, 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.

Fabianx’s picture

And again with MyISAM enabled.

The last submitted patch, 7: improve_webtestcase-2747075-7.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 8: improve_webtestcase-2747075-8.patch, failed testing.

Fabianx’s picture

Fabianx’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 11: improve_webtestcase-2747075-9.patch, failed testing.

Fabianx’s picture

The 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.

Fabianx’s picture

And test again with MyISAM

Edit: Down to 4 min 40 seconds!

Locally one test completes in 3 seconds! (with aggressive cache: 1 sec)

Fabianx’s picture

Status: Needs review » Needs work

The last submitted patch, 16: improve_webtestcase-2747075-16.patch, failed testing.

The last submitted patch, 16: improve_webtestcase-2747075-16.patch, failed testing.

Fabianx’s picture

Fabianx’s picture

Status: Needs work » Needs review
Fabianx’s picture

Fabianx’s picture

That 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 ...

Fabianx’s picture

Version: 7.x-dev » 8.2.x-dev

And to 8.2.x

Fabianx’s picture

A first try, locally I only get 43 -> 32 s speed up, but lets see how many errors we get.

Status: Needs review » Needs work

The last submitted patch, 24: improve_webtestcase-2747075-24.patch, failed testing.

Fabianx’s picture

Status: Needs work » Needs review
FileSize
3.07 KB
8.56 KB

Now not _all_ using the same DB prefix and config dir ... uhm ...

Status: Needs review » Needs work

The last submitted patch, 26: improve_webtestcase-2747075-26.patch, failed testing.

Fabianx’s picture

There 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.

Fabianx’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 28: improve_webtestcase-2747075-27.patch, failed testing.

catch’s picture

This 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.

Fabianx’s picture

Assigned: Fabianx » Unassigned
FileSize
1.3 KB

#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.

Fabianx’s picture

Priority: Critical » Major
Fabianx’s picture

+++ b/includes/module.inc
@@ -460,6 +460,7 @@ function module_enable($module_list, $enable_dependencies = TRUE) {
+      drupal_static_reset('drupal_get_schema_versions');

This is a core bug.

--

Short comment to the D7 version. The above is a legitimate core bug that I still need to file separately.

Fabianx’s picture

I 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.

klausi’s picture

  1. diff --git a/core/modules/simpletest/src/TestBase.php b/core/modules/simpletest/src/TestBase.php
    index 6bb84fd..287453b 100644
    --- a/core/modules/simpletest/src/TestBase.php
    +++ b/core/modules/simpletest/src/TestBase.php
    

    SimpleTest 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).

  2. +++ b/core/modules/simpletest/src/TestBase.php
    @@ -56,6 +56,13 @@
       /**
    +   * Whether to cache the setUp.
    +   *
    +   * @var bool
    +   */
    +  public $cache = FALSE;
    

    why is this public? Shouldn't it be protected? Please add a comment.

  3. +++ b/core/modules/simpletest/src/WebTestBase.php
    @@ -542,20 +543,80 @@ protected function setUp() {
    +      $cached_config_directories = $GLOBALS['config_directories'];
    +      $cached_site_directory = $GLOBALS['config']['simpletest_site_directory'];
    +      $this->rootUser->pass_raw = $GLOBALS['config']['simpletest_root_user_pass'];
    

    Why are we accessing globals here and cannot use class properties? Please add a comment.

  4. +++ b/core/modules/simpletest/src/WebTestBase.php
    @@ -567,6 +628,145 @@ protected function setUp() {
    +   * @return
    +   *   TRUE when cache is used, FALSE when cache is not available.
    

    data type missing (bool).

  5. +++ b/core/modules/simpletest/src/WebTestBase.php
    @@ -567,6 +628,145 @@ protected function setUp() {
    +   * @see DrupalWebTestCase::setUp()
    

    should be just ::setUp(), DrupalWebTestCase does not exist in D8.

  6. +++ b/core/modules/simpletest/src/WebTestBase.php
    @@ -567,6 +628,145 @@ protected function setUp() {
    +      $this->copyCache(substr($this->databasePrefix, 10), $this->getCacheKey());
    

    why do we need the substr() here? What does it cut off? Please add a comment.

  7. +++ b/core/modules/simpletest/src/WebTestBase.php
    @@ -567,6 +628,145 @@ protected function setUp() {
    +   * @param $from
    +   *   The prefixId / cacheKey from where to copy.
    +   * @param $to
    +   *   The prefixId / cacheKey to where to copy.
    

    @param data types missing.

  8. +++ b/core/modules/simpletest/src/WebTestBase.php
    @@ -567,6 +628,145 @@ protected function setUp() {
    +  /**
    

    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?

  9. +++ b/core/modules/simpletest/src/WebTestBase.php
    @@ -567,6 +628,145 @@ protected function setUp() {
    +   * @param $src
    +   *   The source directory.
    +   * @param $dest
    +   *   The destination directory.
    

    data types missing.

  10. +++ b/core/modules/simpletest/src/WebTestBase.php
    @@ -567,6 +628,145 @@ protected function setUp() {
    +    $skip_alter = array(
    +      'taxonomy_term_data',
    +      'node',
    +      'node_access',
    +      'node_revision',
    +      'node_comment_statistics',
    +    );
    

    Why do we skip exactly those tables? Please add a comment.

  11. +++ b/core/modules/simpletest/src/WebTestBase.php
    @@ -567,6 +628,145 @@ protected function setUp() {
    +      if(!in_array($original_table_name, $skip_alter)){
    

    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!

Fabianx’s picture

#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.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Fabianx’s picture

Title: Improve WebTestCase performance by 50% » [meta] Improve WebTestCase / BrowserTestBase performance by 50%
Category: Task » Plan

I 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.

Fabianx’s picture

Child 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:

  \Drupal\Core\Cache\ChainedFastBackendFactory::$disableFastChained = TRUE;
  // install
  \Drupal\Core\Cache\ChainedFastBackendFactory::$disableFastChained = FALSE;
  // Reload container from dump to reset cache services (not rebuild!)

- 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!

Fabianx’s picture

Created #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.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

andypost’s picture

anything left here?

larowlan’s picture

bump

hestenet’s picture

@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.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Mile23’s picture

Agree 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 in BTB::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?

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

larowlan’s picture

The child issues look to reference all the follow ups mentioned in 34, 39, 40

larowlan’s picture

In testing #19 on a client D7 project, I'm finding Exceptions around max key length when switching to myisam

Fabianx’s picture

Assigned: Unassigned » Fabianx

#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.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

marcelovani’s picture

Issue summary: View changes

Since 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?

marcelovani’s picture

Issue summary: View changes

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

larowlan’s picture

Issue tags: +Needs reroll