Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

changes from performance blog post #46

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Binary file added _METRICS/performance metrics.xls
Binary file not shown.
59 changes: 59 additions & 0 deletions _PATCHES/1930960-92-block_cache_node_grant_bypass-d7.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
diff --git a/modules/block/block.module b/modules/block/block.module
index 2977ca8..b6a7332 100644
--- a/modules/block/block.module
+++ b/modules/block/block.module
@@ -848,10 +848,19 @@ function block_block_list_alter(&$blocks) {
* An array of visible blocks as expected by drupal_render().
*/
function _block_render_blocks($region_blocks) {
- // Block caching is not compatible with node access modules. We also
- // preserve the submission of forms in blocks, by fetching from cache only
+ $cacheable = TRUE;
+
+ // We preserve the submission of forms in blocks, by fetching from cache only
// if the request method is 'GET' (or 'HEAD').
- $cacheable = !count(module_implements('node_grants')) && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD');
+ if ($_SERVER['REQUEST_METHOD'] != 'GET' && $_SERVER['REQUEST_METHOD'] != 'HEAD') {
+ $cacheable = FALSE;
+ }
+ // Block caching is not usually compatible with node access modules, so by
+ // default it is disabled when node access modules exist. However, it can be
+ // allowed by using the variable 'block_cache_bypass_node_grants'.
+ elseif (!variable_get('block_cache_bypass_node_grants', FALSE) && count(module_implements('node_grants'))) {
+ $cacheable = FALSE;
+ }

// Proceed to loop over all blocks in order to compute their respective cache
// identifiers; this allows us to do one single cache_get_multiple() call
@@ -1054,7 +1063,7 @@ function block_menu_delete($menu) {
* Implements hook_form_FORM_ID_alter().
*/
function block_form_system_performance_settings_alter(&$form, &$form_state) {
- $disabled = count(module_implements('node_grants'));
+ $disabled = (!variable_get('block_cache_bypass_node_grants', FALSE) && count(module_implements('node_grants')));
$form['caching']['block_cache'] = array(
'#type' => 'checkbox',
'#title' => t('Cache blocks'),
diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php
index 580cc38..e296b09 100644
--- a/sites/default/default.settings.php
+++ b/sites/default/default.settings.php
@@ -433,6 +433,18 @@ ini_set('session.cookie_lifetime', 2000000);
# $conf['js_gzip_compression'] = FALSE;

/**
+ * Block caching:
+ *
+ * Block caching may not be compatible with node access modules depending on
+ * how the original block cache policy is defined by the module that provides
+ * the block. By default, Drupal therefore disables block caching when one or
+ * more modules implement hook_node_grants(). If you consider block caching to
+ * be safe on your site and want to bypass this restriction, uncomment the line
+ * below.
+ */
+# $conf['block_cache_bypass_node_grants'] = TRUE;
+
+/**
* String overrides:
*
* To override specific strings on your site with or without enabling the Locale
121 changes: 121 additions & 0 deletions _PATCHES/D7-2263365-17a-module_implements.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
diff --git a/includes/module.inc b/includes/module.inc
index fe2a980..0c06609 100644
--- a/includes/module.inc
+++ b/includes/module.inc
@@ -676,17 +676,21 @@ function module_hook($module, $hook) {
/**
* Determines which modules are implementing a hook.
*
- * @param $hook
+ * Lazy-loaded include files specified with "group" via hook_hook_info() or
+ * hook_module_implements_alter() will be automatically included as part of
+ * module_implements(*, *, FALSE).
+ *
+ * @param string $hook
* The name of the hook (e.g. "help" or "menu").
- * @param $sort
+ * @param bool $sort
* By default, modules are ordered by weight and filename, settings this option
* to TRUE, module list will be ordered by module name.
- * @param $reset
+ * @param bool $reset
* For internal use only: Whether to force the stored list of hook
* implementations to be regenerated (such as after enabling a new module,
* before processing hook_enable).
*
- * @return
+ * @return string[]|null
* An array with the names of the modules which are implementing this hook.
*
* @see module_implements_write_cache()
@@ -696,8 +700,10 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) {
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
$drupal_static_fast['implementations'] = &drupal_static(__FUNCTION__);
+ $drupal_static_fast['verified'] = &drupal_static(__FUNCTION__ . ':verified');
}
$implementations = &$drupal_static_fast['implementations'];
+ $verified = &$drupal_static_fast['verified'];

// We maintain a persistent cache of hook implementations in addition to the
// static cache to avoid looping through every module and every hook on each
@@ -711,14 +717,18 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) {
// per request.
if ($reset) {
$implementations = array();
+ $verified = array();
cache_set('module_implements', array(), 'cache_bootstrap');
drupal_static_reset('module_hook_info');
drupal_static_reset('drupal_alter');
cache_clear_all('hook_info', 'cache_bootstrap');
- return;
+ return NULL;
}

// Fetch implementations from cache.
+ // This happens on the first call to module_implements(*, *, FALSE) during a
+ // request, but also when $implementations have been reset, e.g. after
+ // module_enable().
if (empty($implementations)) {
$implementations = cache_get('module_implements', 'cache_bootstrap');
if ($implementations === FALSE) {
@@ -727,12 +737,17 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) {
else {
$implementations = $implementations->data;
}
+ // Forget all previously "verified" hooks, in case that $implementations
+ // were cleared via drupal_static_reset('module_implements') instead of
+ // module_implements(*, *, TRUE).
+ $verified = array();
}

if (!isset($implementations[$hook])) {
// The hook is not cached, so ensure that whether or not it has
// implementations, that the cache is updated at the end of the request.
$implementations['#write_cache'] = TRUE;
+ // Discover implementations for this hook.
$hook_info = module_hook_info();
$implementations[$hook] = array();
$list = module_list(FALSE, FALSE, $sort);
@@ -744,13 +759,31 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) {
$implementations[$hook][$module] = $include_file ? $hook_info[$hook]['group'] : FALSE;
}
}
- // Allow modules to change the weight of specific implementations but avoid
+ // Allow modules to change the weight of specific implementations, but avoid
// an infinite loop.
if ($hook != 'module_implements_alter') {
+ // Remember the implementations before hook_module_implements_alter().
+ $implementations_before = $implementations[$hook];
drupal_alter('module_implements', $implementations[$hook], $hook);
+ // Verify implementations that were added or modified.
+ foreach (array_diff_assoc($implementations[$hook], $implementations_before) as $module => $group) {
+ // If drupal_alter('module_implements') changed or added a $group, the
+ // respective file needs to be included.
+ if ($group) {
+ module_load_include('inc', $module, "$module.$group");
+ }
+ // If a new implementation was added, verify that the function exists.
+ if (!function_exists($module . '_' . $hook)) {
+ unset($implementations[$hook][$module]);
+ }
+ }
}
+ // Implementations for this hook are now "verified"
+ $verified[$hook] = TRUE;
}
- else {
+ elseif (!isset($verified[$hook])) {
+ // Implementations for this hook were in the cache, but they are not
+ // "verified" yet.
foreach ($implementations[$hook] as $module => $group) {
// If this hook implementation is stored in a lazy-loaded file, so include
// that file first.
@@ -769,6 +802,7 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) {
$implementations['#write_cache'] = TRUE;
}
}
+ $verified[$hook] = TRUE;
}

return array_keys($implementations[$hook]);
53 changes: 53 additions & 0 deletions _PATCHES/PATCHES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
DON’T HACK CORE!!!!

You’re right, you shouldn’t. That’s why these patches are documented changes
to Drupal core. These changes are all around performance boosting Drupal core

These performance patches should have 0 implication other then speed.
They have been selected based on community recommendations by experts and have
been A/B comparison tested to highlight that they are indeed speeding up Drupal
over it’s stock version.

Presserflow is a fork of Pressflow D7, a performance optimized Drupal core.


Kitchensink / APC
(applies https://www.drupal.org/node/1650930#comment-8437127 and other cache bin management techniques)
APC and Kitchensink metrics in this test use the following additions to standard settings.php in order to better utilize cache bin management in memory.
appended in settings.php

$conf['cache_prefix'] = 'dd1';
#$conf['apc_show_debug'] = TRUE;
$conf['cache_backends'][] = 'sites/all/modules/apc/drupal_apc_cache.inc';
# APC as default container, others are targetted per bin
$conf['cache_default_class'] = 'DrupalAPCCache';
# APC as default, so these can be commented out
$conf['cache_class_cache'] = 'DrupalAPCCache';
$conf['cache_class_cache_admin_menu'] = 'DrupalAPCCache';
$conf['cache_class_cache_block'] = 'DrupalAPCCache';
$conf['cache_class_cache_bootstrap'] = 'DrupalAPCCache';
$conf['cache_class_cache_entity_file'] = 'DrupalAPCCache';
$conf['cache_class_cache_entity_og_membership'] = 'DrupalAPCCache';
$conf['cache_class_cache_entity_og_membership_type'] = 'DrupalAPCCache';
$conf['cache_class_cache_field'] = 'DrupalAPCCache';
$conf['cache_class_cache_menu'] = 'DrupalAPCCache';
$conf['cache_class_cache_libraries'] = 'DrupalAPCCache';
$conf['cache_class_cache_token'] = 'DrupalAPCCache';
$conf['cache_class_cache_views'] = 'DrupalAPCCache';
$conf['cache_class_cache_path_breadcrumbs'] = 'DrupalAPCCache';
$conf['cache_class_cache_path'] = 'DrupalAPCCache';

# Default DB for the ones that change too frequently and are small
#$conf['cache_default_class'] = 'DrupalDatabaseCache';
# THIS MUST BE SERVED FROM DB FOR STABILITY
$conf['cache_class_cache_cis_connector'] = 'DrupalDatabaseCache';
$conf['cache_class_cache_drupal_org_user_data'] = 'DrupalDatabaseCache';
$conf['cache_class_cache_form'] = 'DrupalDatabaseCache';

// this is assuming all databases using this file operate off of default
// this should always be true of ELMSLN connected systems but just be aware
// of this in case your doing any prefixing or crazy stuff like connecting to
// multiple databases
$databases['default']['default']['init_commands'] = array(
'isolation' => "SET SESSION tx_isolation='READ-COMMITTED'"
);
16 changes: 16 additions & 0 deletions _PATCHES/drupal-1710656-3-skip-hidden-menu-items-D7.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
diff --git a/includes/menu.inc b/includes/menu.inc
index b25a374..666b880 100644
--- a/includes/menu.inc
+++ b/includes/menu.inc
@@ -1505,6 +1505,11 @@ function _menu_tree_check_access(&$tree) {
$new_tree = array();
foreach ($tree as $key => $v) {
$item = &$tree[$key]['link'];
+ // Do not load hidden menu items if not in active breadcrumb trail and
+ // user can't administer the menu.
+ if (!empty($item['hidden']) && empty($item['in_active_trail']) && !user_access('administer menu')) {
+ continue;
+ }
_menu_link_translate($item);
if ($item['access'] || ($item['in_active_trail'] && strpos($item['href'], '%') !== FALSE)) {
if ($tree[$key]['below']) {
82 changes: 82 additions & 0 deletions _PATCHES/drupal-2193149-3-cache_field-lock.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
diff --git a/modules/field/field.info.class.inc b/modules/field/field.info.class.inc
index 3b89898..f4f1f63 100644
--- a/modules/field/field.info.class.inc
+++ b/modules/field/field.info.class.inc
@@ -146,7 +146,10 @@ class FieldInfo {

// Save in "static" and persistent caches.
$this->fieldMap = $map;
- cache_set('field_info:field_map', $map, 'cache_field');
+ if (lock_acquire('field_info:field_map')) {
+ cache_set('field_info:field_map', $map, 'cache_field');
+ lock_release('field_info:field_map');
+ }

return $map;
}
@@ -174,7 +177,10 @@ class FieldInfo {
}

// Store in persistent cache.
- cache_set('field_info:fields', $this->fieldsById, 'cache_field');
+ if (lock_acquire('field_info:fields')) {
+ cache_set('field_info:fields', $this->fieldsById, 'cache_field');
+ lock_release('field_info:fields');
+ }
}

// Fill the name/ID map.
@@ -231,7 +237,10 @@ class FieldInfo {
}

// Store in persistent cache.
- cache_set('field_info:instances', $this->bundleInstances, 'cache_field');
+ if (lock_acquire('field_info:instances')) {
+ cache_set('field_info:instances', $this->bundleInstances, 'cache_field');
+ lock_release('field_info:instances');
+ }
}

$this->loadedAllInstances = TRUE;
@@ -419,7 +428,11 @@ class FieldInfo {
foreach ($instances as $instance) {
$cache['fields'][] = $this->fieldsById[$instance['field_id']];
}
- cache_set("field_info:bundle:$entity_type:$bundle", $cache, 'cache_field');
+
+ if (lock_acquire("field_info:bundle:$entity_type:$bundle")) {
+ cache_set("field_info:bundle:$entity_type:$bundle", $cache, 'cache_field');
+ lock_release("field_info:bundle:$entity_type:$bundle");
+ }

return $instances;
}
@@ -460,7 +473,10 @@ class FieldInfo {

// Store in the 'static' and persistent caches.
$this->bundleExtraFields[$entity_type][$bundle] = $info;
- cache_set("field_info:bundle_extra:$entity_type:$bundle", $info, 'cache_field');
+ if (lock_acquire("field_info:bundle_extra:$entity_type:$bundle")) {
+ cache_set("field_info:bundle_extra:$entity_type:$bundle", $info, 'cache_field');
+ lock_release("field_info:bundle_extra:$entity_type:$bundle");
+ }

return $this->bundleExtraFields[$entity_type][$bundle];
}
diff --git a/modules/field/field.info.inc b/modules/field/field.info.inc
index 02b3c9c..dea2fd4 100644
--- a/modules/field/field.info.inc
+++ b/modules/field/field.info.inc
@@ -223,7 +223,11 @@ function _field_info_collate_types($reset = FALSE) {
}
drupal_alter('field_storage_info', $info['storage types']);

- cache_set("field_info_types:$langcode", $info, 'cache_field');
+ // Set the cache if we can acquire a lock.
+ if (lock_acquire("field_info_types:$langcode")) {
+ cache_set("field_info_types:$langcode", $info, 'cache_field');
+ lock_release("field_info_types:$langcode");
+ }
}
}

55 changes: 55 additions & 0 deletions _PATCHES/drupal-2222635-26-rename-truncate.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
diff --git a/includes/database/query.inc b/includes/database/query.inc
index 8af91c2..9e73b4c 100644
--- a/includes/database/query.inc
+++ b/includes/database/query.inc
@@ -930,6 +930,38 @@ class TruncateQuery extends Query {
* Return value is dependent on the database type.
*/
public function execute() {
+ // Keep track of the tables that will be truncated.
+ $tables_to_truncate = &drupal_static('TruncateQuery::execute');
+
+ // NoOp if table is empty.
+ $table_name = $this->connection->escapeTable($this->table);
+ $query = db_select($table_name);
+ $query->addExpression('COUNT(*)');
+ $count = $query->execute()->fetchField();
+ if ($count == 0) {
+ return $this;
+ }
+
+ // Renaming tables is a lot faster than truncate. Rename and then Truncate
+ // at the end of the request if not in a transaction.
+ if (!$this->connection->inTransaction()) {
+ // Make sure truncated table exists before trying to use it.
+ db_query('CREATE TABLE IF NOT EXISTS {' . $table_name . '__truncated_table} LIKE {' . $table_name . '};');
+
+ // Remove any values from the *__truncated_table if needed.
+ $query = db_select($table_name . '__truncated_table');
+ $query->addExpression('COUNT(*)');
+ $count = $query->execute()->fetchField();
+ if ($count > 0) {
+ db_query('TRUNCATE {' . $table_name . '__truncated_table}');
+ }
+
+ // Run TRUNCATE at the end of this request.
+ if (empty($tables_to_truncate[$table_name . '__truncated_table'])) {
+ $tables_to_truncate[$table_name . '__truncated_table'] = TRUE;
+ drupal_register_shutdown_function('db_query', 'TRUNCATE {' . $table_name . '__truncated_table}');
+ }
+ }
return $this->connection->query((string) $this, array(), $this->queryOptions);
}

@@ -952,7 +984,10 @@ class TruncateQuery extends Query {
return $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '}';
}
else {
- return $comments . 'TRUNCATE {' . $this->connection->escapeTable($this->table) . '} ';
+ $table_name = $this->connection->escapeTable($this->table);
+ // Use rename so the truncate happens at the end of this request.
+ $sql = $comments . 'RENAME TABLE {' . $table_name . '} TO {' . $table_name . '__temp_table}, {' . $table_name . '__truncated_table} TO {' . $table_name . '}, {' . $table_name . '__temp_table} TO {' . $table_name . '__truncated_table} ; ';
+ return $sql;
}
}
}
Loading