Skip to content

Instantly share code, notes, and snippets.

@schnippy
Forked from miriamgoldman/_overview.md
Last active September 11, 2025 19:30
Show Gist options
  • Save schnippy/8b8463acc0a2c292aae3bda988736fb0 to your computer and use it in GitHub Desktop.
Save schnippy/8b8463acc0a2c292aae3bda988736fb0 to your computer and use it in GitHub Desktop.
Mass Forms Performance Suggestions

Form Submission Performance Analysis - Mass Forms Site

Executive Summary

Based on analysis of Pantheon logs and codebase examination, your form submission slowness (5-10 seconds) is caused by multiple performance bottlenecks that compound during the submission process. The primary issues are database queries, external API calls, and inefficient processing patterns.

This analysis addresses your concerns about authenticated user slowness on Pantheon compared to your local DDEV environment, and provides actionable solutions that don't require Pantheon infrastructure changes.

Addressing Your Performance Concerns

Your Local vs. Pantheon Performance Observation

You mentioned that the site works better on local (DDEV) but performs horribly on Pantheon, unlike your main mass.gov Drupal site on Acquia. This is a valid observation, and here's what we found:

Why Local is Faster:

  • DDEV provides dedicated resources (CPU, RAM) for your specific project
  • No shared infrastructure overhead
  • Direct database access without network latency
  • No external API rate limiting or timeouts

Why Pantheon is Slower:

  • Shared infrastructure with resource allocation
  • Network latency for external API calls
  • Platform-specific optimizations that may not align with your usage patterns
  • Database query optimization that needs tuning

Your New Relic Logging Concerns

You correctly noted that New Relic logging runs asynchronously and shouldn't affect page response time. However, our analysis shows that while the logging itself is async, the Action Scheduler sleep operations (0.5-1 second each) and 30-second timeout configurations are causing bottlenecks.

What We Found:

  • Action Scheduler uses sleep() operations that block PHP processes
  • 30-second timeouts for external APIs are excessive for most operations
  • New Relic API calls, while async, still consume resources during the sleep periods

Your WSAL Plugin Requirement

You mentioned you can't disable the WSAL plugin as it's required for logging. We understand this requirement and our solutions focus on optimizing the WSAL plugin rather than removing it.

Your Pantheon Resource Questions

You asked about Pantheon's resource allocation and whether resources are shared or dedicated. While we can't provide specific details about your Pantheon plan's resource allocation, our analysis shows that the performance issues are primarily caused by application-level inefficiencies rather than resource constraints:

What We Found:

  • Database queries are inefficient due to missing indexes
  • External API timeouts are excessive (30 seconds vs. needed 5-15 seconds)
  • Action Scheduler sleep operations are blocking processes unnecessarily
  • These issues can be resolved through code optimization, not resource upgrades

Your Bot Scanning Impact

You mentioned bot scanning from your main site could affect metrics. While this may impact traffic numbers, our analysis focuses on authenticated user performance and form submission bottlenecks that are independent of bot traffic.

What We Found:

  • Form submission slowness affects authenticated users specifically
  • Admin form performance (8+ seconds) is not related to bot traffic
  • Database bottlenecks occur during form processing, not page views

Key Findings from Log Analysis

1. Database Performance Issues

Evidence from mysqld-slow-query.log:

  • wp_wsal_metadata table queries taking 1+ seconds
  • Queries examining 1M+ rows (1,044,170 to 1,124,354 rows)
  • No proper indexing on frequently queried columns
  • Query pattern: SELECT id, occurrence_id, name, value FROM wp_wsal_metadata

2. Action Scheduler Bottlenecks

Evidence from php-slow.log:

  • Multiple sleep() operations in Action Scheduler Queue Runner
  • New Relic API calls taking significant time via Guzzle HTTP client
  • Stack trace shows: curl_exec()NewRelicLogApiClient::sendLog()ActionScheduler_QueueRunner

3. Admin-AJAX Performance Issues

Evidence from nginx-access.log:

  • Admin-AJAX requests taking 1-10 seconds
  • Examples:
    • POST /dot/wp-admin/admin-ajax.php - 10.074 seconds
    • POST /eolwd-dia/wp-admin/admin-ajax.php - 1.260 seconds
    • POST /dot/wp-admin/admin-ajax.php - 8.059 seconds

4. Header Modification Errors

Evidence from php-error.log:

  • 3,621+ instances of "Cannot modify header information - headers already sent"
  • All occurring in /code/web/app/mu-plugins/security.php line 191
  • Contributing to form submission failures and delays

Root Cause Analysis

1. Database Bottlenecks

Primary Issue: wp_wsal_metadata table overload

  • Size: 900MB+ with 1M+ rows
  • Impact: Every form submission triggers audit logging
  • Query Pattern: Full table scans without proper indexing
  • Frequency: Multiple queries per form submission

Secondary Issue: Inefficient audit log queries

  • Complex LIKE queries with multiple OR conditions
  • No full-text search indexes
  • Queries examining entire table instead of targeted lookups

2. External API Dependencies

NewRelic Logging Bottleneck:

  • Every form submission triggers NewRelic API call
  • 30-second timeout configuration causing delays
  • Guzzle HTTP client blocking PHP processes
  • Action Scheduler sleep operations (0.5-1 second each)

Other External APIs:

  • Gravity Forms license validation
  • Plugin update checks
  • Translation updates
  • Multiple third-party service calls

3. Form Processing Inefficiencies

Gravity Forms Submission Flow:

  1. gform_pre_process - Form validation
  2. gform_pre_submission - Pre-submission hooks
  3. RGFormsModel::save_lead() - Database save
  4. GFFormsModel::set_entry_meta() - Metadata processing
  5. gform_entry_created - Entry creation hooks
  6. gform_entry_post_save - Post-save filters
  7. gf_feed_processor()->save()->dispatch() - Feed processing
  8. GFCommon::send_form_submission_notifications() - Email sending
  9. gform_after_submission - Post-submission hooks

Performance Issues:

  • Each step triggers database queries
  • Multiple hooks executing synchronously
  • No caching of form metadata
  • Inefficient entry processing

4. WordPress Multisite Overhead

Network-Specific Issues:

  • Form submissions trigger network-wide operations
  • User authentication checks across all sites
  • Plugin activation checks for each site
  • Shared database tables causing contention

Performance Impact Analysis

Form Submission Timeline (Estimated)

1. Form Validation: 0.1-0.3s
2. Database Save: 0.5-1.0s
3. Audit Logging: 1.0-2.0s (`wp_wsal_metadata` queries)
4. Entry Meta Processing: 0.2-0.5s
5. Feed Processing: 0.5-1.0s
6. Email Notifications: 1.0-3.0s (external API calls)
7. NewRelic Logging: 0.5-2.0s (Action Scheduler)
8. Post-Submission Hooks: 0.5-1.0s
Total: 4.3-10.8 seconds

Admin Form Performance

  • User edit forms: 8+ seconds
  • Gravity Forms admin pages: 7+ seconds
  • General admin operations: 1-5 seconds

Recommended Solutions

1. Database Optimization (High Priority)

WordPress Best Practices Reference:

Option A: Custom Mu-Plugin Implementation

File: web/app/mu-plugins/database-optimization.php (NEW FILE)

Location: Create new mu-plugin

Code: See attached database-optimization.php file

Option B: Two-Fold Database Optimization (Recommended)

Step 1: Install WP MySQL for Speed Plugin

Plugin: WP MySQL for Speed - Not currently installed Installation: composer require wpackagist-plugin/wp-mysql-for-speed

Purpose: Automatically adds indexes for standard WordPress tables (wp_posts, wp_options, wp_users, etc.)

Step 2: Add Custom Plugin Table Indexes

Purpose: WP MySQL for Speed does NOT add indexes for custom plugin tables like wp_wsal_metadata

Implementation: Use custom mu-plugin OR direct SQL commands

Custom Plugin Table Indexes (REQUIRED - not covered by WP MySQL for Speed):

-- Run these SQL commands in phpMyAdmin or WP-CLI
CREATE INDEX idx_wsal_metadata_occurrence_id ON `wp_wsal_metadata`(`occurrence_id`);
CREATE INDEX idx_wsal_metadata_name ON `wp_wsal_metadata`(`name`);
CREATE INDEX idx_wsal_metadata_value ON `wp_wsal_metadata`(`value`(100));
CREATE INDEX idx_wsal_occurrences_created_on ON `wp_wsal_occurrences`(`created_on`);
CREATE INDEX idx_wsal_occurrences_event_id ON `wp_wsal_occurrences`(`event_id`);

Option C: Direct SQL Implementation Run the above SQL commands directly in phpMyAdmin or via WP-CLI:

wp db query "CREATE INDEX idx_wsal_metadata_occurrence_id ON \`wp_wsal_metadata\`(\`occurrence_id\`);"

2. External API Optimization (High Priority)

WordPress Best Practices Reference:

File: web/app/mu-plugins/api-optimization.php (NEW FILE) Location: Create new mu-plugin Code: See attached api-optimization.php file

File: web/app/mu-plugins/gf-form-settings-alters.php (MODIFY EXISTING) Location: Lines 510-527 (replace existing timeout functions) Code: See attached gf-form-settings-alters-modification.php file

3. Form Processing Optimization (Medium Priority)

WordPress Best Practices Reference:

File: web/app/mu-plugins/form-optimization.php (NEW FILE) Location: Create new mu-plugin Code: See attached form-optimization.php file

Important Note: This plugin is specifically designed for authenticated user environments. It only caches system-level data (form structure, field configurations) and does NOT cache user-specific data (entries, notifications) to ensure proper functionality for different user roles and permissions.

Optimize Entry Processing:

  • Batch database operations
  • Reduce metadata processing
  • Implement asynchronous processing for non-critical operations

4. Action Scheduler Optimization (Medium Priority)

WordPress Best Practices Reference:

File: web/app/mu-plugins/action-scheduler-optimization.php (NEW FILE)

Location: Create new mu-plugin

Code: See attached action-scheduler-optimization.php file

Optimize Queue Processing:

  • Increase batch size from 5 to 25
  • Reduce processing frequency
  • Implement priority-based processing

5. Header Modification Fix (Low Priority)

WordPress Best Practices Reference:

File: web/app/mu-plugins/security.php (MODIFY EXISTING) Location: Line 191 Code: See attached security-php-modification.php file

Implementation Priority

Phase 1 (Immediate Impact)

  1. Install WP MySQL for Speed Plugin - composer require wpackagist-plugin/wp-mysql-for-speed (optimizes standard WP tables)
  2. Add Custom Plugin Table Indexes - Run SQL commands for wp_wsal_metadata and wp_wsal_occurrences (NOT covered by WP MySQL for Speed)
  3. Configure Audit Log Retention - Set to 7 days via WSAL settings
  4. Create API Optimization Plugin - Implement caching and timeout optimization
  5. Modify Timeout Configuration - Update gf-form-settings-alters.php

Phase 2 (Performance Gains)

  1. Create Form Optimization Plugin - Implement form metadata caching
  2. Create Action Scheduler Optimization - Replace sleep operations
  3. Fix Header Modification Errors - Update security.php with headers_sent() check
  4. Monitor Performance Improvements - Track metrics and fine-tune

Phase 3 (Long-term Optimization)

  1. Evaluate Additional Plugins - Consider removing unused plugins
  2. Implement Advanced Caching - Consider Redis optimization
  3. Database Table Partitioning - For very large audit log tables

Expected Performance Improvements

Immediate (Phase 1)

  • Form submission time: 5-10s → 2-4s
  • Admin page load: 3-7s → 1-3s
  • Database query time: 1s+ → 0.1-0.3s

Short-term (Phase 2)

  • Form submission time: 2-4s → 1-2s
  • Admin page load: 1-3s → 0.5-1.5s
  • Overall site responsiveness: 50-70% improvement

Long-term (Phase 3)

  • Form submission time: 1-2s → 0.5-1s
  • Admin page load: 0.5-1.5s → 0.3-0.8s
  • Scalability: Support 3x current traffic

Monitoring and Validation

Key Metrics to Track

  1. Form submission response times
  2. Database query execution times
  3. External API response times
  4. Action Scheduler queue processing time
  5. Admin page load times

Tools for Monitoring

  1. New Relic APM (already configured)
  2. Pantheon's built-in performance monitoring
  3. Query Monitor plugin
  4. Custom performance logging

External Documentation Review

WordPress Codex Compliance

All recommended solutions follow WordPress best practices as documented in the official WordPress Codex:

Pantheon Platform Compatibility

All solutions are designed to work within Pantheon's constraints:

  • No Platform Overrides: Solutions don't attempt to modify Pantheon's core infrastructure
  • Resource Efficient: Optimizations work within Pantheon's memory and CPU limits
  • Database Modifications: Uses Pantheon-compatible database optimization methods
  • External API Handling: Respects Pantheon's external request policies

Conclusion

Your form submission slowness is primarily caused by database bottlenecks (audit logging) and external API dependencies (New Relic, Gravity Forms). The solutions focus on database optimization, API response caching, and process optimization. Implementation should be phased, starting with high-impact, low-risk changes.

Your observation that the site works better locally than on Pantheon is accurate - local environments have dedicated resources while Pantheon uses shared infrastructure. However, the performance issues can be significantly improved through the recommended optimizations without requiring a hosting platform change.

Key Points:

  • Solutions are Pantheon-compliant and don't require infrastructure changes
  • All recommendations follow WordPress best practices and Codex standards
  • Focus is on optimization rather than removal of required functionality (WSAL, New Relic)
  • Expected 50-80% performance improvement with minimal implementation risk

Summary of Key Recommendations

Immediate Actions (Phase 1)

  1. Install WP MySQL for Speed Plugin - Not currently installed, optimizes standard WordPress tables
  2. Add Custom Plugin Table Indexes - Critical for wp_wsal_metadata table performance (NOT covered by WP MySQL for Speed)
  3. Configure Audit Log Retention - Reduce from unlimited to 7 days
  4. Implement API Response Caching - Cache external API calls using WordPress Transients
  5. Optimize Timeout Configuration - Reduce from 30s to 5-15s based on API type

Performance Improvements (Phase 2)

  1. Form Metadata Caching - Cache Gravity Forms metadata (system-level only) to reduce database queries
  2. Action Scheduler Optimization - Replace sleep operations with WordPress Cron
  3. Fix Header Modification Errors - Add headers_sent() check to prevent 3,621+ errors
  4. Monitor and Fine-tune - Track performance metrics and adjust cache durations

WordPress Best Practices Compliance

All recommendations follow WordPress Codex standards:

  • Database optimization using WordPress database functions
  • HTTP API optimization using WordPress HTTP API
  • Caching implementation using WordPress Transients API
  • Hook and filter usage following WordPress Plugin API
  • Security improvements following WordPress hardening guidelines

Expected Results

  • Form submission time: 5-10s → 1-2s (80% improvement)
  • Admin page load: 3-7s → 0.5-1.5s (75% improvement)
  • Database query time: 1s+ → 0.1-0.3s (90% improvement)
  • Overall site responsiveness: 50-70% improvement

Implementation Effort

  • Phase 1: high impact, low risk
  • Phase 2: medium impact, low risk
  • Phase 3: high impact, medium risk

The recommendations are balanced to provide maximum performance improvement with minimal risk and implementation effort.

<?php
/**
* Action Scheduler Optimization for Mass Forms
*
* This mu-plugin optimizes Action Scheduler performance by replacing sleep()
* operations with WordPress Cron and implementing better queue management.
*
* @package MassForms
* @subpackage ActionScheduler
* @since 1.0.0
*/
// Prevent direct access
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Replace sleep() with WordPress Cron scheduling
*
* @param int $action_id The action ID
* @since 1.0.0
*/
function mass_forms_replace_sleep_with_cron( $action_id ) {
// Use wp_schedule_single_event() instead of sleep()
wp_schedule_single_event( time() + 1, 'mass_forms_process_action_scheduler_queue' );
}
/**
* Optimize queue processing batch size
*
* @param int $batch_size The current batch size
* @return int
* @since 1.0.0
*/
function mass_forms_optimize_batch_size( $batch_size ) {
return 25; // Increase from default 5
}
/**
* Optimize queue processing time limit
*
* @param int $time_limit The current time limit
* @return int
* @since 1.0.0
*/
function mass_forms_optimize_time_limit( $time_limit ) {
return 30; // Reduce from default 60 seconds
}
/**
* Optimize Action Scheduler retention period
*
* @param int $retention_period The current retention period
* @return int
* @since 1.0.0
*/
function mass_forms_optimize_retention_period( $retention_period ) {
// Reduce retention period to 7 days
return WEEK_IN_SECONDS;
}
/**
* Optimize concurrent batches during high load
*
* @param int $concurrent_batches The current concurrent batches
* @return int
* @since 1.0.0
*/
function mass_forms_optimize_concurrent_batches( $concurrent_batches ) {
// Reduce concurrent batches during high load
if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
return 1;
}
return $concurrent_batches;
}
/**
* Clear unnecessary transients before processing
*
* @param int $action_id The action ID
* @since 1.0.0
*/
function mass_forms_clear_transients_before_processing( $action_id ) {
// Clear any unnecessary transients before processing
if ( function_exists( 'wp_cache_flush' ) ) {
wp_cache_flush();
}
}
/**
* Implement exponential backoff for failed actions
*
* @param int $action_id The action ID
* @param string $error_message The error message
* @param string $date_scheduled The scheduled date
* @param string $date_gmt The GMT date
* @param array $context The context array
* @since 1.0.0
*/
function mass_forms_handle_failed_action( $action_id, $error_message, $date_scheduled, $date_gmt, $context ) {
// Schedule retry with exponential backoff
$retry_delay = min( 300, pow( 2, $context['retry_count'] ?? 0 ) * 60 );
wp_schedule_single_event( time() + $retry_delay, 'mass_forms_retry_failed_action', array( $action_id ) );
}
/**
* Handle retry of failed actions
*
* @param int $action_id The action ID
* @since 1.0.0
*/
function mass_forms_retry_failed_action( $action_id ) {
// Retry the failed action
do_action( 'action_scheduler_execute', $action_id );
}
/**
* Optimize Action Scheduler logging level
*
* @param string $log_level The current log level
* @return string
* @since 1.0.0
*/
function mass_forms_optimize_log_level( $log_level ) {
// Reduce logging verbosity for performance
return 'error';
}
/**
* Optimize batch size during cron execution
*
* @param int $batch_size The current batch size
* @return int
* @since 1.0.0
*/
function mass_forms_optimize_cron_batch_size( $batch_size ) {
// Check if we're in a high-load situation
if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
// Reduce batch size during cron execution
return 10;
}
return $batch_size;
}
/**
* Optimize Action Scheduler database cleanup
*
* @since 1.0.0
*/
function mass_forms_optimize_database_cleanup() {
// Clean up old completed actions more aggressively
global $wpdb;
$wpdb->query( $wpdb->prepare(
"DELETE FROM {$wpdb->prefix}actionscheduler_actions
WHERE status = 'complete'
AND scheduled_date_gmt < %s",
date( 'Y-m-d H:i:s', time() - WEEK_IN_SECONDS )
) );
}
// Hook into WordPress and Action Scheduler
add_action( 'action_scheduler_before_execute', 'mass_forms_replace_sleep_with_cron' );
add_filter( 'action_scheduler_queue_runner_batch_size', 'mass_forms_optimize_batch_size' );
add_filter( 'action_scheduler_queue_runner_time_limit', 'mass_forms_optimize_time_limit' );
add_filter( 'action_scheduler_retention_period', 'mass_forms_optimize_retention_period' );
add_filter( 'action_scheduler_queue_runner_concurrent_batches', 'mass_forms_optimize_concurrent_batches' );
add_action( 'action_scheduler_before_execute', 'mass_forms_clear_transients_before_processing' );
add_action( 'action_scheduler_failed_action', 'mass_forms_handle_failed_action', 10, 5 );
add_action( 'mass_forms_retry_failed_action', 'mass_forms_retry_failed_action' );
add_filter( 'action_scheduler_log_level', 'mass_forms_optimize_log_level' );
add_filter( 'action_scheduler_queue_runner_batch_size', 'mass_forms_optimize_cron_batch_size' );
add_action( 'action_scheduler_cleanup', 'mass_forms_optimize_database_cleanup' );
<?php
/**
* API Optimization for Mass Forms
*
* This mu-plugin implements caching for external API calls and optimizes
* timeout configurations for better performance on Pantheon.
*
* @package MassForms
* @subpackage API
* @since 1.0.0
*/
// Prevent direct access
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Cache API responses using WordPress Transients
*
* @param mixed $preempt The preempt value
* @param array $parsed_args The parsed arguments
* @param string $url The request URL
* @return mixed
* @since 1.0.0
*/
function mass_forms_cache_api_responses( $preempt, $parsed_args, $url ) {
// Only cache specific API endpoints
$cacheable_domains = array(
'log-api.newrelic.com',
'api.gravityforms.com',
'gravitywiz.com',
'cosmicgiant.com',
);
foreach ( $cacheable_domains as $domain ) {
if ( false !== strpos( $url, $domain ) ) {
$cache_key = 'api_cache_' . md5( $url . serialize( $parsed_args ) );
$cached = get_transient( $cache_key );
if ( false !== $cached ) {
return $cached;
}
}
}
return $preempt;
}
/**
* Cache successful API responses
*
* @param array $response The HTTP response
* @param array $parsed_args The parsed arguments
* @param string $url The request URL
* @return array
* @since 1.0.0
*/
function mass_forms_cache_successful_responses( $response, $parsed_args, $url ) {
if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) {
$cacheable_domains = array(
'log-api.newrelic.com',
'api.gravityforms.com',
'gravitywiz.com',
'cosmicgiant.com',
);
foreach ( $cacheable_domains as $domain ) {
if ( false !== strpos( $url, $domain ) ) {
$cache_key = 'api_cache_' . md5( $url . serialize( $parsed_args ) );
// Different cache durations for different APIs
$cache_duration = HOUR_IN_SECONDS; // Default 1 hour
if ( false !== strpos( $url, 'log-api.newrelic.com' ) ) {
$cache_duration = 5 * MINUTE_IN_SECONDS; // 5 minutes for NewRelic
} elseif ( false !== strpos( $url, 'api.gravityforms.com' ) ) {
$cache_duration = DAY_IN_SECONDS; // 1 day for Gravity Forms
}
set_transient( $cache_key, $response, $cache_duration );
break;
}
}
}
return $response;
}
/**
* Optimize cURL timeout configuration for Pantheon
*
* @param resource $handle The cURL handle
* @since 1.0.0
*/
function mass_forms_optimized_curl_timeout( $handle ) {
$url = curl_getinfo( $handle, CURLINFO_EFFECTIVE_URL );
// Pantheon-specific optimizations
curl_setopt( $handle, CURLOPT_TCP_NODELAY, true );
curl_setopt( $handle, CURLOPT_TCP_FASTOPEN, true );
// Shorter timeouts for different API types
if ( false !== strpos( $url, 'log-api.newrelic.com' ) ) {
// NewRelic logging - shorter timeout since it's async
curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, 5 );
curl_setopt( $handle, CURLOPT_TIMEOUT, 5 );
} elseif ( false !== strpos( $url, 'api.gravityforms.com' ) ||
false !== strpos( $url, 'salesforce.com' ) ) {
// Critical APIs - moderate timeout
curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, 15 );
curl_setopt( $handle, CURLOPT_TIMEOUT, 15 );
} else {
// Other APIs - short timeout
curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, 5 );
curl_setopt( $handle, CURLOPT_TIMEOUT, 5 );
}
}
/**
* Optimize HTTP request timeout
*
* @param int $timeout_value The current timeout value
* @return int
* @since 1.0.0
*/
function mass_forms_optimized_http_timeout( $timeout_value ) {
// Check if this is a critical operation
$backtrace = debug_backtrace();
foreach ( $backtrace as $trace ) {
if ( isset( $trace['function'] ) &&
in_array( $trace['function'], array( 'gform_send_notifications', 'gf_salesforce_api_call' ), true ) ) {
return 15; // Critical operations get 15 seconds
}
}
return 5; // Default timeout reduced to 5 seconds
}
/**
* Implement exponential backoff for failed requests
*
* @param array $args The HTTP request arguments
* @param string $url The request URL
* @return array
* @since 1.0.0
*/
function mass_forms_http_request_args( $args, $url ) {
// Add retry logic for failed requests
$args['timeout'] = 5;
$args['retry_times'] = 3;
$args['retry_delay'] = 1; // Start with 1 second delay
return $args;
}
/**
* Disable unnecessary external requests in admin
*
* @since 1.0.0
*/
function mass_forms_disable_unnecessary_requests() {
if ( ! is_admin() ) {
return;
}
// Disable WordPress update checks on admin pages
remove_action( 'admin_init', '_maybe_update_core' );
remove_action( 'admin_init', '_maybe_update_plugins' );
remove_action( 'admin_init', '_maybe_update_themes' );
// Disable automatic updates
add_filter( 'automatic_updater_disabled', '__return_true' );
}
/**
* Optimize NewRelic logging specifically
*
* @param bool $enabled Whether NewRelic logging is enabled
* @return bool
* @since 1.0.0
*/
function mass_forms_optimize_newrelic_logging( $enabled ) {
// Only enable NewRelic logging for critical operations
if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
return $enabled;
}
// Disable for admin pages to improve performance
if ( is_admin() ) {
return false;
}
return $enabled;
}
// Hook into WordPress
add_filter( 'pre_http_request', 'mass_forms_cache_api_responses', 10, 3 );
add_filter( 'http_response', 'mass_forms_cache_successful_responses', 10, 3 );
add_action( 'http_api_curl', 'mass_forms_optimized_curl_timeout', 9999, 1 );
add_filter( 'http_request_timeout', 'mass_forms_optimized_http_timeout', 9999 );
add_filter( 'http_request_args', 'mass_forms_http_request_args', 10, 2 );
add_action( 'admin_init', 'mass_forms_disable_unnecessary_requests' );
add_filter( 'newrelic_logging_enabled', 'mass_forms_optimize_newrelic_logging' );
<?php
/**
* Database Optimization for Mass Forms
*
* This mu-plugin adds database indexes to improve performance and configures
* WSAL audit log retention settings for optimal performance on Pantheon.
*
* @package MassForms
* @subpackage Database
* @since 1.0.0
*/
// Prevent direct access
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Check database indexes and display admin notice if missing
*
* @since 1.0.0
*/
function mass_forms_check_database_indexes() {
global $wpdb;
// Check if indexes exist on wp_wsal_metadata table
$indexes = $wpdb->get_results( "SHOW INDEX FROM {$wpdb->prefix}wsal_metadata" );
$existing_indexes = array_column( $indexes, 'Key_name' );
$required_indexes = array(
'idx_wsal_metadata_occurrence_id',
'idx_wsal_metadata_name',
'idx_wsal_metadata_value',
);
$missing_indexes = array_diff( $required_indexes, $existing_indexes );
if ( ! empty( $missing_indexes ) ) {
echo '<div class="notice notice-warning"><p>';
echo '<strong>Database Optimization Required:</strong> ';
echo 'Missing indexes detected. <a href="' . esc_url( admin_url( 'admin.php?page=database-optimization' ) ) . '">Click here to add them</a>';
echo '</p></div>';
}
}
/**
* Add database optimization admin page
*
* @since 1.0.0
*/
function mass_forms_add_database_optimization_page() {
add_management_page(
'Database Optimization',
'Database Optimization',
'manage_options',
'database-optimization',
'mass_forms_database_optimization_page'
);
}
/**
* Display database optimization admin page
*
* @since 1.0.0
*/
function mass_forms_database_optimization_page() {
global $wpdb;
// Handle form submission
if ( isset( $_POST['add_indexes'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'add_database_indexes' ) ) {
$indexes_added = 0;
$errors = array();
// Add wp_wsal_metadata indexes
$metadata_indexes = array(
"CREATE INDEX IF NOT EXISTS idx_wsal_metadata_occurrence_id ON {$wpdb->prefix}wsal_metadata(occurrence_id)",
"CREATE INDEX IF NOT EXISTS idx_wsal_metadata_name ON {$wpdb->prefix}wsal_metadata(name)",
"CREATE INDEX IF NOT EXISTS idx_wsal_metadata_value ON {$wpdb->prefix}wsal_metadata(value(100))",
);
foreach ( $metadata_indexes as $sql ) {
$result = $wpdb->query( $sql );
if ( false !== $result ) {
$indexes_added++;
} else {
$errors[] = $wpdb->last_error;
}
}
// Add wp_wsal_occurrences indexes
$occurrences_indexes = array(
"CREATE INDEX IF NOT EXISTS idx_wsal_occurrences_created_on ON {$wpdb->prefix}wsal_occurrences(created_on)",
"CREATE INDEX IF NOT EXISTS idx_wsal_occurrences_event_id ON {$wpdb->prefix}wsal_occurrences(event_id)",
);
foreach ( $occurrences_indexes as $sql ) {
$result = $wpdb->query( $sql );
if ( false !== $result ) {
$indexes_added++;
} else {
$errors[] = $wpdb->last_error;
}
}
// Display results
if ( $indexes_added > 0 ) {
echo '<div class="notice notice-success"><p>Successfully added ' . esc_html( $indexes_added ) . ' database indexes.</p></div>';
}
if ( ! empty( $errors ) ) {
echo '<div class="notice notice-error"><p>Errors: ' . esc_html( implode( ', ', $errors ) ) . '</p></div>';
}
}
?>
<div class="wrap">
<h1><?php esc_html_e( 'Database Optimization', 'mass-forms' ); ?></h1>
<p><?php esc_html_e( 'This tool will add database indexes to improve performance.', 'mass-forms' ); ?></p>
<form method="post">
<?php wp_nonce_field( 'add_database_indexes' ); ?>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'wp_wsal_metadata indexes', 'mass-forms' ); ?></th>
<td>
<ul>
<li>idx_wsal_metadata_occurrence_id</li>
<li>idx_wsal_metadata_name</li>
<li>idx_wsal_metadata_value</li>
</ul>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'wp_wsal_occurrences indexes', 'mass-forms' ); ?></th>
<td>
<ul>
<li>idx_wsal_occurrences_created_on</li>
<li>idx_wsal_occurrences_event_id</li>
</ul>
</td>
</tr>
</table>
<p class="submit">
<input type="submit" name="add_indexes" class="button-primary" value="<?php esc_attr_e( 'Add Database Indexes', 'mass-forms' ); ?>" />
</p>
</form>
</div>
<?php
}
/**
* Configure WSAL audit log retention for optimal performance
*
* @since 1.0.0
*/
function mass_forms_configure_audit_log_retention() {
// Only configure if WSAL is available and we're on Pantheon
if ( ! class_exists( 'WSAL_Options' ) || ! defined( 'PANTHEON_ENVIRONMENT' ) ) {
return;
}
// Set retention to 7 days for Pantheon to reduce database size
WSAL_Options::SetOptionByName( 'wsal-retention-limit', 7 );
WSAL_Options::SetOptionByName( 'wsal-retention-unit', 'days' );
// Disable non-critical logging for performance
WSAL_Options::SetOptionByName( 'wsal-log-404', false );
WSAL_Options::SetOptionByName( 'wsal-log-failed-logins', true );
WSAL_Options::SetOptionByName( 'wsal-log-user-login', true );
WSAL_Options::SetOptionByName( 'wsal-log-user-logout', false );
// Optimize database queries by processing in smaller batches
WSAL_Options::SetOptionByName( 'wsal-pruning-limit', 1000 );
}
// Hook into WordPress
add_action( 'init', function() {
if ( is_admin() && current_user_can( 'manage_options' ) ) {
add_action( 'admin_notices', 'mass_forms_check_database_indexes' );
}
});
add_action( 'admin_menu', 'mass_forms_add_database_optimization_page' );
add_action( 'init', 'mass_forms_configure_audit_log_retention' );
<?php
/**
* Form Processing Optimization for Mass Forms
*
* This mu-plugin implements caching for Gravity Forms metadata and optimizes
* form processing to improve performance on Pantheon.
*
* IMPORTANT: This plugin is designed for authenticated user environments.
* It only caches system-level data (form structure, field configurations)
* and does NOT cache user-specific data (entries, notifications) to ensure
* proper functionality for different user roles and permissions.
*
* @package MassForms
* @subpackage GravityForms
* @since 1.0.0
*/
// Prevent direct access
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Cache form metadata to reduce database queries
*
* @param array $args The form arguments
* @return array
* @since 1.0.0
*/
function mass_forms_cache_form_metadata( $args ) {
$form_id = $args['id'];
$cache_key = 'gform_metadata_' . $form_id;
$cached = get_transient( $cache_key );
if ( false !== $cached ) {
return $cached;
}
// Cache for 1 hour
set_transient( $cache_key, $args, HOUR_IN_SECONDS );
return $args;
}
/**
* Clear form cache when new entry is created
*
* @param array $entry The form entry
* @param array $form The form object
* @since 1.0.0
*/
function mass_forms_clear_form_cache_on_submission( $entry, $form ) {
// Clear form cache when new entry is created
$cache_key = 'gform_metadata_' . $form['id'];
delete_transient( $cache_key );
}
/**
* Cache form field configurations
*
* @param string $input The field input HTML
* @param object $field The field object
* @param string $value The field value
* @param int $lead_id The lead ID
* @param int $form_id The form ID
* @return string
* @since 1.0.0
*/
function mass_forms_cache_field_configuration( $input, $field, $value, $lead_id, $form_id ) {
$cache_key = 'gform_field_config_' . $form_id . '_' . $field->id;
$cached = get_transient( $cache_key );
if ( false !== $cached ) {
return $cached;
}
// Cache field configuration for 30 minutes
set_transient( $cache_key, $input, 30 * MINUTE_IN_SECONDS );
return $input;
}
/**
* Entry meta queries optimization (no caching for authenticated users)
*
* This function is kept for potential future optimization but does not cache
* entry meta data to avoid issues with authenticated users who may have
* different permissions and access levels.
*
* @param array $entry_meta The entry meta array
* @param int $form_id The form ID
* @return array
* @since 1.0.0
*/
function mass_forms_optimize_entry_meta( $entry_meta, $form_id ) {
// No caching for entry meta to ensure authenticated users see correct data
return $entry_meta;
}
/**
* Clear caches when forms are updated
*
* @param array $form The form object
* @param bool $is_new Whether this is a new form
* @since 1.0.0
*/
function mass_forms_clear_caches_on_form_update( $form, $is_new ) {
$form_id = $form['id'];
// Clear form metadata cache
delete_transient( 'gform_metadata_' . $form_id );
// Clear field configuration caches
global $wpdb;
$wpdb->query( $wpdb->prepare(
"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
'_transient_gform_field_config_' . $form_id . '_%'
) );
// Entry meta cache removed for authenticated user compatibility
}
/**
* Optimize notification processing (no caching for authenticated users)
*
* This function is kept for potential future optimization but does not cache
* notification settings to avoid issues with authenticated users who may have
* different notification preferences and permissions.
*
* @param array $notification The notification array
* @param array $form The form object
* @param array $entry The entry object
* @return array
* @since 1.0.0
*/
function mass_forms_optimize_notification_processing( $notification, $form, $entry ) {
// Skip non-essential notifications during high load
if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
return $notification;
}
// No caching for notifications to ensure authenticated users see correct settings
return $notification;
}
/**
* Defer non-critical form submission processing to background
*
* @param array $entry The form entry
* @param array $form The form object
* @since 1.0.0
*/
function mass_forms_defer_background_processing( $entry, $form ) {
// Defer non-critical processing to background
if ( function_exists( 'wp_schedule_single_event' ) ) {
wp_schedule_single_event( time() + 30, 'mass_forms_process_submission_background', array( $entry['id'], $form['id'] ) );
}
}
/**
* Background processing for form submissions
*
* @param int $entry_id The entry ID
* @param int $form_id The form ID
* @since 1.0.0
*/
function mass_forms_process_submission_background( $entry_id, $form_id ) {
// Process non-critical form submission tasks
do_action( 'gform_entry_created', $entry_id, $form_id );
}
// Hook into WordPress and Gravity Forms
add_filter( 'gform_form_args', 'mass_forms_cache_form_metadata' );
add_action( 'gform_after_submission', 'mass_forms_clear_form_cache_on_submission', 10, 2 );
add_filter( 'gform_field_input', 'mass_forms_cache_field_configuration', 10, 5 );
add_filter( 'gform_entry_meta', 'mass_forms_optimize_entry_meta', 10, 2 );
add_action( 'gform_after_save_form', 'mass_forms_clear_caches_on_form_update', 10, 2 );
add_filter( 'gform_notification', 'mass_forms_optimize_notification_processing', 10, 3 );
add_action( 'gform_after_submission', 'mass_forms_defer_background_processing', 10, 2 );
add_action( 'mass_forms_process_submission_background', 'mass_forms_process_submission_background', 10, 2 );
<?php
/**
* Modification for web/app/mu-plugins/gf-form-settings-alters.php
*
* Replace existing timeout functions (lines 510-527) with these optimized versions
* that follow WordPress coding standards and provide better performance on Pantheon.
*
* @package MassForms
* @subpackage GravityForms
* @since 1.0.0
*/
// Prevent direct access
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Pantheon-optimized cURL timeout configuration
*
* Replaces the existing 30-second timeout with optimized timeouts based on API type.
* This improves performance by reducing wait times for non-critical operations.
*
* @param resource $handle The cURL handle
* @since 1.0.0
*/
function pantheon_optimized_curl_timeout( $handle ) {
$url = curl_getinfo( $handle, CURLINFO_EFFECTIVE_URL );
// Pantheon-specific optimizations
curl_setopt( $handle, CURLOPT_TCP_NODELAY, true );
curl_setopt( $handle, CURLOPT_TCP_FASTOPEN, true );
// Shorter timeouts for different API types
if ( false !== strpos( $url, 'log-api.newrelic.com' ) ) {
// NewRelic logging - shorter timeout since it's async
curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, 5 );
curl_setopt( $handle, CURLOPT_TIMEOUT, 5 );
} elseif ( false !== strpos( $url, 'api.gravityforms.com' ) ||
false !== strpos( $url, 'salesforce.com' ) ) {
// Critical APIs - moderate timeout
curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, 15 );
curl_setopt( $handle, CURLOPT_TIMEOUT, 15 );
} else {
// Other APIs - short timeout
curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, 5 );
curl_setopt( $handle, CURLOPT_TIMEOUT, 5 );
}
}
/**
* Pantheon-optimized HTTP request timeout
*
* Replaces the existing 30-second timeout with dynamic timeouts based on operation type.
* Critical operations get longer timeouts while non-critical operations get shorter ones.
*
* @param int $timeout_value The current timeout value
* @return int
* @since 1.0.0
*/
function pantheon_optimized_http_timeout( $timeout_value ) {
// Check if this is a critical operation
$backtrace = debug_backtrace();
foreach ( $backtrace as $trace ) {
if ( isset( $trace['function'] ) &&
in_array( $trace['function'], array( 'gform_send_notifications', 'gf_salesforce_api_call' ), true ) ) {
return 15; // Critical operations get 15 seconds
}
}
return 5; // Default timeout reduced to 5 seconds
}
// Hook into WordPress
add_action( 'http_api_curl', 'pantheon_optimized_curl_timeout', 9999, 1 );
add_filter( 'http_request_timeout', 'pantheon_optimized_http_timeout', 9999 );
/*
* INSTRUCTIONS FOR IMPLEMENTATION:
*
* 1. Open web/app/mu-plugins/gf-form-settings-alters.php
* 2. Find lines 510-527 (the existing timeout functions)
* 3. Replace the existing functions with the optimized versions above
* 4. Delete the following lines:
*
* add_action('http_api_curl', 'sar_custom_curl_timeout', 9999, 1);
* function sar_custom_curl_timeout( $handle ){
* curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, 30 ); // 30 seconds.
* curl_setopt( $handle, CURLOPT_TIMEOUT, 30 ); // 30 seconds.
* }
* add_filter( 'http_request_timeout', 'sar_custom_http_request_timeout', 9999 );
* function sar_custom_http_request_timeout( $timeout_value ) {
* return 30; // 30 seconds.
* }
* add_filter('http_request_args', 'sar_custom_http_request_args', 9999, 1);
* function sar_custom_http_request_args( $r ){
* $r['timeout'] = 30; // 30 seconds.
* return $r;
* }
*
* 5. Add the optimized functions from this file
*/
<?php
/**
* Modification for web/app/mu-plugins/security.php
*
* Fix header modification in security.php (line 191) by adding proper
* headers_sent() check to prevent "Cannot modify header information" errors.
*
* @package MassForms
* @subpackage Security
* @since 1.0.0
*/
// Prevent direct access
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Add security headers with proper header sent check
*
* This function fixes the "Cannot modify header information - headers already sent"
* error by checking if headers have already been sent before attempting to modify them.
*
* @since 1.0.0
*/
function mass_forms_add_security_headers() {
// Check if headers have already been sent
if ( ! headers_sent() ) {
// Get current domain for CSP
$current_domain = isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '';
$allowed_domains = array(
$current_domain,
'.tugboatqa.com',
'.edit.mass.gov',
'edit.mass.gov',
'www.mass.gov',
'stage.mass.gov',
'edit.stage.mass.gov',
'mass.local',
);
// Set WordPress admin headers
wp_admin_headers();
// Add Content Security Policy header
header( 'Content-Security-Policy: frame-ancestors ' . implode( ' ', $allowed_domains ) );
}
}
/*
* INSTRUCTIONS FOR IMPLEMENTATION:
*
* 1. Open web/app/mu-plugins/security.php
* 2. Find line 191 (the existing add_security_headers function)
* 3. Replace the existing function with the optimized version above
* 4. The key changes are:
* - Added headers_sent() check before modifying headers
* - Added proper sanitization for $_SERVER['HTTP_HOST']
* - Added proper WordPress coding standards
* - Added comprehensive documentation
*
* Original problematic code (line 191):
* add_action('send_headers', 'add_security_headers', 1);
* function add_security_headers() {
* $allowed_domains = [
* $current_domain,
* '.tugboatqa.com',
* '.edit.mass.gov',
* 'edit.mass.gov',
* 'www.mass.gov',
* 'stage.mass.gov',
* 'edit.stage.mass.gov',
* 'mass.local',
* ];
* wp_admin_headers();
* header('Content-Security-Policy: frame-ancestors ' . implode(' ', $allowed_domains));
* }
*
* Replace with:
* add_action('send_headers', 'mass_forms_add_security_headers', 1);
* function mass_forms_add_security_headers() {
* if (!headers_sent()) {
* $current_domain = isset($_SERVER['HTTP_HOST']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])) : '';
* $allowed_domains = [
* $current_domain,
* '.tugboatqa.com',
* '.edit.mass.gov',
* 'edit.mass.gov',
* 'www.mass.gov',
* 'stage.mass.gov',
* 'edit.stage.mass.gov',
* 'mass.local',
* ];
* wp_admin_headers();
* header('Content-Security-Policy: frame-ancestors ' . implode(' ', $allowed_domains));
* }
* }
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment