Two questions decide how eDisMax behaves in production: how the pieces of the score add up, and where each parameter is set.
The parsed query has two parts. The required part is the term matching, filtered by mm — that decides which documents are in the result set at all. Everything after it is optional: the phrase clauses only add score to documents that already qualified.
Two documents matching the same terms. Phrase clauses decide the order between them.
This is why phrase boosting can never rescue a document that mm excluded, and why cranking pf is the wrong answer to “relevant documents are missing”. Missing documents are an mm, an analyzer, or a qf coverage problem. Wrong order is what phrase boosts fix.
02 · Where each parameter lives
Three layers, one direction of precedence.
schema.xml defines what can be searched — the fields, their types, and the analysis chain that decides which tokens end up in the index. A field the analyzer stems, folds and de-duplicates behaves very differently under qf than a string field.
<!-- schema.xml excerpt: one analyzed text type, the fields, and an aggregate catch-all --> <schema name="classicbook" version="1.6"> <fieldType name="text_general" class="solr.TextField" positionIncrementGap="100"> <analyzer type="index"> <charFilter class="solr.HTMLStripCharFilterFactory"/> <tokenizer class="solr.ICUTokenizerFactory"/> <filter class="solr.EnglishPossessiveFilterFactory"/> <filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/> <filter class="solr.WordDelimiterGraphFilterFactory" generateWordParts="1" catenateWords="1" preserveOriginal="1"/> <filter class="solr.ASCIIFoldingFilterFactory" preserveOriginal="true"/> <filter class="solr.SnowballPorterFilterFactory" protected="protwords.txt" language="English"/> <filter class="solr.RemoveDuplicatesTokenFilterFactory"/> </analyzer> <analyzer type="query"> <charFilter class="solr.HTMLStripCharFilterFactory"/> <tokenizer class="solr.ICUTokenizerFactory"/> <filter class="solr.EnglishPossessiveFilterFactory"/> <filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.SynonymGraphFilterFactory" ignoreCase="true" synonyms="synonyms.txt" expand="true"/> <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/> <filter class="solr.WordDelimiterGraphFilterFactory" generateWordParts="1" catenateWords="1" preserveOriginal="1"/> <filter class="solr.ASCIIFoldingFilterFactory" preserveOriginal="true"/> <filter class="solr.SnowballPorterFilterFactory" protected="protwords.txt" language="English"/> <filter class="solr.RemoveDuplicatesTokenFilterFactory"/> </analyzer> </fieldType> <field name="id" type="string" indexed="true" stored="true" required="true"/> <field name="title" type="text_general" indexed="true" stored="true" multiValued="false"/> <field name="author" type="string" indexed="true" stored="true"/> <field name="summary" type="text_general" indexed="true" stored="true" multiValued="false"/> <field name="content" type="text_general" indexed="true" stored="false" multiValued="false"/> <!-- Aggregate catch-all: cheap recall net, kept at a low qf weight --> <field name="text_all" type="text_general" indexed="true" stored="false" multiValued="true"/> <copyField source="title" dest="text_all"/> <copyField source="author" dest="text_all"/> <copyField source="summary" dest="text_all"/> <copyField source="content" dest="text_all"/> <uniqueKey>id</uniqueKey> </schema>
<defaultSearchField> and <solrQueryParser defaultOperator> were deprecated in Solr 3.6 and removed in Solr 7. Use df and q.op in the request handler instead — shown below. Under eDisMax, df only matters for terms a user explicitly field-qualifies; ordinary terms go through qf.solrconfig.xml is where the parser and its defaults belong. Putting them in the handler means every client — your site, your mobile app, an integration you have not written yet — gets the same relevance without repeating itself.
<requestHandler name="/select" class="solr.SearchHandler" default="true"> <lst name="defaults"> <str name="defType">edismax</str> <str name="df">text_all</str> <str name="q.op">OR</str> <!-- Which fields, and how much each one is worth --> <str name="qf">title^3.0 summary^1.5 text_all^0.5</str> <!-- How many of the terms a document must contain --> <str name="mm">2<75% 4<90% 6<100%</str> <!-- Phrase rewards, and how far the words may drift --> <str name="pf">title^5 summary^3</str> <str name="ps">2</str> <str name="pf2">title^4 summary^2</str> <str name="ps2">1</str> <str name="pf3">title^3 summary^1</str> <str name="ps3">1</str> <!-- Best-field scoring with a small contribution from the others --> <str name="tie">0.1</str> <str name="hl">true</str> <str name="hl.fl">title,summary,content</str> </lst> </requestHandler>
The eDisMax query parser
How the score is built, and where each parameter lives (this page)