Di cosa vi parlerò
- L’importanza della ricerca nell’e-commerce
- Cosa offre Magento
- Apache Solr
- Prestazioni
- Qualcosa in più
Perchè è importante
“L’80% di chi acquista usa la ricerca per trovare quello che vuole”
“Chi trova un prodotto attraverso la ricerca interna compra il doppio delle volte”
“L’utente non ha sempre voglia e pazienza di navigare il catalogo per trovare quello che vuole”
Suggerimenti
Rich auto-complete search box
Aumenta le vendite fino a 4 volte
Jakob Nielsen, UseIt.com
Rilevanza dei risultati
“I migliori della classe, per quanto riguarda la rilevanza della ricerca, riescono a fornire il prodotto cercato tra i primi 10 solo nel 67% dei casi”
La ricerca è un aspetto complesso che deve essere adattato al tipo di dominio e al gruppo di utenti.
Aberdeen Group study
Abituati bene
Cosa offre Magento EE
Like
Fulltext serach
Like + Fulltext
- Non perde risultati
- Problemi di prestazioni- Tende a restituire più di quello che serve
- Molto veloce anche su database grandi
- Risultati spesso poco rilevanti- Poca personalizzazione (score e token)
- Migliora la rilevanza della ricerca
- Problemi di prestazioni
Mysql Fulltext
2 Modalità
Natural Language Boolean Mode
- Score basato su TF-IDF (“term frequency / inverse document frequency”)
- MATCH (+keyword1, -keyword2)
- Score basato su numero di match
- Pesi diversi alle parole
- No customizzazioni sul metodo che usa per spezzare in parole - Tokenize
- Numero minimo di caratteri 4 (impostazione modificabile a livello global)
- Elenco di stopwords (impostazione modificabile a livello global)
- Le parole presenti nel 50% dei documenti vengono trattate come stopwords
Caratteristiche comuni
Problemi Fulltext (1/3)
Risultato atteso:
http://dev.mysql.com/doc/refman/5.7/en//fulltext-stopwords.html
Problemi Fulltext
Lunghezza minima della parola 4 caratteri e lista di parole ignorate: “Seven bag”
Query: “school”
Calcolo della rilevanza disatteso, vorrei che il primo risultato fosse l’oggetto che ha la Keyword nel nome.
Problemi Fulltext (2/3)
Rilevanza risultati
Problemi Fulltext (3/3)
Query: “jeens”
Se l’utente sbaglia non ottiene nessun risultato
L’importanza del Did you mean...
Spellcheck
cos’èHigh performance search engine server (basato su )Open Source - 100% Java
caratteristiche- Funzionalità avanzate di ricerca fulltext- Flessibile e configurabile via XML- Estendibile attraverso plug-in- REST-like APIs- Eseguito esternamente alla web-app in un Java servlet container (Tomcat, Jetty …)- Ottimizzato per alti volumi di traffico e predisposto per scalare (replication e sharding)- Indicizza documenti come insiemi di attributi (chiave valore)- Parser di documenti di formati diversi (es. word, PDF, ...)
Attuali versioni stabili: 3.6.2 - 4.8.1
Funzionalità di ricerca avanzata- Stemming Es. "pescare", "pescato", "pescatore" si riferisco a "pesce"
- Fuzzy search (es. shirt = sirt)
- Score dei risultati basati su
(VSM, TF, IDF, doc/query Boost, funzioni custom es. log(sum(popularity,1)))
- Schemaless
- Faccette (Layer navigation)
- Content elevation (risultati sponsorizzati)
- Did you mean?
- Stopwords e Synonyms (divisi per lingua)
- Recommendations (More Like This)
- Geographical search
Configurabile da XML- ${solr.solr.home}/solr.xml
- ${istanceDir}/conf/solrconfig.xml - ${istanceDir}/conf/schema.xml
(configurazioni: cache, ricerca, index …)
(definisce la struttura dei documenti che indicizza)
<fieldType name="text_en"
class="solr.TextField" ..>
<analyzer type="index">
<tokenizer />
<charFilter />
<filter />
</analyzer>
<analyzer ..
</fieldType>
<!-- System required fields. -->
<field name="id" type="string" indexed="true" required="true" stored="true"/>
<field name="in_stock" type="boolean" indexed="true" required="true"/>
<dynamicField name="*_def" type="textgen" indexed="true"/>
<dynamicField name="*_en" type="text_en" indexed="true"/>
<dynamicField name="*_fr" type="text_fr" indexed="true"/>
<dynamicField name="*_de" type="text_de" indexed="true"/>
Esempio di Schema
Solr + Magento EE- Codice EE per l’integrazione + pre-configurazione per Solr 3.6.2
- Sostituisce la ricerca standard con fallback a Mysql nel caso Solr non risponda
- Sistema di indexing integrato
- Ritorna l’elenco degli ID
Solr + Magento EE- Suggerimenti e autocorrezioni
- Calolo della rilevanza in base
al peso degli attributi
- Stopwords multi-lingua
- Faccette (Layer navigation)
- Ricerca con caratteri speciali spider man | spiderman = > spider-man
- Migliori performance Con elevato traffico, Solr evita i frequenti updates
della tabella MySQLcatalogsearch_fulltext e mitiga il
problema dei lock del db.
- Maggiore controllo e personalizzazione
- Scalabile
Cosa Manca?- Auto-completamento o Rich Auto-Complete Search Box
- More Like this
- Indicizzare contenuti correlati ai prodotti (blog aziendale + Manuali in PDF)
- Regole custom per calcolo rilevanza (più venduti ecc.)
Analisi sui dati 7.4 million Posts = 8.18 GB
Like
index 0
index size 0
query 49k-399k ms
(%% - rexExp)
Fulltext (MyISAM)
index 31m18s
index size 2382MiB
query 16-200ms
(boolean - natural)
Fulltext (InnoDB)
index 25m27s
index size ?
query 350-740ms
Apache Solr
index 14m28
index size 2766MiB
query 79ms
Full Text Search Throwdown Bill Karwin, Percona Inc. Ricerca del 2012
2 aspetti da considerare in MagentoTest su strada
Dataset
Ricerca per keyword Navigazione catalogo
Ricerca per keyword
Maximizing Performance and Scalability with Magento Enterprise Editionhttp://www.magentocommerce.com/whitepaper/
Nostri test interniRicerca Concorrenza Tempo di risposta medio
Like 1 2.09
Like 5 3
Like 10 5
Like 20 13.65 sec
Fullsearch 1 1.4
Fullsearch 5 1.96
Fullsearch 10 3.04
Fullsearch 20 5.14
Solr 1 1.45
Solr 5 2.20
Solr 10 3.06
Solr 20 5.4
50K prodotti Quad core4GB di ram
Navigazione faccette
Maximizing Performance and Scalability with Magento Enterprise Editionhttp://www.magentocommerce.com/whitepaper/
Community ?
- Compatibile con Solr 4.x- Autocompletamento- Ricerca per keyword- Indexing integrato
- Configurazione Solr base
- Schema.xml
- Indicizzatore
- QueryBuilder
- Frontend Block
Dove intervenire
app/code/community/JeroenVermeulen/
Solarium/Model/Engine.php
app/code/community/JeroenVermeulen/
Solarium/Model/Engine.php
app/code/community/JeroenVermeulen/
Solarium/Block/Catalogsearch/
Autocomplete.php
${istanceDir}/conf/schema.xml
<schema name="magento_fulltext" version="100">
<types>
...
<fieldType name="text_general" class="solr.TextField" positionIncrementGap="100">
<analyzer type="index">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
<analyzer type="query">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
</types>
<fields>
<field name="id" type="int" indexed="true" stored="true" required="true"/>
<field name="product_id" type="string" indexed="true" stored="true" required="true"
/>
<field name="store_id" type="int" indexed="true" stored="true" required="true"/>
<!-- Magento Product Fulltext -->
<field name="text" type="text_general" indexed="true" stored="true"/>
<field name="url" type="string" indexed="false" stored="true"/>
<field name="image" type="string" indexed="false" stored="true"/>
<field name="name" type="text_general" indexed="true" stored="true"/>
<field name="_version_" type="long" indexed="true" stored="true"/>
</fields>
<uniqueKey>id</uniqueKey>
<solrQueryParser defaultOperator="AND"/>
</schema>
- Schema.xml
- Indicizzatore
- QueryBuilder
- Frontend Block
Dove intervenire
app/code/community/JeroenVermeulen/
Solarium/Model/Engine.php
app/code/community/JeroenVermeulen/
Solarium/Model/Engine.php
app/code/community/JeroenVermeulen/
Solarium/Block/Catalogsearch/
Autocomplete.php
${istanceDir}/conf/schema.xml
public function rebuildIndex( $storeId = null, $productIds = null ) {
...
while ( $product = $products->fetch() ) {
$productModel = Mage::getModel('catalog/product')
->setStoreId($storeId)
->load($product[ 'product_id' ]);
$data = array( 'id' => intval( $product[ 'fulltext_id' ] ),
…
'name' => $productModel->getName()
);
$buffer->createDocument( $data );
}
$solariumResult = $buffer->flush();
…
}
<doc>
<field name="id">123</field>
<field name="product_id">166</field>
<field name="store_id">1</field>
<field name="text">Fulltext index | </field>
<field name="url">url</field>
<field name="image">url_image</field>
<field name="name">Nome prodotto</field>
</doc>
- Schema.xml
- Indicizzatore
- QueryBuilder
- Frontend Block
Dove intervenire
app/code/community/JeroenVermeulen/
Solarium/Model/Engine.php
app/code/community/JeroenVermeulen/
Solarium/Model/Engine.php
app/code/community/JeroenVermeulen/
Solarium/Block/Catalogsearch/
Autocomplete.php
${istanceDir}/conf/schema.xml
…
$query = $this->_client->createSelect();
$query->addParam( 'df', 'text' );
$query->setQuery( $this->_filterString( $queryString ). '*' );
$query->setRows( $this->getConf( 'results/max' ) );
$query->setFields( array( 'product_id', 'score', 'name', 'image', 'url') );
$dismax = $query->getEDisMax();
$dismax->setQueryFields('name^2 product_id^1.5 text');
$solrResultSet = $this->_client->select( $query );
…
{ "response":{ "numFound":15, "start":0, "maxScore":1.0, "docs":[ { "product_id":"164", "name":"Gaming Computer", "score":1.0 }, { "product_id":"162", "name":"Microsoft Wireles", "score":1.0 }, … ]}
Result
http://localhost:8983/solr/select/?spellcheck=true&sort=score+des&spellcheck.q=micr*&json.nl=flat&wt=json&rows=100&omitHeader=true&df=text&fl=product_id,score,text,name&start=0&q=micr*&timeAllowed=5&fq=store_id:1&spellcheck.alternativeTermCount=1
Query
- Schema.xml
- Indicizzatore
- QueryBuilder
- Frontend Block
Dove intervenire
app/code/community/JeroenVermeulen/
Solarium/Model/Engine.php
app/code/community/JeroenVermeulen/
Solarium/Model/Engine.php
app/code/community/JeroenVermeulen/
Solarium/Block/Catalogsearch/
Autocomplete.php
${istanceDir}/conf/schema.xml
class JeroenVermeulen_Solarium_Block_Catalogsearch_Autocomplete extends
Mage_CatalogSearch_Block_Autocomplete {
…
public function getSuggestProduct()
{
if ( ! $this->_suggestProduct ) {
$query = $this->helper('catalogsearch')->getQueryText();
$query = $query . '*';
$counter = 0;
$data = array();
$storeId = Mage::app()->getStore()->getId();
$engine = Mage::getSingleton('jeroenvermeulen_solarium/engine');
$products = $engine->queryAutosuggest( $storeId, $query, 1 );
foreach ( $products as $value) {
$_datap = array(
'id' => $value['product_id'],
'row_class' => ( ++$counter ) % 2 ? 'odd' : 'even',
'text' => $value['name'],
…
);
$datap[] = $_datap;
}
$this->_suggestProduct = $datap;
}
return $this->_suggestProduct;
}
…
}
Riepilogo
+ Rilevanza nella ricerca+ Adattabile al dominio+ Pronto per scalare+ Migliori prestazioni
+
Riepilogo
+ Rilevanza nella ricerca+ Adattabile al dominio+ Pronto per scalare+ Migliori prestazioni
+
= Migliore esperienza utente
?Domande
Luca PeressiniMail: [email protected]: @PeressiniLucalinkedin: http://it.linkedin.com/pub/luca-peressini/56/182/128