SQL 구체화된 뷰를 사용하여 Rails 및 ActiveRecord를 즉시 검색합니다.
데모 앱을 참조하세요( demo_app/
폴더에 있는 코드):
ElasticSearch와 같은 외부 시스템 없이 Rails 앱에 매우 빠른 검색 기능을 추가하세요. 이제 우리가 이미 알고 좋아하는 ActiveRecord/Arel 표현식을 작성하고 이를 SQL 구체화된 뷰로 변환하는 것이 마법처럼 간단해졌습니다. ActiveRecord를 사용하여 쿼리하고 구성할 준비가 되었습니다. Rails에 대해 여러분이 좋아하는 모든 것을 더 빠르게 제공합니다.
Rails의 검색 속도가 느려지는 이유는 무엇입니까? 대규모 테이블, 많은 조인, 하위 쿼리, 누락되거나 사용되지 않은 인덱스, 복잡한 쿼리. 또한 느린가요? Ruby를 통해 여러 외부 시스템의 데이터를 조정하여 검색 결과를 생성합니다.
SearchCraft를 사용하면 강력한 SQL 구체화된 뷰를 작성하고 사용하여 검색 및 보고 쿼리 결과를 미리 계산하는 것이 쉽습니다. 데이터베이스 인덱스와 비슷하지만 복잡한 쿼리에 사용됩니다.
구체화된 뷰는 PostgreSQL, Oracle 및 SQL Server*의 뛰어난 기능입니다. 미리 계산된 쿼리 결과 테이블입니다. 쿼리 속도가 빠릅니다. 그들은 굉장합니다. 다른 검색 시스템과 마찬가지로 새 데이터로 새로고침할 시기를 제어할 수 있습니다.
Rails 및 ActiveRecord 내에서 일반 테이블과 마찬가지로 읽기 전용 구체화된 뷰에 액세스할 수 있습니다. 함께 참여할 수도 있습니다. ActiveRecord 모델, 범위 및 연결에서 사용할 수 있습니다.
class ProductSearch < ActiveRecord :: Base
include SearchCraft :: Model
end
완료. 뷰에서 설명하는 모든 열은 모델의 속성이 됩니다.
기본 뷰에 product_id
, product_name
, reviews_count
및 reviews_average
열이 있는 경우 다른 ActiveRecord 모델처럼 쿼리할 수 있습니다.
ProductSearch . all
[ #<ProductSearch product_id: 2, product_name: "iPhone 15", reviews_count: 5, reviews_average: 0.38e1>,
#<ProductSearch product_id: 1, product_name: "Laptop 3", reviews_count: 5, reviews_average: 0.28e1>,
#<ProductSearch product_id: 4, product_name: "Monopoly", reviews_count: 3, reviews_average: 0.2e1>]
ProductSearch . order ( reviews_average : :desc )
[ #<ProductSearch product_id: 2, product_name: "iPhone 15", reviews_count: 5, reviews_average: 0.38e1>,
#<ProductSearch product_id: 1, product_name: "Laptop 3", reviews_count: 5, reviews_average: 0.28e1>,
#<ProductSearch product_id: 4, product_name: "Monopoly", reviews_count: 3, reviews_average: 0.2e1>]
외래 키를 포함하면 belongs_to
연관을 사용할 수 있습니다. 범위를 추가할 수 있습니다. 메소드를 추가할 수 있습니다. 이를 나머지 SQL 데이터베이스에 대한 쿼리의 시작점으로 사용할 수 있습니다. 그냥 일반적인 ActiveRecord 모델입니다.
이 모든 것은 이미 Rails와 ActiveRecord를 통해 가능합니다. SearchCraft 성과는 구체화된 견해를 가지고 살아가는 것을 사소한 것으로 만드는 것입니다. 새로 고치고 작성하는 것은 쉽지 않습니다.
구체화된 각 SearchCraft는 쿼리가 생성되거나 마지막으로 새로 고쳐진 시점의 쿼리 결과에 대한 스냅샷을 보여줍니다. 이는 쿼리에서 내용이 파생된 테이블과 같습니다.
SearchCraft 구체화된 뷰의 기본 데이터가 변경되어 새로 고치고 싶다면 refresh!
당신의 모델 수업에. 이는 SearchCraft::Model
믹스인에 의해 제공됩니다.
ProductSearch . refresh!
이 ActiveRecord 관계/배열을 Rails 뷰에 전달하고 렌더링할 수 있습니다. 이를 다른 테이블에 조인하고 추가 범위를 적용할 수 있습니다.
그러나 SearchCraft의 가장 큰 기능은 구체화된 뷰를 작성한 다음 이를 반복하는 데 도움이 된다는 것입니다.
ActiveRecord 표현식, Arel 표현식 또는 일반 SQL로 디자인하세요. 롤백하고 다시 실행할 마이그레이션이 없습니다. 데이터베이스의 SQL 뷰가 Rails 앱의 SearchCraft 코드와 일치하는지 추적할 필요가 없습니다. SearchCraft는 구체화된 뷰를 자동으로 생성하고 업데이트합니다.
SearchCraft 보기를 업데이트하고 테스트를 실행하면 작동합니다. SearchCraft 보기를 업데이트하고 개발 앱을 새로 고치면 작동합니다. rails console
열면 작동합니다. 그런 다음 뷰를 업데이트하고 reload!
, 작동합니다. 어디서나 프로덕션에 배포하면 작동합니다.
SearchCraft를 사용하여 구체화된 뷰를 디자인하는 것은 어떤 모습입니까? 위의 ProductSearch
모델의 경우 SearchCraft::Builder
상속하고 view_scope
메서드 또는 view_select_sql
메서드를 제공하는 ProductSearchBuilder
클래스를 만듭니다.
class ProductSearchBuilder < SearchCraft :: Builder
def view_scope
Product . where ( active : true )
. select (
"products.id AS product_id" ,
"products.name AS product_name" ,
"(SELECT COUNT(*) FROM product_reviews WHERE product_reviews.product_id = products.id) AS reviews_count" ,
"(SELECT AVG(rating) FROM product_reviews WHERE product_reviews.product_id = products.id) AS reviews_average"
)
end
end
view_scope
메소드는 ActiveRecord 관계를 반환해야 합니다. 원하는 만큼 간단할 수도 있고 복잡할 수도 있습니다. 조인, 하위 쿼리 및 ActiveRecord로 수행할 수 있는 모든 작업을 사용할 수 있습니다. 위의 예에서 우리는:
products
테이블에서 id
및 name
열을 선택합니다. 나중에 앱에서 Product
모델에 조인하기 위한 외래 키로 product_id
사용할 수 있습니다.product_reviews
테이블의 rating
열을 계산하고 평균을 계산하는 SQL 하위 쿼리를 사용하여 새로운 reviews_count
및 reviews_average
열을 만듭니다. SearchCraft는 이를 구체화된 뷰로 변환하고 데이터베이스에 생성하며, 다음에 개발 앱을 다시 로드하거나 테스트를 실행할 때 위의 ProductSearch
모델이 이를 사용하기 시작합니다. 변경하면 SearchCraft가 자동으로 뷰를 삭제하고 다시 생성합니다.
Rails 콘솔에 앱을 로드하거나, 테스트를 실행하거나, 개발 앱을 새로 고치면 ProductSearch
모델이 자동으로 업데이트되어 ProductSearchBuilder
의 변경 사항과 일치하게 됩니다.
ProductSearch . all
[ #<ProductSearch product_id: 2, product_name: "iPhone 15", reviews_count: 5, reviews_average: 0.38e1>,
#<ProductSearch product_id: 1, product_name: "Laptop 3", reviews_count: 5, reviews_average: 0.28e1>,
#<ProductSearch product_id: 4, product_name: "Monopoly", reviews_count: 3, reviews_average: 0.2e1>]
ProductSearch . order ( reviews_average : :desc )
[ #<ProductSearch product_id: 2, product_name: "iPhone 15", reviews_count: 5, reviews_average: 0.38e1>,
#<ProductSearch product_id: 1, product_name: "Laptop 3", reviews_count: 5, reviews_average: 0.28e1>,
#<ProductSearch product_id: 4, product_name: "Monopoly", reviews_count: 3, reviews_average: 0.2e1>]
SQL을 작성하려면 대신 view_select_sql
메소드를 사용할 수 있습니다.
class NumberBuilder < SearchCraft :: Builder
# Write SQL that produces 5 rows, with a 'number' column containing the number of the row
def view_select_sql
"SELECT generate_series(1, 5) AS number;"
end
end
class Number < ActiveRecord :: Base
include SearchCraft :: Model
end
Number . all
[ #<Number number: 1>, #<Number number: 2>, #<Number number: 3>, #<Number number: 4>, #<Number number: 5>]
구체화된 뷰의 뛰어난 기능은 인덱스를 추가할 수 있다는 것입니다. 심지어 고유한 인덱스도 있습니다.
현재 인덱스를 추가하는 메커니즘은 빌더 클래스에 view_indexes
메소드를 추가하는 것입니다.
예를 들어 NumberBuilder
의 number
열에 고유 인덱스를 추가할 수 있습니다.
class NumberBuilder < SearchCraft :: Builder
def view_indexes
{
number : { columns : [ "number" ] , unique : true }
}
end
또는 이전의 ProductSearchBuilder
에 대한 여러 인덱스:
class ProductSearchBuilder < SearchCraft :: Builder
def view_indexes
{
id : { columns : [ "product_id" ] , unique : true } ,
product_name : { columns : [ "product_name" ] } ,
reviews_count : { columns : [ "reviews_count" ] } ,
reviews_average : { columns : [ "reviews_average" ] }
}
end
end
기본적으로 인덱스는 using: :btree
합니다. :gin
, :gist
와 같이 레일에서 사용할 수 있는 다른 인덱싱 방법을 사용할 수도 있고, trigram
확장을 사용하는 경우 :gin_trgm_ops
사용할 수도 있습니다. 아래에서 설명하는 것처럼 텍스트 검색을 설정할 때 유용할 수 있습니다.
구체화된 뷰의 또 다른 이점은 검색에 최적화된 열을 만들 수 있다는 것입니다. 위의 예를 들어 ProductSearchBuilder
에서 reviews_average
미리 계산했으므로 특정 평균 평점을 가진 제품을 쉽게 찾을 수 있습니다.
ProductSearch . where ( "reviews_average > 4" )
ActiveRecord의 놀라운 기능은 쿼리를 함께 결합하는 기능입니다. 구체화된 뷰는 기본 ActiveRecord 모델이므로 다른 쿼리와 함께 결합할 수 있습니다.
MV의 ProductSearch#product_id
와 테이블 Product#id
기본 키 간의 연결을 설정해 보겠습니다.
class ProductSearch < ActiveRecord :: Base
include SearchCraft :: Model
belongs_to :product , foreign_key : :product_id , primary_key : :id
end
이제 ActiveRecord 쿼리를 사용하여 테이블을 조인하거나 즉시 로드할 수 있습니다. 다음은 각 ProductSearch#product
연결이 미리 로드된 ProductSearch
개체의 관계를 반환합니다.
ProductSearch . includes ( :product ) . where ( "reviews_average > 4" )
다음은 ProductSearch
구체화된 뷰 검색을 기반으로 Product
개체를 반환합니다.
class Product
has_one :product_search , foreign_key : :product_id , primary_key : :id , class_name : "ProductSearch"
end
Product . joins ( :product_searches ) . merge (
ProductSearch . where ( "reviews_average > 4" )
)
PostgreSQL에는 to_tsvector
, ts_rank
및 websearch_to_tsquery
와 같은 기능의 조합을 사용하여 텍스트 검색을 위한 솔루션이 제공됩니다.
더 많은 문서가 보류 중입니다. 구체화된 뷰에서 이러한 함수를 사용하는 방법에 대한 예는 test/searchcraft/builder/test_text_search.rb
참조하세요.
저는 아직 Store Connect의 코드에서 이 솔루션을 추출하는 중입니다.
하나의 SearchCraft 구체화된 뷰가 있으면 이에 의존하는 또 다른 뷰를 생성할 수 있습니다. depends_on
메소드를 사용하여 이 작업도 수행할 수 있습니다.
class SquaredBuilder < SearchCraft :: Builder
depends_on "NumberBuilder"
def view_select_sql
"SELECT number, number * number AS squared FROM #{ Number . table_name } ;"
end
end
class Squared < ActiveRecord :: Base
include SearchCraft :: Model
end
NumberBuilder
를 변경하면 SearchCraft가 자동으로 Number
및 Squared
구체화된 뷰를 모두 삭제하고 다시 생성합니다.
Squared . all
[ #<Squared number: 1, squared: 1>,
#<Squared number: 2, squared: 4>,
#<Squared number: 3, squared: 9>,
#<Squared number: 4, squared: 16>,
#<Squared number: 5, squared: 25>]
복잡한 SQL 또는 Arel 표현식을 작성하는 데 자신이 없습니까? 나도 마찬가지다. GPT4나 GitHub Copilot에 문의합니다. 나는 스키마와 테이블의 성격을 설명하고 SQL을 작성하도록 요청한 다음 이를 Arel로 변환하도록 요청합니다. 아니면 SQL의 작은 조각을 제공하고 이를 Arel로 변환하도록 요청합니다. 그런 다음 결과를 SearchCraft 빌더 클래스에 복사하여 붙여넣습니다.
SQL 또는 Arel로 검색 쿼리를 표현하고 이를 SearchCraft 구체화된 뷰에 넣는 방법을 배우는 것은 절대적으로 가치가 있습니다. 사용자는 매우 빠른 경험을 갖게 될 것입니다.
Rails 앱 내에서 Gemfile에 gem을 추가하세요.
bundle add searchcraft
SearchCraft는 필요한 내부 DB 테이블을 자동으로 생성하므로 실행할 데이터베이스 마이그레이션이 없습니다. 물론 구체화된 뷰를 자동으로 생성하고 다시 생성합니다.
모든 Rails 앱 내에서 이 튜토리얼을 따라갈 수 있습니다. Rails 앱이 없다면 이 프로젝트의 demo_app
폴더에 있는 앱을 사용하세요.
보석을 설치합니다:
bundle add searchcraft
기존 애플리케이션 모델 중 하나인 Product
선택하면 이에 대한 간단한 구체화된 뷰가 생성됩니다. 예를 들어, 우리는 HTML 보기에서 상위 5개 판매 제품과 이에 사용할 일부 세부 정보를 빠르게 얻을 수 있는 방법을 원합니다.
새 ActiveRecord 모델 파일 app/models/product_latest_arrival.rb
생성:
class ProductLatestArrival < ActiveRecord :: Base
include SearchCraft :: Model
end
Rails 규칙에 따라 이 모델은 product_latest_arrivals
라는 SQL 테이블이나 뷰를 찾습니다. 아직 존재하지 않습니다.
rails console
열고 쿼리해 보면 이를 확인할 수 있습니다.
ProductLatestArrival . all
# ActiveRecord::StatementInvalid ERROR: relation "product_latest_arrivals" does not exist
구체화된 뷰를 정의하기 위해 새로운 SearchCraft 빌더 클래스를 생성할 수 있습니다. 새 파일 app/searchcraft/product_latest_arrival_builder.rb
만듭니다.
저는 여러분의 빌더에게 app/searchcraft
제안하지만 Rails가 자동으로 로드하는 모든 app/
하위 폴더로 이동할 수 있습니다.
class ProductLatestArrivalBuilder < SearchCraft :: Builder
def view_scope
Product . order ( created_at : :desc ) . limit ( 5 )
end
end
rails console``, run
하고 쿼리를 다시 확인하세요.
reload!
ProductLatestArrival . all
ProductLatestArrival Load ( 1.3 ms ) SELECT "product_latest_arrivals" . * FROM "product_latest_arrivals"
=>
[ #<ProductLatestArrival:0x000000010a737d18
id : 1 ,
name : "Rustic Wool Coat" ,
active : true ,
created_at : Fri , 25 Aug 2023 07 :15 :16 . 995228000 UTC + 00 : 00 ,
updated_at : Fri , 25 Aug 2023 07 :15 :16 . 995228000 UTC + 00 : 00 ,
image_url : "https://loremflickr.com/g/320/320/coat?lock=1" > ,
...
Gemfile
에 annotate
gem이 설치되어 있는 경우 product_latest_arrival.rb
모델이 구체화된 뷰의 열을 반영하도록 업데이트되었음을 확인할 수 있습니다.
# == Schema Information
#
# Table name: product_latest_arrivals
#
# id :bigint
# name :string
# active :boolean
# created_at :datetime
# updated_at :datetime
# image_url :string
#
class ProductLatestArrival < ActiveRecord :: Base
include SearchCraft :: Model
end
애플리케이션이 소스 제어 하에 있는 경우 db/schema.rb
최신 보기 정의를 반영하도록 업데이트되었음을 확인할 수도 있습니다. git diff db/schema.rb
실행합니다:
create_view "product_latest_arrivals" , materialized : true , sql_definition : <<-SQL
SELECT products.id,
products.name,
products.active,
products.created_at,
products.updated_at,
products.image_url
FROM products
LIMIT 5;
SQL
이제 빌더에서 view_scope
계속 변경하고 reload!
Rails 콘솔에서 변경 사항을 테스트해 보세요.
예를 들어 각 열에 대해 SQL 표현식을 사용하여 원하는 열만 select()
할 수 있습니다.
class ProductLatestArrivalBuilder < SearchCraft :: Builder
def view_scope
Product
. order ( created_at : :desc )
. limit ( 5 )
. select (
"products.id as product_id" ,
"products.name as product_name" ,
"products.image_url as product_image_url" ,
)
end
end
또는 Arel 표현식을 사용하여 SQL을 작성할 수 있습니다.
class ProductLatestArrivalBuilder < SearchCraft :: Builder
def view_scope
Product
. order ( created_at : :desc )
. limit ( 5 )
. select (
Product . arel_table [ :id ] . as ( "product_id" ) ,
Product . arel_table [ :name ] . as ( "product_name" ) ,
Product . arel_table [ :image_url ] . as ( "product_image_url" ) ,
)
end
end
데이터 업데이트는 어떻습니까? 더 많은 Products
만들어 보겠습니다.
Product . create! ( name : "Starlink" )
Product . create! ( name : "Fishing Rod" )
ProductLatestArrival.all
검사하면 이러한 새 제품을 찾을 수 없습니다 . 이는 구체화된 뷰가 생성되거나 마지막으로 새로 고쳐진 시점의 데이터에 대한 스냅샷이기 때문입니다.
보기를 새로 고치려면:
ProductLatestArrival . refresh!
또는 모든 보기를 새로 고치려면 다음을 수행하십시오.
SearchCraft :: Model . refresh_all!
그리고 최근에 새로 도착한 항목이 이제 구체화된 뷰에 있는지 확인합니다.
ProductLatestArrival . pluck ( :name )
=> [ "Fishing Rod" , "Starlink" , "Sleek Steel Bag" , "Ergonomic Plastic Bench" , "Fantastic Wooden Keyboard" ]
이 튜토리얼의 아티팩트를 제거하려는 경우. 먼저 데이터베이스 스키마에서 구체화된 뷰를 삭제합니다.
ProductLatestArrivalBuilder . new . drop_view!
그런 다음 파일을 제거하고 git checkout .
다른 변경 사항을 되돌리려면
rm app/searchcraft/product_latest_arrival_builder.rb
rm app/models/product_latest_arrival.rb
git checkout db/schema.rb
SearchCraft는 두 가지 레이크 작업을 제공합니다.
rake searchcraft:refresh
- 모든 구체화된 뷰 새로 고침rake searchcraft:rebuild
- 재생성해야 하는 뷰가 있는지 확인 이를 Rails 앱에 추가하려면 Rakefile
하단에 다음을 추가하세요.
SearchCraft . load_tasks
Builder
하위 클래스를 관찰하고 자동으로 변경 사항을 감지하여 뷰 스키마를 구체화하고 다시 생성합니다.refresh!
구체화된 뷰 내용db/schema.rb
덤프합니다.annotate
gem이 설치된 경우 구체화된 뷰가 업데이트될 때마다 모델에 주석을 답니다.rake searchcraft:refresh
하고 재생성해야 하는 뷰가 있는지 확인합니다 rake searchcraft:rebuild
저장소를 확인한 후 bin/setup
실행하여 종속 항목을 설치하세요. 그런 다음 rake test
실행하여 테스트를 실행합니다. 실험할 수 있는 대화형 프롬프트를 보려면 bin/console
실행할 수도 있습니다.
이 gem을 로컬 머신에 설치하려면, bundle exec rake install
실행하세요. 새 버전을 출시하려면 version.rb
에서 버전 번호를 업데이트한 다음, 버전에 대한 git 태그를 생성하는 bundle exec rake release
실행하고, git 커밋과 생성된 태그를 푸시하고, .gem
파일을 rubygems에 푸시하세요. org.
버전 번호를 올리려면 다음 안내를 따르세요.
gem bump
명령(예: gem bump -v patch
을 사용하십시오.demo_app/Gemfile.lock
업데이트(예: (cd demo_app; bundle)
git add demo_app/Gemfile.lock; git commit --amend --no-edit
rake release
컷 gem bump -v patch
(cd demo_app; bundle)
git add demo_app/Gemfile.lock; git commit --amend --no-edit
git push
rake release
버그 보고서 및 끌어오기 요청은 GitHub(https://github.com/drnic/searchcraft)에서 환영합니다. 이 프로젝트는 협업을 위한 안전하고 환영받는 공간이 되도록 의도되었으며, 기여자는 행동 강령을 준수해야 합니다.
이 gem은 MIT 라이선스 조건에 따라 오픈 소스로 제공됩니다.
Searchcraft 프로젝트의 코드베이스, 이슈 추적기, 채팅방 및 메일링 목록에서 상호 작용하는 모든 사람은 행동 강령을 따라야 합니다.
rails db:rollback
, 재구축 마이그레이션 SQL, rails db:migrate
및 테스트)이 느려졌습니다. 또한 버그가 발생했습니다. 단계를 실행하는 것을 잊어버리고 이상한 동작을 보게 되었습니다. 상대적으로 정적인 뷰나 구체화된 뷰가 있고 Rails 마이그레이션을 사용하고 싶다면 scenic
Gem을 사용해 보세요. 이 searchcraft
gem은 여전히 뷰 refresh
기능과 schema.rb
에 뷰 추가를 위해 scenic
에 의존합니다.