Hi, I updated from beta8 to 1.1 recently, since then I have extreme performance issues.
Using XHProf I can see FacetapiFacetProcessor::mapValues (10 calls) + facetapi_get_taxonomy_hierarchy (5 calls) together take more than 7s to finish, sometimes I even get a "The connection to the server was reset while the page was loading" in my browser. In beta8 the whole page was processed in less than 1s.
We have 2 big hierarchical taxonomies with 10-30k items each and a few other facets with up to 10 values.

Anyone else experiencing this?
sb

Comments

cpliakas’s picture

We have 2 big hierarchical taxonomies with 10-30k items

Are you processing the entire vocabulary on search pages, or do you have the hard limit set so that only a subset are acted upon?

Thanks,
Chris

stephanbauer’s picture

Are you processing the entire vocabulary on search pages, or do you have the hard limit set so that only a subset are acted upon?

Hi, we had it set to unlimited, seems this was the problem.
I've reduced it to 100 and it works much better, thanks!
But now the problem is, that even with only 30 root items and nothing selected so all root items should be shown, not all of them are displayed in the block.

cpliakas’s picture

stephanbauer,

Thanks for reporting back! So this is a tricky problem. How Facet API works is that the server (i.e. Solr, Database search, etc) does a lot of the processing, but then Facet API applies runtime-level stuff such as hierarchy processing, mapping, etc. Therefore it isn't really designed to do mass processing of the content, and I am not surprised that it chokes with 30k pieces of content that it has to chew through in PHP.

Regarding the parent items, though, they should inherit the counts of the child items, so the most popular items should be displayed. Are you by any chance using Search API as the search backend?

Thanks,
Chris

cpliakas’s picture

Also...

One more thing to note is that in order to get the labels in a way that work with Drupal's various translation systems (i.e. the i18n module), it has to do a taxonomy_term_load_multiple() on all returned taxonomy terms. So for 30k docs, that is a lot of loads.

If you do need to display a large number of terms, you might be able to do this by replacing the hierarchy processing / map processing callbacks to something less generic that might not go through the various override mechanisms, but will solve your specific use case and be more performant. For example, instead of doing a taxonomy_term_load_multiple(), query the appropriate taxonomy table directly. This will break translations, but it will be more efficient.

Thanks,
Chris

stephanbauer’s picture

Thanks, unfortunately translation is an issue. But I will try that, so I should use hook_facetapi_facet_info_alter() and change 'map callback' and 'hierarchy callback'?
sb

stephanbauer’s picture

Hi again,
changing 'values callback' in hook_facetapi_facet_info_alter() doesn't seem to work, is there another way of altering the values?
Thanks,
sb

cpliakas’s picture

stephanbauer,

Actually, "values callback" is not currently used by anything. It is a legacy key that has an unfortunate name. Implementing hook_facetapi_facet_info_alter() is the way to go changing the "map callback" for the taxonomy terms. Make sure to clear cache after implementing the hook, otherwise it won;t get picked up.

Chris

stephanbauer’s picture

Hi again, just a quick question: As I don't think there is a lot to improve in facetapi_get_taxonomy_hierarchy() and this one takes too long already I changed these two callbacks (facetapi_get_taxonomy_hierarchy() + facetapi_map_taxonomy_terms()) to only process the values that are visible in the facet block.
But unfortunately the other terms appear with their tid in the root.

Is there any way to remove the other terms from the output in an early stage so they don't have to be processed that much?

Thanks!
sb

giorgio79’s picture

Core taxonomy functions in Drupal don't scale. But, there are contrib solutions. Have you tried this? http://drupal.org/project/taxonomy_edge

giorgio79’s picture

Core taxonomy functions in Drupal don't scale. But, there are contrib solutions. Have you tried this? http://drupal.org/project/taxonomy_edge or http://drupal.org/project/lineage

stephanbauer’s picture

Hi,

this is a bit hacked together but it works fine for me. It's very specific, checking the database and not using i18n functions for term values etc.

function mymodule_facetapi_facet_info_alter(array &$facet_info, array $searcher_info) {
  // Change callback functions for big taxonomy facets.
  $facet_info['im_field_terms_x']['map callback'] = 'mymodule_map_taxonomy_terms';
  $facet_info['im_field_terms_x']['hierarchy callback'] = 'mymodule_get_taxonomy_hierarchy';
}

// Map callback.
function mymodule_map_taxonomy_terms(array $values, array $facet) {
  $visible_values = mymodule_get_visible_values();

  global $language;
  $map = array();
  if ($language->language == 'en') {
    foreach($visible_values as $tid) {
      $result = db_query("SELECT name FROM taxonomy_term_data WHERE tid = :tid", array(':tid' => $tid));
      if ($item = $result->fetchAssoc())
        $map[$tid] = $item['name'];
    }
  } else {
    foreach($visible_values as $tid) {
      // Look for a translation.
      $result = db_query("SELECT translation FROM locales_source s INNER JOIN locales_target t ON s.lid = t.lid WHERE s.context = :context", array(':context' => 'term:'.$tid.':name'));
      if ($item = $result->fetchAssoc()) {
        $map[$tid] = $item['translation'];
      } else {
        // Get untranlated term name.
        $result = db_query("SELECT name FROM taxonomy_term_data WHERE tid = :tid", array(':tid' => $tid));
        if ($item = $result->fetchAssoc())
          $map[$tid] = $item['name'];
      }
    }
  }
  return $map;
}

// Taxonomy hierarchy callback.
function mymodule_get_taxonomy_hierarchy(array $values) {
  $visible_values = mymodule_get_visible_values();

  $result = db_select('taxonomy_term_hierarchy', 'th')
    ->fields('th', array('tid', 'parent'))
    ->condition('th.parent', '0', '>')
    ->condition(db_or()
      ->condition('th.tid', $visible_values, 'IN')
      ->condition('th.parent', $visible_values, 'IN')
    )
    ->execute();

  foreach ($result as $record) {
    $parents[$record->tid] = array($record->parent);
  }
  return $parents;
}

// Get visible values.
function mymodule_get_visible_values(){
  // TODO: Can be tweaked to process even less items.
  $visible_values = &drupal_static(__FUNCTION__);

  if (!isset($visible_values)) {
    $active = array();
    if (isset($_GET['f'])) {
      foreach($_GET['f'] as $f) {
        if (strpos($f, "im_field_terms_x") === 0)
          $active[] = substr($f, 32);
      }
    }
    $visible_values = $active;
    foreach($active as $a) {
      $result = db_query("SELECT tid FROM taxonomy_term_hierarchy WHERE parent = :tid", array(':tid' => $a));
      foreach($result as $item)
        $visible_values[$item->tid] = $item->tid;
      $result = db_query("SELECT parent FROM taxonomy_term_hierarchy WHERE tid = :tid", array(':tid' => $a));
      if (($item = $result->fetchObject()) && $item->parent != 0) {
        $visible_values[$item->parent] = $item->parent;
        $result = db_query("SELECT tid FROM taxonomy_term_hierarchy WHERE parent = :tid", array(':tid' => $item->parent));
        foreach($result as $item)
          $visible_values[$item->tid] = $item->tid;
      }
    }
    $result = db_query("SELECT d.tid FROM taxonomy_term_hierarchy h INNER JOIN taxonomy_term_data d ON d.tid = h.tid WHERE d.vid = 2 AND h.parent = 0");
    foreach($result as $item)
      $visible_values[$item->tid] = $item->tid;
  }
  return $visible_values;
}

// theme_facetapi_link_inactive().
function mytheme_facetapi_link_inactive($variables) {
  // Check if is numeric (because it's not processed by the mapping).
  if (is_numeric($variables['text'])) {
    if (isset($variables['options']['query']['f'])) {
      // Check if this is the correct taxonomy.
      if (strpos($variables['options']['query']['f'][count($variables['options']['query']['f'])-1], 'im_field_terms_x') === 0) {
        // Return FALSE so it can be easly identified in theme_list_item().
        return FALSE;
      }
    }
  }
  // Run theme_facetapi_link_inactive() for other items.
  return theme_facetapi_link_inactive($variables);
}

// theme_item_list().
function mytheme_item_list($variables) {
  // Check the ID to make sure this it the list we want to change.
  if (isset($variables['attributes']['id']) && $variables['attributes']['id'] == 'facetapi-facet-apachesolrsolr-block-im-field-terms-x') {
    foreach($variables['items'] as $key => $value) {
      // Remove items without 'data' (see mytheme_facetapi_link_inactive()).
      if (!$value['data'])
        unset($variables['items'][$key]);
    }
  }
  // Theme items.
  return theme_item_list($variables);
}