Slow Solr Queries Debugging
Understanding and Fixing Slow Drupal Solr Queries
Your Drupal site is slow when searching. You check the Solr debug output and discover that the query itself runs in 200-300ms — but faceting takes 1,500-2,000ms or more. The bottleneck is not Solr's search — it is all the extra work Drupal asks Solr to do.
Why Drupal Queries Are Slow
1. The *:* Query Pattern
Drupal often sends {!boost b=boost_document} *:* — meaning "match every single document." Even with filter queries narrowing results, Solr still has to evaluate the entire index first.
2. Too Many Facets
Drupal's Facet API requests facet counts for every active facet field. Each facet must count every term across every matching document. With 10+ facets and facet.limit=-1 (return all values), this is the main performance killer.
3. FCS Method (Slow)
Solr's default FieldCache Scanning (FCS) method scans the entire field value space. Modern alternatives like JSON facets or streaming facets are much faster.
4. Excessive Filters
Dozens of fq parameters — especially negations like (*:* -field:value) and range queries — each add cost.
Fixes in Drupal
A. Reduce Active Facets
Go to Configuration > Search API > View Modes and disable facets that are not visible on the page. Every active facet triggers Solr work, even if Drupal does not render it.
B. Limit Facet Results
In Facet API settings, change facet.limit from -1 (return everything) to a reasonable number:
facet.limit = 50 facet.mincount = 1
C. Use the JSON Facet API
If your Solr version is 7.x or newer, switch Drupal's Search API backend to use the JSON Facet API. It is faster, parallelized, and supports streaming facet counts.
D. Enable Caching
Enable Drupal's Search API query cache and facet cache to avoid recomputing identical facets for repeated queries.
Fixes in Solr
A. Enable DocValues on Facet Fields
Ensure all fields used for faceting have docValues="true" in your schema:
<field name="field_category" type="string" stored="true" indexed="true" docValues="true"/>
DocValues make faceting dramatically faster.
B. Use facet.threads
Parallelize facet computation:
facet.threads=4
C. Enable Filter Cache
In solrconfig.xml, ensure you have a properly sized filter cache:
<filterCache class="solr.FastLRUCache" size="512" initialSize="512" autowarmCount="128"/>
D. Switch to JSON Facets
Replace classic facet.field parameters with JSON facets:
json.facet={ categories: { type: "terms", field: "field_category", limit: 50 }, formats: { type: "terms", field: "field_format", limit: 50 } }
Optimization Checklist
| Area | Action | Typical Gain |
|---|---|---|
| Reduce facet count | Disable unused facets in Drupal | 60-80% faster |
| Set facet.limit | Change from -1 to 50 | Major |
| Enable DocValues | Add docValues="true" to facet fields |
Major |
| Use JSON facets | Modern Solr API | 2-5x faster |
| Enable caching | Drupal + Solr caches | Significant |
| facet.threads=4 | Parallelize computation | 20-40% faster |
| Remove facet.missing | Unless needed by UX | 5-15% faster |
Before vs After
Before (slow):
q={!boost b=boost_document} *:*&facet.limit=-1&facet.field=field_a&facet.field=field_b...
After (fast):
q=*:*&fq=available:true&facet.limit=50&facet.threads=4&
json.facet={
categories:{type:terms,field:field_category,limit:50},
formats:{type:terms,field:field_format,limit:50}
}
Deep Pagination (start=100000 and beyond)
Another silent killer, and a favorite of bots and scrapers: requesting very deep result pages.
Solr cannot jump straight to result number 100,000. To serve start=100000&rows=20, it must collect and sort all 100,020 top documents first, on every single request. The cost grows with the offset, so page 5,000 costs thousands of times more than page 1. A scraper walking your entire index page by page will happily push start= into the hundreds of thousands, turning each request into a multi-second, CPU-burning query.
How to fix it
1. Cap the page depth in your application. Real visitors never browse past page 100. Refuse or clamp anything deeper:
if (page > 100) page = 100;
2. Use cursorMark for exports and crawls. If you legitimately need to walk the whole index (data export, sitemap, migration), use Solr's cursor API. It has constant cost per page, no matter how deep you go:
q=*:*&sort=id asc&rows=100&cursorMark=*
Each response returns a nextCursorMark value. Pass it as cursorMark in the next request. Requirements: a stable sort that includes your unique key field, and no start parameter.
3. Keep rows sane. Large rows values multiply the cost of every request. Fetch what you display.
How you find out
The Opensolr watchdog monitors your request analytics daily and emails you when it detects hard queries or deep pagination patterns on your index, with the offending IPs and offsets included.