Let me start with the motivation for this. I have an Organic Group that has many thousands of members, and I'd like to be able to iterate over that list using the Entity module's EntityMetadataWrapper classes. All good so far, OG provides functionality for that to work quite nicely via it's internal call to og_get_group_members_properties().

The problem with that function is it's incredibly slow, because it makes a call to og_membership_load_multiple() for every single member of my group. And worse, it does so unnecessarily because if you pull apart the original EntityFieldQuery it's already reading the correct database table. So what we have here is a classic case of database abstraction not fitting our scenario quite how we need it to.

So what's the solution? Well, we could just write a SelectQuery, but that would be to throw away much of the power of EFQ, when actually it's still able to do most of the job that we ask of it. How about instead we extend EFQ with our own OG class, which has a proper understanding of OGs architecture. And with that, I give you OgMembershipFieldQuery!

See attached patch, and have a nice day.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Angry Dan’s picture

Of course, what use is a class if you don't actually use it! See attached second patch, with an implementation on that function I was talking about earlier - og_get_group_members_properties().

If you want to test, then set yourself up with a group and around 1,000 users. Load the group with entity_metadata_wrapper() and try accessing $wrapper->{'og_membership__' . OG_STATE_ACTIVE}->raw() (to avoid doing 1,000 user_loads) or whatever it would be and see how much of a speed improvement there is.

Angry Dan’s picture

Status: Active » Needs review
RoySegall’s picture

Before reviewing - can you supply bench mark of before and after?

Angry Dan’s picture

Yeah, I guess so.

Assume creating a standalone php file - benchmark.php, with the following code:

define('DRUPAL_ROOT', getcwd());

require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);

$node_wrapper = entity_metadata_wrapper('node', 7);
$node_wrapper->{'members__' . OG_STATE_ACTIVE}->raw();

Take three test cases:

  1. Firstly, as a control I comment out those last two lines. I use the devel module to measure the full page load time and memory
  2. Now, with the attached patches in place, and node 7 being an OG with 2551 users I uncomment those two lines
  3. Finally, without the patch applied.

I'm not producing repeat benchmarks or anything like that, I don't have time. My results are observational but significant enough to be representative. Fluctuations are natural but 95% of my results sit inside the quoted values. All tests are with a warm cache.

  1. The control test shows that my site bootstraps at between 150-350ms and memory peaks at 6.75MB.
  2. The second test adds around 100 ms to bring the average up to 200-400ms. Peak memory is up to 10.25.
  3. The final test, without patches, sees loads consistently sit around 1500ms and peak memory roughly doubling to 21.5MB.

More scientific results I can't give you, but these are my observations - it's quicker.

Angry Dan’s picture

Just wanted to bump this issue as I've been using the OgMembershipFieldQuery class in production for a long time now and I think it could be really beneficial in general.

Angry Dan’s picture

... And here's an example of where this class is so useful:

    $user_query = new OgMembershipFieldQuery();
    $user_query
      ->setGroup('node', $profile->getIdentifier())
      ->setEntity('user')
      ->setFetchEntity();

    $members = $user_query->execute();