Skip to main content

 

Splunk Lantern

Comparing search head hardware changes and their impact on Splunk platform search performance

When you upgrade the hardware behind your Splunk platform search head cluster, you'll likely want to quantify the performance gains the new servers deliver. Measuring this accurately is more complex than it first appears, as search runtimes can vary significantly from run to run, even on identical hardware.

This article walks you through a scenario in which search head instances were migrated to newer hardware, with the new instances added into the cluster and measured to quantify the improvement. It shows you the complexity involved in determining how well a search head is performing, along with the performance differences between the various instances. Finally, it provides a reusable framework for comparing scheduled search performance across hardware types.

The findings in this article reflect a specific hardware comparison carried out by the author, Gareth Anderson, in his own environment, so your results might vary depending on workload, hardware generation, firmware, OS configuration, and search head sizing. You should treat these results as a reference point and benchmark against your own environment before making procurement decisions.

The hardware differences

The servers old-001 to old-004 used throughout this article are the same "type" of CPU (2.70 Ghz), new-003 is the newer type of hardware (3.90 Ghz), and other-shc is even newer in terms of CPU generation and has a base clock of 3.6 Ghz. The monitoring server is a VMware instance with close to zero real users, just many scheduled searches. The monitoring server did not have Nmon running, so it is not included in all comparison graphs.

CPU configuration

The migration was occurring from the 2.70 Ghz to the 3.90 Ghz processors; everything else is used for comparison purposes only.

The newer 3.90 Ghz processor has fewer total cores, and this proved to be a challenge. This image shows the percent CPU per server:

cpu-per-instance.png

The load balancer unintentionally assigned too many users to the new‑003 server. When there were more than 100 users on this search head, it started to hit the CPU limits and caused general chaos (concurrency quotas reached, slower searches). The Splunk platform search performance comparison section was set up to ignore the time periods where 100% CPU was in use, as that changes the results.

I/O performance

To determine IOStats through the introspection data, run this search:

index=_introspection sourcetype=splunk_resource_usage component=IOStats host IN (...)
| eval avg_total_ms = 'data.avg_total_ms' 
| where avg_total_ms>0
| timechart minspan=60s partial=f limit=99 span=2h perc95(avg_total_ms) BY host

This image shows the Splunk platform introspection data average service times:

total_service_time_introspection_data.png

The first finding is that while the new-003 server has faster CPUs, it has slower I/O compared to the previous hardware. This finding was a surprise. Note that the old servers mount the drives as NVMe (/dev/nvme0n1), while the new servers have the drive mounted as an "sd" drive (/dev/sdc).

Read I/O performance

This image shows the read IOPS per server, measured using Nmon:

read-iops.png

This image shows the read service times, measured using Nmon:

read-service-time.png

For read service times, the newer servers are faster; the old servers have the occasional spike above this graph. The IOPS is extremely low, as expected. The graph above is "clipped", but the old servers did spike to 50 ms on some occasions.

Write I/O performance

In terms of write IOPS, there is not a huge amount of activity, and for an unknown reason the new-003 seems to have more write activity compared to other SHC members. This image shows the write IOPS per server, measured with Nmon:

Screenshot 2026-06-05 102858.png

This image shows the write service times per server, measured with Nmon:

write-service-time.png

As the introspection data showed, the new-003 server is unfortunately slower in write service times when measured using Nmon.

The newest hardware (new-001 or other-shc) is much faster in terms of response times, which seems to be a pattern for the newer NVMe drives.

Summary of findings

The following sections detail the testing methodology and the differences between comparing scheduled searches versus ad-hoc search runs. This image shows the main findings for scheduled search runtimes — the average difference between new and old search heads based on 30 days of scheduled search data, broken down by runtime:

Screenshot 2026-06-05 114106.png

The performance did generally improve, but the improvements were much smaller than expected when looking at the average. Measuring with the 95th percentile instead shows that some scheduled searches did see a significant improvement. This image shows the 95th percentile comparison between new and old search heads based on 30 days of scheduled search data, broken down by runtime:

Screenshot 2026-06-05 114147.png

While most, if not all, searches were expected to benefit, the benefit was mainly seen on the simple searches used for manual testing (detailed later in this article), not on the average scheduled search. The other main finding was that the variance of searches per-run was very high; it was common to see more than 30% difference in runtimes for the same scheduled search on the same hardware. The outliers often had 2–3X higher runtimes.

The ratio of phase 0 to phase 1 runtimes was also measured, calculated as (100/avg_sh_time)*avg_indexer_time. The higher the number, the more time spent in phase 0 and less "extra" time spent in phase 1. This image shows the average ratio of phase 0 to phase 1 time by range over 7 days:

avg_ratio_by_range_7days.png

This image shows the average ratio of phase 0 to phase 1 time by range over 30 days:

avg_ratio_by_range_30days.png

A value of 50% in the above graphs means that the phase 1 time is equally as long as the phase 0 time multiplied by two. In other words, if the phase 0 time is 5 seconds, the phase 1 time is 10 seconds. A value of 99% means there is very little extra time spent in phase 1 once phase 0 completes.

The two graphs below look for a ratio of less than 50%, so they should have more benefit from the CPU change.

This image shows the average difference between new and old search heads based on 7 days of scheduled search data, with long phase 1 runtimes, broken down by runtime:

shc_diff_ratio_by_range_7days.png

This image shows the average difference between new and old search heads based on 30 days of scheduled search data, with long phase 1 runtimes, broken down by runtime:

shc_diff_ratio_by_range_30days.png

Unfortunately, there is no simple rule in this environment that "if your CPU clock speed increases by 50%, then your search execution times will drop by 10%". In fact, the testing showed that the more complicated or "real world" queries appeared to perform much more randomly than expected.

How do you measure Splunk platform search head performance?

There are many ways to test Splunk platform query performance. The first test was straightforward and triggered as ad-hoc searches. Three different queries were run, initially on 1 old server and also on 1 new server at the same time.

After running these queries a few times, a few additional servers were included to see how the hardware involved affects performance.

The queries were:

index=_internal earliest=-1m
index=_internal earliest=-10m | stats count BY host, source
index=_internal earliest=-60m | stats count BY host, source

While some manual measurements were taken initially, this quickly proved to be quite time-consuming.

Although the search_telemetry data in the introspection index is not always accurate, it works well for the purpose of ad-hoc queries. Note that, as of Splunk platform 9.3.3, scheduled searches with more than 500,000 events are not recorded in this data for their phase 0 performance. Additionally, subsearches are not recorded here. Finally, if the search has errors, then this data is no longer as reliable from this source.

The results from the introspection data were compared to the job inspector, using a search similar to "SearchHeadLevel — Job performance data per indexer" from Alerts for Splunk Admins, and the timings appeared to line up.

The query involved was:

index=_introspection sourcetype=search_telemetry 
    [ search index=_audit info=completed "earliest=-1m" OR "earliest=-10m" OR "earliest=-60m"
    | rex field=search_id mode=sed "s/'//g" 
    | table search_id ] 
| rex "\"outputCount\":(?P<output_count>\d+)" 
| fields _time phases.phase_0.elapsed_time_aggregations.max, phases.phase_1.elapsed_time_aggregations.max, perf.results_count, output_count, host 
| eval phases.phase_0.elapsed_time_aggregations.max=round('phases.phase_0.elapsed_time_aggregations.max',2), phases.phase_1.elapsed_time_aggregations.max=round('phases.phase_1.elapsed_time_aggregations.max',2)
| eval count=if('perf.results_count'<10000,output_count,'perf.results_count')
| table _time, host, count, phases.phase_0.elapsed_time_aggregations.max, phases.phase_1.elapsed_time_aggregations.max
| where count>5000
| eval type=if(count>100000,"events","stats_60min")
| eval mode="smart"

The mode was changed manually, but otherwise this query made capturing the measurements much easier.

Results from ad-hoc testing

The 1 minute query was designed to simulate a dense query to test performance. Stats queries were then added to try to push more workload to the phase 1 or search head level.

This image shows the average runtimes across various environments, where "old" is the hardware being moved from and new-003 is the hardware being moved to:

manual-avg-runtimes (1).png

This image shows the 95th percentile runtimes across the same environments:

manual-95th-perc-runtimes.png

The "other shc" is faster than the new-003 hardware, and the monitoring server is a VM. The main comparison is new-003 compared to "old" search heads. The "old alt SHC" is another search head cluster on the same hardware with much less workload.

This image shows the average runtime searching events with fast mode:

manual-fast-events.png

This image shows the average runtime searching events with smart mode:

manual-smart-events.png

The runtime advantage slightly increases when using smart mode compared to fast mode.

This image shows the average runtime searching with stats over 10 minutes:

manual-stats-10min.png

This image shows the average runtime searching with stats over 60 minutes:

manaul-stats-60min.png

There is a clear advantage for the new-003 server here. Based on this testing alone, the new hardware appears to provide a significant advantage in terms of runtime performance.

The next section covers the scheduled search testing, which made the overall results much less clear.

Scheduled searches

How do you test scheduled searches?

A few options were tried for testing scheduled searches. The first was the remote_searches.log:

index=_internal `indexerhosts` source="/opt/splunk/var/log/splunk/remote_searches.log" "StreamedSearch - Streamed search connection closed" OR "StreamedSearch - Streamed search search starting" 
| rex "(starting|closed): search_id=(?P<search_id>[^,]+)" 
| search NOT search_id=*RemoteStorageRetrieveBuckets*
| stats range(_time) AS runtime, last(_raw) AS _raw BY search_id, host
| stats max(runtime) AS slowest_time, last(_raw) AS raw_query BY search_id

This alternative measures the indexing tier execution time of searches. Subsearches are found this way, and searches including subsearches will not include the time involved in running subsearches (which is different from how the _audit log records total_run_time). However, this isn't the fastest way to find the data — _introspection sourcetype=search_telemetry or index=_audit records this data in an easier-to-use format.

This query runs on the indexing tier, but it is quite inefficient. It queries each indexer to find the slowest runtime, and includes the raw data for troubleshooting whether the correct results are being found. This finds any subsearch executing on the indexing tier and appears to be extremely close to what the introspection data shows.

This source was not used, as it only provided phase 0 runtimes and the main target was the phase 1 or search head level runtimes.

The Splunk platform query-based approach

Because dashboard searches can change the time period searched over, only scheduled searches were included to simplify the query. Because the information on the scheduled searches exists in the audit.log, the data was queried from the audit.log to confirm which scheduled search was being worked with.

The introspection data was then queried for the performance information related to the phase 0 and phase 1 runtimes, excluding any queries running for less than 2 seconds.

This approach started simple, but because a large variation was found between results, more and more stats were added. A few different methods were also created to classify the searches:

  • The "type" of search — for example, whether it
    • runs a search that relies on an external system such as a database or LDAP
    • runs a metadata, stats, or eventcount command
    • is local to the search head (for example, loadjob, inputlookup, or rest, which might or might not be local depending on the splunk_server argument)
    • looks like a standard search involving the indexing tier (for example, search, multisearch, set, union, or from)
  • Classification based on "runtime" to group searches together, with the expectation that searches with a longer runtime provide a more consistent performance difference.
  • The standard deviation and average response time were also determined. This allows for a rough percentage of how much a search runtime varies on each hardware type.

The query

| multisearch 
    [ search index=_introspection sourcetype=search_telemetry `searchheadhosts` perf.search_runtime_secs>2 search_id=scheduler*
        | eval hour=strftime(_time, "%H"), minute=strftime(_time, "%M"), dayofweek=strftime(_time, "%a") 
    | rename desc.app AS app
    | fields phases.phase_0.elapsed_time_aggregations.max, phases.phase_1.elapsed_time_aggregations.max, host, search_id app ] 
    [ search index=_audit `searchheadhosts` info=completed total_run_time>2 search_id="'scheduler*" has_error_warn=false fully_completed_search=true 
    | eval hour=strftime(_time, "%H"), minute=strftime(_time, "%M"), dayofweek=strftime(_time, "%a") 
    | eval search_id=trim(search_id,"\'")
    | rex ", savedsearch_name=\"(?P<savedsearch_name>.*?)\", search_startup_time"
    | rex "(?s), search='(?P<search>.*)\]$" 
    | rex field=search mode=sed "s/\n/ /g" 
    | eval search=if(substr(search,len(search),len(search)-1)=="'",substr(search,0,len(search)-1),search)
    | rex field=search "^(\s*\|\s*)?\s*(?P<command>\S+)" 
    | rex field=search "(\||^)\s*(?P<subsearch>(append|union|multisearch|set|appendcols|appendpipe|join|map))"
    | eval subsearch=if(isnotnull(subsearch),"true","false")
    | eval search_type=case(command="ldapsearch" OR command="runaql" OR command="dbxquery","remote_server",command="tstats" OR command="metadata" OR command="eventcount","metadata",command="makeresults" OR command="inputlookup" OR command="rest" OR command="loadjob" OR command="noop" OR command="stats","local",match(command,"^trackme"),"customcommand",command="multisearch" OR command="union" OR command="savedsearch" OR command="datamodel" OR command="from" OR command="set","search",command="`","search",1=1,command)
    | fields total_run_time, savedsearch_name, search_id, result_count, event_count, scan_count, search_type, app subsearch ] 
| eval type="new"
| selfjoin search_id app
| table _time, phases.phase_0.elapsed_time_aggregations.max, phases.phase_1.elapsed_time_aggregations.max, host, savedsearch_name, total_run_time, result_count, event_count, scan_count search_type app subsearch
| bin _time span=24h 
| stats count, avg(phases.phase_1.elapsed_time_aggregations.max) AS avg_sh_time, avg(phases.phase_0.elapsed_time_aggregations.max) AS avg_indexer_time, avg(total_run_time) AS total_run_time, avg(scan_count) AS scan_count, avg(event_count) AS event_count, avg(result_count) AS result_count, values(subsearch) AS subsearch, stdev(phases.phase_1.elapsed_time_aggregations.max) AS phase_1_stdev, stdev(phases.phase_0.elapsed_time_aggregations.max) AS phase_0_stdev by savedsearch_name, type, _time, search_type, app
| eval phase_1_diff=round(phase_1_stdev/avg_sh_time,3), phase_0_diff=round(phase_0_stdev/avg_indexer_time,3)
| eventstats perc95(phase_1_diff) AS phase_1_diff, perc95(phase_0_perc) AS phase_0_diff, avg(phase_0_diff) AS avg_phase_0_diff, avg(phase_1_diff) AS avg_phase_1_diff by savedsearch_name, search_type, app
| eval day=strftime(_time, "%a") 
| eval hour=strftime(_time, "%H")
| sort 0 savedsearch_name type
| streamstats window=1 global=f current=f reset_on_change=true values(avg_sh_time) AS avg_new_sh_time, values(avg_indexer_time) AS avg_new_indexer_time, values(scan_count) AS prev_scan_count, values(event_count) AS prev_event_count, values(result_count) AS prev_result_count by savedsearch_name
| eval shc_diff=((avg_sh_time/avg_new_sh_time)-1)*100, indexer_diff=((avg_indexer_time/avg_new_indexer_time)-1)*100
| eval ratio=(100/avg_sh_time)*avg_indexer_time
| foreach avg* phase_* ratio shc_diff total_run_time [eval <<FIELD>>=round('<<FIELD>>',2)]
| rangemap field=avg_sh_time 1-3=1-3 4-6=4-6 7-10=7-10 11-30=11-30 31-60=31-60 61-120=61-120 121-300=121-300 301-600=301-600 default=over600

This search was also tried on hourly data, but only a fraction of the overall scheduled searches run hourly and the results overall didn't look that different.

The above search itself is very slow. To gather 7 days of data, it took 81.659 seconds on the slowest indexer, and 407 seconds in total for the search head to complete the query. This resulted in 25,257 stats.

To run this over 30 days took 2,242 seconds and produced 108,776 stats; the phase 0 failed due to errors and reported 0.03 seconds in the introspection data.

Performance graphs

The first challenge was the variance between runs; many scheduled searches would have multiple runs during the day with randomly varying runtimes.

Variance between scheduled search runtimes

The initial expectation was that as the runtimes extended beyond the 30–60 second mark, more consistency would be seen from the runtimes. This image shows the phase 1 (search head) runtimes as a percentage difference between runs on the same type of hardware, split by runtime:

Screenshot 2026-06-05 115458.png

This image shows the phase 0 (indexer) runtimes as a percentage difference between runs, split by runtime:

Screenshot 2026-06-05 115339.png

This did not prove to be the case, and the phase 0 runtimes were also highly variable at the indexing tier. Breaking down by commands was also tried, in case the variance came from external systems such as databases or LDAP. This image shows the phase 1 (search head) runtimes as a percentage difference between runs on the same type of hardware, split by search type:

Screenshot 2026-06-05 115636.png

This image shows the phase 0 (indexer) runtimes as a percentage difference between runs, split by search type:

Screenshot 2026-06-05 115727.png

A longer comparison period was also tested — 30 days instead of 7 days — in case more data was needed for consistency, as this compares 1 day to the week(s) before. This image shows the phase 1 (search head) runtimes as a percentage difference between runs on the same type of hardware, split by runtime:

Screenshot 2026-06-05 115955.png

This image shows the phase 0 (indexer) runtimes as a percentage difference between runs on the same type of hardware, split by search runtime:

Screenshot 2026-06-05 115905.png

This image shows the phase 1 (search head) runtimes as a percentage difference between runs on the same type of hardware, split by search type:

Screenshot 2026-06-05 115813.png

The 30 day comparison versus the 7 day comparison wasn't that different; clearly the runtimes of scheduled searches vary more than expected. There were even scheduled searches varying by taking twice as long as a previous runtime in some cases.

Performance increases for the new hardware compared to the old hardware

To exclude the outliers, anything greater than 100 or less than -100 in terms of the performance difference was excluded, as this would move the averages too much. Only searches that appeared to vary less than 30% in their phase 1 runtimes were then included. There might be a better way to do the comparison, but this was sufficient for this purpose. This image shows the average scheduled search performance by search type over 7 days of data (the number is the % improvement compared to the old servers):

Screenshot 2026-06-05 104642.png

This image shows the average scheduled search performance by search runtime over 7 days of data (the number is the % improvement compared to the old servers):

Screenshot 2026-06-05 104659 (1).png

This image shows the average difference between new and old search heads based on 7 days of scheduled search data, with long phase 1 times, broken down by runtime:

shc_diff_ratio_by_range_7days.png

The 95th percentile shows that some searches are getting a significant benefit from the new hardware. This image shows the 95th percentile scheduled search performance by search type over 7 days of data (the number is the % improvement compared to the old servers):

Screenshot 2026-06-05 114522.png

This image shows the 95th percentile scheduled search performance by search runtime over 7 days of data (the number is the % improvement compared to the old servers):

Screenshot 2026-06-05 114536.png

This image shows the 95th percentile scheduled search performance by search runtime over 7 days of data, with long phase 1 runtimes (the number is the % improvement compared to the old servers):

shc_diff_perc95_7days.png

The 30 day version took 2,242 seconds and produced 108,776 stats; the phase 0 failed due to errors and reported 0.03 seconds. This image shows the average scheduled search performance by type over 30 days of data (the number is the % improvement compared to the old servers):

Screenshot 2026-06-05 114319.png

This image shows the average scheduled search performance by runtime over 30 days of data (the number is the % improvement compared to the old servers):

Screenshot 2026-06-05 114106.png

This image shows the average difference between new and old search heads based on 30 days of scheduled search data, with long phase 1 times, broken down by runtime (the number is the % improvement compared to the old servers):

shc_diff_ratio_by_range_30days.png

The 95th percentile shows that some searches are getting a significant benefit from the new hardware. This image shows the 95th percentile scheduled search performance by range over 30 days of data (the number is the % improvement compared to the old servers):

Screenshot 2026-06-05 114147.png

This image shows the 95th percentile scheduled search performance by type over 30 days of data (the number is the % improvement compared to the old servers):

Screenshot 2026-06-05 114235.png

This image shows the 95th percentile scheduled search performance by type over 30 days of data, with long phase 1 runtimes (the number is the % improvement compared to the old servers):

shc_diff_perc95_30days.png

Comparing to an alternative environment

Another isolated Splunk platform environment was available to compare this data to. This alternative environment was running Splunk platform 10.0.4, had 8 i3en.2xlarge indexers, and the SHC tier was 3 i3en.xlarge. The expectation was that perhaps this smaller environment would have more consistency in terms of search runtimes for scheduled searches. This image shows the phase 1 (search head) runtimes as a percentage difference between runs over 7 days:

cloud-7days-phase1.png

This image shows the phase 1 (search head) runtimes as a percentage difference between runs over 30 days:

clipboard_822d0738-a401-4b5a-af71-53f5ca53ba94.png

The phase 0 or indexing tier also showed a similar variance. This image shows the phase 0 (indexer) runtimes as a percentage difference over 7 days:

cloud-7days-phase0.png

This image shows the phase 0 (indexer) runtimes as a percentage difference over 30 days:

clipboard_f9932ea2-026b-4075-8c19-097e9cee5293.png

The indexers vary more than expected, approaching a 66% difference between searches over a 30 day period. This image shows the phase 1 (search head) runtimes as a percentage difference between runs over 30 days, split by search type:

cloud-30days-phase-1-diff-searchtype.png

This image shows the phase 0 (indexer) runtimes as a percentage difference over 30 days, split by search type:

cloud-30days-phase-0-diff-searchtype.png

The search head tier had less variance, but often over 30% variance between searches, and again quite a few searches vary by 62%.

The longer running queries were expected to have more consistency when broken down by runtimes, but this is not the case. This image shows the phase 1 (search head) runtimes as a percentage difference between runs over 30 days, split by runtime:

cloud-30days-phase1-by-range.png

This image shows the phase 0 (indexer) runtimes as a percentage difference over 30 days, split by search runtime:

cloud-30days-phase0-by-range.png

Conclusion

The results are not what was expected. The processors' base clock speed increased by 44% compared to the older hardware. The old servers were likely using at least some of their turbo speed, but it's unlikely they were sitting close to the maximum of 3.7 Ghz, and the new servers start at 3.9 Ghz — at minimum, a 5% increase in clock speed, and more likely a 20% or greater difference.

While some searches did appear to have a 20% or greater benefit, the variance between scheduled search runs of the exact same search varied much more than expected throughout each run. During manual comparisons, scheduled searches running shortly after each other sometimes ran 3 times slower than a previous run, with no clear explanation. It likely relates to the overall workload within each Splunk platform instance.

Overall, the newer hardware does help improve performance, but it's not as clear cut as "if you increase the processor clock speed by 50%, then search runtimes will decrease by 10%", or any percentage. It varies much more on a per-search basis than expected.

The simpler ad-hoc test runs did show a clear benefit, so the new hardware is expected to be faster, but the difference is much less than expected. The scheduled search comparison framework should prove useful for future testing covering other Splunk platform performance topics.

Additional resources

These resources might help you understand and implement this guidance: