Commit 00cf42bca0d521f4e9c0d2b8ee8f67cfe79ad89b

Authored by Andrew Kane
1 parent 0984bc9a

Moved performant conversions section [skip ci]

Showing 1 changed file with 87 additions and 89 deletions   Show diff stats
README.md
... ... @@ -679,7 +679,93 @@ end
679 679  
680 680 Reindex and set up a cron job to add new conversions daily. For zero downtime deployment, temporarily set `conversions: false` in your search calls until the data is reindexed.
681 681  
682   -For a performant way to reindex conversion data, check out [performant conversions](#performant-conversions).
  682 +### Performant Conversions
  683 +
  684 +A performant way to do conversions is to cache them to prevent N+1 queries. For Postgres, create a migration with:
  685 +
  686 +```ruby
  687 +add_column :products, :search_conversions, :jsonb
  688 +```
  689 +
  690 +For MySQL, use `:json`, and for others, use `:text` with a [JSON serializer](https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html).
  691 +
  692 +Next, update your model. Create a separate method for conversion data so you can use [partial reindexing](#partial-reindexing).
  693 +
  694 +```ruby
  695 +class Product < ApplicationRecord
  696 + searchkick conversions: [:conversions]
  697 +
  698 + def search_data
  699 + {
  700 + name: name,
  701 + category: category
  702 + }.merge(conversions_data)
  703 + end
  704 +
  705 + def conversions_data
  706 + {
  707 + conversions: search_conversions || {}
  708 + }
  709 + end
  710 +end
  711 +```
  712 +
  713 +Deploy and reindex your data. For zero downtime deployment, temporarily set `conversions: false` in your search calls until the data is reindexed.
  714 +
  715 +```ruby
  716 +Product.reindex
  717 +```
  718 +
  719 +Then, create a job to update the conversions column and reindex records with new conversions. Here’s one you can use for Searchjoy:
  720 +
  721 +```ruby
  722 +class ReindexConversionsJob < ApplicationJob
  723 + def perform(class_name, since: nil, reindex: true)
  724 + # get records that have a recent conversion
  725 + recently_converted_ids =
  726 + Searchjoy::Conversion.where(convertable_type: class_name).where(created_at: since..)
  727 + .order(:convertable_id).distinct.pluck(:convertable_id)
  728 +
  729 + # split into batches
  730 + recently_converted_ids.in_groups_of(1000, false) do |ids|
  731 + # fetch conversions
  732 + conversions =
  733 + Searchjoy::Conversion.where(convertable_id: ids, convertable_type: class_name)
  734 + .joins(:search).where.not(searchjoy_searches: {user_id: nil})
  735 + .group(:convertable_id, :query).distinct.count(:user_id)
  736 +
  737 + # group by record
  738 + conversions_by_record = {}
  739 + conversions.each do |(id, query), count|
  740 + (conversions_by_record[id] ||= {})[query] = count
  741 + end
  742 +
  743 + # update conversions column
  744 + model = Searchkick.load_model(class_name)
  745 + model.transaction do
  746 + conversions_by_record.each do |id, conversions|
  747 + model.where(id: id).update_all(search_conversions: conversions)
  748 + end
  749 + end
  750 +
  751 + # reindex conversions data
  752 + model.where(id: ids).reindex(:conversions_data) if reindex
  753 + end
  754 + end
  755 +end
  756 +```
  757 +
  758 +Run the job:
  759 +
  760 +```ruby
  761 +ReindexConversionsJob.perform_now("Product")
  762 +```
  763 +
  764 +And set it up to run daily.
  765 +
  766 +```ruby
  767 +ReindexConversionsJob.perform_later("Product", since: 1.day.ago)
  768 +```
683 769  
684 770 ## Personalized Results
685 771  
... ... @@ -1585,94 +1671,6 @@ And use:
1585 1671 Product.reindex(:prices_data)
1586 1672 ```
1587 1673  
1588   -### Performant Conversions
1589   -
1590   -Cache conversions to prevent N+1 queries. For Postgres, create a migration with:
1591   -
1592   -```ruby
1593   -add_column :products, :search_conversions, :jsonb
1594   -```
1595   -
1596   -For MySQL, use `:json`, and for others, use `:text` with a [JSON serializer](https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html).
1597   -
1598   -Next, update your model. Create a separate method for conversion data so you can use [partial reindexing](#partial-reindexing).
1599   -
1600   -```ruby
1601   -class Product < ApplicationRecord
1602   - searchkick conversions: [:conversions]
1603   -
1604   - def search_data
1605   - {
1606   - name: name,
1607   - category: category
1608   - }.merge(conversions_data)
1609   - end
1610   -
1611   - def conversions_data
1612   - {
1613   - conversions: search_conversions || {}
1614   - }
1615   - end
1616   -end
1617   -```
1618   -
1619   -Deploy and reindex your data. For zero downtime deployment, temporarily set `conversions: false` in your search calls until the data is reindexed.
1620   -
1621   -```ruby
1622   -Product.reindex
1623   -```
1624   -
1625   -Then, create a job to update the conversions column and reindex records with new conversions. Here’s one you can use for Searchjoy:
1626   -
1627   -```ruby
1628   -class ReindexConversionsJob < ApplicationJob
1629   - def perform(class_name, since: nil, reindex: true)
1630   - # get records that have a recent conversion
1631   - recently_converted_ids =
1632   - Searchjoy::Conversion.where(convertable_type: class_name).where(created_at: since..)
1633   - .order(:convertable_id).distinct.pluck(:convertable_id)
1634   -
1635   - # split into batches
1636   - recently_converted_ids.in_groups_of(1000, false) do |ids|
1637   - # fetch conversions
1638   - conversions =
1639   - Searchjoy::Conversion.where(convertable_id: ids, convertable_type: class_name)
1640   - .joins(:search).where.not(searchjoy_searches: {user_id: nil})
1641   - .group(:convertable_id, :query).distinct.count(:user_id)
1642   -
1643   - # group by record
1644   - conversions_by_record = {}
1645   - conversions.each do |(id, query), count|
1646   - (conversions_by_record[id] ||= {})[query] = count
1647   - end
1648   -
1649   - # update conversions column
1650   - model = Searchkick.load_model(class_name)
1651   - model.transaction do
1652   - conversions_by_record.each do |id, conversions|
1653   - model.where(id: id).update_all(search_conversions: conversions)
1654   - end
1655   - end
1656   -
1657   - # reindex conversions data
1658   - model.where(id: ids).reindex(:conversions_data) if reindex
1659   - end
1660   - end
1661   -end
1662   -```
1663   -
1664   -Run the job:
1665   -
1666   -```ruby
1667   -ReindexConversionsJob.perform_now("Product")
1668   -```
1669   -
1670   -And set it up to run daily.
1671   -
1672   -```ruby
1673   -ReindexConversionsJob.perform_later("Product", since: 1.day.ago)
1674   -```
1675   -
1676 1674 ## Advanced
1677 1675  
1678 1676 Searchkick makes it easy to use the Elasticsearch or OpenSearch DSL on its own.
... ...