Como funciona a relevância no Solr

Existem fatores básicos (BEM básicos) que determinam o score de um documento para uma uma busca. São eles:

  • TF (Term Frequency) – Quanto mais vezes um determinado termo aparece num documento, mais importante é aquele documento;
  • IDF (Inverse Document Frequency) – Documentos que tem termos raros contam mais do os que tem termos extremamente comuns;
  • Coord (Coordination Factor) – Se uma busca tem mais de um termo, quanto mais termos são encontrados num documento, maior o seu score;
  • LengthNorm – Campos com menos termos tem mais importância do que campos com muitos termos. Abordamos este assunto mais profundamente no post Omitnorms – Desvendando os segredos desta propriedade;
  • Index-Time boost – Se foi definido boost para um documento em tempo de indexação, pesquisas que retornem aquele documento farão com que ele tenha maior relevância;
  • Query Clause Boost – Quando é definido um boost em tempo de query, a parte que recebeu o boost terá maior peso no cálculo de relevância dos documentos.

Valeu

Anúncios

Turbinando o DataImportHandler com o ScriptTransformer

Em um dos projetos em que trabalhei, foi necessário mais do que simplesmente fazer a importação dos dados do repositório principal para o Solr, em muitos casos tivemos que tratar os campos antes de serem enviados.

Fora as discussões de “em qual camada da aplicação estes dados devem ser tratados”, alguns deles foram modificados em tempo de indexação. E não existe lugar melhor para fazer do que no DataImportHandler.

Existem várias maneiras de se tratar os dados no DIH. Um deles é com o TemplateTransformer, que utilizamos da seguinte forma (no caso abaixo estou utilizando um FileListEntityProcessor com um XPathEntityProcessor):

<entity name="x" processor="XPathEntityProcessor" forEach="/SEU/XPATH/AQUI" url="${f.fileAbsolutePath}" transformer="TemplateTransformer">

...

<field column="seucampo" template="${x.campoorigem} texto fixo" />

Isso fará com que o campo “seu campo” tenha o valor de “teste texto fixo” quando o valor do campo origem for “teste”.

E a maneira mais flexível (e complexa) de alterar um registro em tempo de indexação é com a utilização de scripts no DIH. Para isso, o primeiro passo é incluir a chamada ao script em seu “entity”:

<entity name="x" processor="XPathEntityProcessor" forEach="/SEU/XPATH/AQUI" url="${f.fileAbsolutePath}" transformer="TemplateTransformer,script:SuaFuncao">...

Após isso, você insere a sua tag script após a abertura da tag dataConfig, recebendo um parâmetro para o registro que você está trabalhando, relacionado ao entity corrente (o script será executado uma vez para cada registro) e com o nome definido um passo atrás, como eu fiz no exemplo abaixo:

<script>
<![CDATA[
function SuaFuncao(linha) {
return linha
}
]]>
</script>

E então, dentro deste script existem várias coisas que podem ser feitas, avalie o simples exemplo abaixo, basta apenas um pouco de conhecimento de javascript:


<script>
<![CDATA[
function SuaFuncao(linha) {

var textoManual = new java.util.ArrayList();
var campo1 = linha.get("campo1");
var campo2 = linha.get("campo2");
var suites = linha.get("suites");
var vagas = linha.get("vagas");
var tipooferta = linha.get("tipooferta");

if (campo1 != null)
textoManual.add(campo1 + " descrição do campo");

if (campo2 == "teste" || campo2 == "aquivai")
textoManual.add(campo2);

//Adicionar uma palavra fixa
textoManual.add("palavra-para-todos-os-campos");

linha.put("textomanual", textoManual);
return linha;

}
]]>
</script>

Com um pouco mais de imaginação dá para fazer muitas coisas legais. Para saber um pouco mais sobre o ScriptTransformer, visite o site oficial: http://wiki.apache.org/solr/

Bem galera, é isso, qualquer dúvida, estou à disposição.

Paz!

Omitnorms – Desvendando os segredos desta propriedade

Se olharmos na documentação oficial do Solr, encontraremos a seguinte explicação:

  • This is arguably an advanced option.
  • Set to true to omit the norms associated with this field (this disables length normalization and index-time boosting for the field, and saves some memory). Only full-text fields or fields that need an index-time boost need norms.

Começamos mal… A primeira vez que eu li isso eu nem entendi direito, então fui atrás tentar descobrir o que era.

Mas, logo de cara, entendi que isso desabilitava boost em tempo de indexação e liberava um pouco de memória. Ou seja, por padrão defino os campos com omitnorms = true, pois a maioria dos campos não são fulltext e nem precisam deste tipo de boost.

Então começo a definir quais devem ter normas. Pela rápida explicação do tutorial, os campos que precisam de boost em index-time não tem negócio, então estes vão ter mesmo omitnorms=false, pois as normas são que guardam os dados deste tipo de boost.

Dentre aqueles que são fulltext, vamos entender quais vão precisar das normas. Para isso tive que entender o que são as normas. Normas são:

  • Um fator de score específico para cada campo de um documento
  • É calculado em tempo de indexação
  • Baseado no boost do documento, do campo e no lenghtNorm

Do que esta na explicação acima, temos como novidade apenas a questão do LengthNorm, que é um valor calculado de acordo com o conteúdo do campo. De forma que campos com menos conteúdo tenham mais relevância no cálculo final do valor da norma. Esta norma é calculada multiplicando-se os valores do boost do documento, com o lenghtNorm e o boost do campo.

Para tentar tornar a explicação um pouco mais simples, a norma é um dos fatores que são levados em consideração na hora de se determinar quais documentos são mais importantes para uma determinada busca – o score daquele documento.

Basta agora apenas saber que este valor de norma perde precisão no processo de indexação e busca, ou seja, as modificações de valores de boost devem ser realizadas com cuidado e muitos testes.

Ou seja, se seu campo fulltext deve ser levado em conta na hora de se determinar o score de um documento com base na quantidade de dados nele, determine que ele armazene a norma (omitnorms=false), caso contrário não. Dica: campos fulltext muito pequenos normalmente não vão necessitar de normas.

Mas afinal, que diferença faz eu colocar omitnorms=false para todos os campos fulltext? Não é mais simples?

E aqui mora o perigo. Por não entendermos corretamente o que esta propriedade faz podemos comprometer a utilização da memória de nosso servidor. Um exemplo nos ajudará a entender melhor o que quero dizer:

As normas são armazenadas no índice utilizando 1 byte por campo por documento. Quando, através de um IndexReader, elas são carregadas, são criados arrays com o tamanho do número total de documentos no índice (new Array[maxdoc]), sendo um array por campo. Isso significa que, mesmo que apenas um documento num índice de 6 milhões de registros tenha o campo com normas, será alocado na memória um Array de aproximadamente 6 Mb na RAM. E isso porque estamos falando de apenas um campo, se este mesmo índice tiver 10 campos assim, estamos falando de 60 Mb. Em alguns cenários, com mais campos ou mais registros, podemos estar falando de uma pequena catástrofe (não se surpreenda se 1 Gb de RAM sumir de seu servidor quando o Solr for iniciado).

Notas:
Index-time boost é diferente de query-time boost. Para boost em tempo de query não são necessárias as normas para o campo, ou seja, podemos definir como omitnorms=true.

Valeu e até a próxima.

Solr – PositionIncrementGap – O que é isso?

No Solr, quando temos um campo multivalorado (multivalued = true), ele é armazenado numa única estrutura, por exemplo no campo author, onde passamos: “Ed René Kivitz” e “Paul Washer”, o valor é armazenado assim:

“Ed René Kivitz Paul Washer”

Ou seja, uma busca deste tipo ‘author:”Kivitz Paul”‘ pode trazer resultados, assim como ‘author:Kivitz Paul’ (note a ausência de aspas) também traria. O que normalmente não queremos que aconteça. Para evitar isso é que o PositionIncrementGap existe. Ele adiciona um espaço entre um valor e outro, fazendo com que estes resultados errados não ocorram. Teoricamente, o resultado desta indexação com um positionIncrementGap, ficaria mais ou menos assim:

“Ed René Kivitz                                                     Paul Washer”

E uma busca ‘author:”Kivitz Paul”‘ não retornaria nada, enquanto ‘author:Kivitz Paul’ retornaria.

Detalhe: Alterar o positionIncrementGap no schema.xml só sutirá efeito após uma reindexação completa.

Qualquer dúvida, comente!

Valeu, até a próxima.

Dica Solr – Número 1 – O Schema.xml É case sensitive

Ou seja, no schema.xml, escrever:

<field name="author" type="text" indexed="true" stored="true" required="true" multivalued="true" />

é diferente de escrever:

<field name="author" type="text" indexed="true" stored="true" required="true" multiValued="true" />

Caso você escreva tudo em minúsculo, e tente mandar o campo author como multivalorado, irá receber o seguinte erro da amável ferramenta post.jar:

SimplePostTool: FATAL: Solr returned an error: ERROR_9788573126_multiple_values_encountered_for_non_multiValued_field_author_Jos_Antonio_Ramalho_Daniela_Mantegari

O erro no jetty será o seguinte:

org.apache.solr.common.SolrException log
SEVERE: org.apache.solr.common.SolrException: ERROR: [9788573126] multiple values encountered for non multiValued field author: [Jos? Antonio Ramalho, Daniela Mantegari]
at org.apache.solr.update.DocumentBuilder.toDocument(DocumentBuilder.java:214)
at org.apache.solr.update.processor.RunUpdateProcessor.processAdd(RunUpdateProcessorFactory.java:60)
at org.apache.solr.handler.XMLLoader.processUpdate(XMLLoader.java:139)
at org.apache.solr.handler.XMLLoader.load(XMLLoader.java:69)
at org.apache.solr.handler.ContentStreamHandlerBase.handleRequestBody(ContentStreamHandlerBase.java:54)
at org.apache.solr.handler.RequestHandlerBase.handleRequest(RequestHandlerBase.java:131)
at org.apache.solr.core.SolrCore.execute(SolrCore.java:1316)
at org.apache.solr.servlet.SolrDispatchFilter.execute(SolrDispatchFilter.java:338)
at org.apache.solr.servlet.SolrDispatchFilter.doFilter(SolrDispatchFilter.java:241)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1089)
at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:365)
at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181)
at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:712)
at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:405)
at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:211)
at org.mortbay.jetty.handler.HandlerCollection.handle(HandlerCollection.java:114)
at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:139)
at org.mortbay.jetty.Server.handle(Server.java:285)
at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:502)
at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:835)
at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:641)
at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:208)
at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:378)
at org.mortbay.jetty.bio.SocketConnector$Connection.run(SocketConnector.java:226)
at org.mortbay.thread.BoundedThreadPool$PoolThread.run(BoundedThreadPool.java:442)

Valeu