Problem/Motivation

When you collect a large number of registrations (in the case of our website it's around 5000 registrants) and you manually add an instance to a Series, calculating the value of the computed field availability_count will cause your database server to go down.

The thing is:

If you go to a Series and use the "Add instance" option, after submitting the form you will get this:
SQLSTATE[HY000]: General error: 2006 MySQL server has gone away: INSERT INTO "cache_entity" ("cid", "expire", "created", "tags", "checksum", "data", "serialized") VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2, :db_insert_placeholder_3....

This is because when making the POST request, the computation of the availability_count takes place, invoking the method Drupal\recurring_events_registration\Plugin\ComputedField\AvailabilityCount::computeValue().
That ends up using the Drupal\recurring_events_registration\RegistrationCreationService service to call retrieveAvailability(), which in turn calls retrieveRegisteredParties(TRUE, FALSE) that uses loadByProperties(). And here is where disaster comes:

retrieve Registered Parties load By Properties problem

retrieve Registered Parties count

Update: Along with the availability_count, there are a couple more computed fields that are also calculated when submitting the "Add instance" form: registration_count and waitlist_count, which, in turn, also call retrieveRegisteredParties(), generating more calls to loadByProperties() and making the process even heavier.

Steps to reproduce

  1. Have a large number of registrants, say 10000
  2. Go to a Series and use the "Add instance" option
  3. Try to save the instance

Proposed resolution

This gives me various considerations:

  1. retrieveAvailability() must not rely on retrieveRegisteredParties() and a full load of the registrant entities just to get a count of the registrants. It should use another method based on Entity Query and the count() method. Something like the following (not tested yet):
      public function retrieveRegisteredPartiesCount($include_nonwaitlisted = TRUE, $include_waitlisted = TRUE, $uid = FALSE) {
        $query = $this->storage->getQuery();
    
        if ($include_nonwaitlisted && !$include_waitlisted) {
          $query->condition('waitlist', 0);
        }
        elseif (!$include_nonwaitlisted && $include_waitlisted) {
          $query->condition('waitlist', 1);
        }
    
        if (!$include_waitlisted) {
          $query->condition('waitlist', 0);
        }
    
        if ($uid) {
          $query->condition('user_id', $uid);
        }
    
        switch ($this->getRegistrationType()) {
          case 'series':
            if (!empty($this->eventSeries->id())) {
              $query->condition('eventseries_id', $this->eventSeries->id());
            }
            break;
    
          case 'instance':
            if (!empty($this->eventInstance->id())) {
              $query->condition('eventinstance_id', $this->eventInstance->id());
            }
            break;
        }
        
        $result = $query->count()->execute();
    
        return $result;
      }
    
  2. Computed fields should not be calculated during the POST request when creating or editing an entity. I faced something similar in another project. The resulting values of computed fields are actually useful when getting/viewing/reading the entities, but in the POST request generated by a form submission and before the entity is saved, it does not make too much sense to me. I made an adjustment to various computed fields in another project: I verified the method of the request, and it case it is POST, I assigned an empty value to the computed field and do not perform the computation:
     /**
       * {@inheritdoc}
       */
      protected function computeValue() {
        // When saving or editing an entity, we are not interested in calculating
        // the values for its computed fields. The resulting values of these
        // computed fields are actually useful when getting/viewing/reading the
        // entities, for example, when retrieving an entity data from a GET request
        // to a REST or JSON:API endpoint.
        // If the request has the 'POST' method, assign an empty value to the
        // computed field and return.
        if ($this->requestStack->getCurrentRequest()->getMethod() == 'POST') {
          $this->list[0] = $this->createItem(0, NULL);
          return;
        }
        /*
         * The ComputedItemListTrait only calls this once on the same instance; from
         * then on, the value is automatically cached in $this->items, for use by
         * methods like getValue().
         */
        if (!isset($this->list[0])) {
          $entity = $this->getEntity();
          $this->list[0] = $this->createItem(0, $this->getRegistrationCreationService($entity)->retrieveAvailability());
        }
      }
    
  3. Also, notice how the $this->list[0] is defined just once. Read the comment about ComputedItemListTrait only calling this once

Remaining tasks

Check the changes proposed above and create a patch (done)

Comments

camilo.escobar created an issue. See original summary.

camilo.escobar’s picture

Title: Adding an instance to a Series: Computation of 'availability_count' brings down the database server » Adding an instance to a Series: Computed fields bring down the database server
Issue summary: View changes
camilo.escobar’s picture

StatusFileSize
new13.19 KB
camilo.escobar’s picture

Issue summary: View changes
owenbush’s picture

Status: Active » Needs review

owenbush’s picture

Status: Needs review » Fixed

This looked good, thank you for your work and I thought the approach was really good.

chrisla’s picture

Status: Fixed » Needs work
StatusFileSize
new534 bytes

The fix here is incompatible with Drupal 10 as it is missing the now required $query->accessCheck() part of the query. A D10 site will break when the module is updated.

Attached patch will fix for D10 users

christian deloach’s picture

Thanks endless_wander. Patch works great. I confirm that it addresses the issue with the missing accessCheck.

chrisla’s picture

Status: Needs work » Reviewed & tested by the community

  • owenbush committed fabf6a6b on 2.0.x
    Issue #3391389 by endless_wander, owenbush: Adding an instance to a...
owenbush’s picture

Status: Reviewed & tested by the community » Fixed

The accesscheck patch has been committed to 2.0.x

camilo.escobar’s picture

Thanks owenbush and endless_wander!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.