Account key for search records

Add an account key field to improve search performance. This field
allows us to filter the records by account directly in the fulltext
index, so we only need to examine rows belonging to the relevant account.
This commit is contained in:
Donal McBreen
2025-11-21 09:36:18 +00:00
parent 7f76835150
commit 70dd754cf7
4 changed files with 92 additions and 21 deletions
+2 -1
View File
@@ -19,7 +19,7 @@ module Searchable
def update_in_search_index
Search::Record.for_account(account_id).upsert_all(
[ search_record_attributes.merge(id: ActiveRecord::Type::Uuid.generate) ],
update_only: [ :card_id, :board_id, :title, :content, :created_at ]
update_only: [ :card_id, :board_id, :title, :content, :created_at, :account_key ]
)
end
@@ -33,6 +33,7 @@ module Searchable
def search_record_attributes
{
account_id: account_id,
account_key: "account#{account_id}",
searchable_type: self.class.name,
searchable_id: id,
card_id: search_card_id,
+5 -3
View File
@@ -37,14 +37,16 @@ class Search::Record < ApplicationRecord
scope :for_query, ->(query:, user:) do
if query.valid? && user.board_ids.any?
matching(query.to_s).for_user(user)
matching(query.to_s, user.account_id).for_user(user)
else
none
end
end
scope :matching, ->(query) do
where("MATCH(#{table_name}.content, #{table_name}.title) AGAINST(? IN BOOLEAN MODE)", query)
scope :matching, ->(query, account_id) do
account_key = "account#{account_id}"
full_query = "+#{account_key} +(#{query})"
where("MATCH(#{table_name}.account_key, #{table_name}.content, #{table_name}.title) AGAINST(? IN BOOLEAN MODE)", full_query)
end
scope :for_user, ->(user) do
@@ -0,0 +1,37 @@
class AddAccountKeyToSearchRecords < ActiveRecord::Migration[8.2]
def up
16.times do |shard_id|
table_name = "search_records_#{shard_id}"
# Add account_key column
add_column table_name, :account_key, :string, null: false, default: ""
# Backfill existing records with account_key
execute <<-SQL
UPDATE #{table_name}
SET account_key = CONCAT('account', account_id)
SQL
# Create new fulltext index including account_key
add_index table_name, [:account_key, :content, :title], type: :fulltext
# Drop existing fulltext index
remove_index table_name, column: [:content, :title], type: :fulltext
end
end
def down
16.times do |shard_id|
table_name = "search_records_#{shard_id}"
# Drop new fulltext index
remove_index table_name, column: [:account_key, :content, :title], type: :fulltext
# Restore original fulltext index
add_index table_name, [:content, :title], type: :fulltext
# Remove account_key column
remove_column table_name, :account_key
end
end
end
Generated
+48 -17
View File
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
ActiveRecord::Schema[8.2].define(version: 2025_11_21_092508) do
create_table "accesses", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.datetime "accessed_at"
t.uuid "account_id", null: false
@@ -408,8 +408,24 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.index ["user_id"], name: "index_search_queries_on_user_id"
end
create_table "search_records", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
t.string "account_key", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
t.datetime "created_at", null: false
t.uuid "searchable_id", null: false
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_on_account_id"
t.index ["account_key", "content", "title"], name: "index_search_records_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_0", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -418,12 +434,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_0_on_account_id"
t.index ["content", "title"], name: "index_search_records_0_on_content_and_title", type: :fulltext
t.index ["account_key", "content", "title"], name: "index_search_records_0_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_0_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_1", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -432,12 +449,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_1_on_account_id"
t.index ["content", "title"], name: "index_search_records_1_on_content_and_title", type: :fulltext
t.index ["account_key", "content", "title"], name: "index_search_records_1_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_1_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_10", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -446,12 +464,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_10_on_account_id"
t.index ["content", "title"], name: "index_search_records_10_on_content_and_title", type: :fulltext
t.index ["account_key", "content", "title"], name: "index_search_records_10_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_10_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_11", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -460,12 +479,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_11_on_account_id"
t.index ["content", "title"], name: "index_search_records_11_on_content_and_title", type: :fulltext
t.index ["account_key", "content", "title"], name: "index_search_records_11_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_11_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_12", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -474,12 +494,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_12_on_account_id"
t.index ["content", "title"], name: "index_search_records_12_on_content_and_title", type: :fulltext
t.index ["account_key", "content", "title"], name: "index_search_records_12_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_12_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_13", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -488,12 +509,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_13_on_account_id"
t.index ["content", "title"], name: "index_search_records_13_on_content_and_title", type: :fulltext
t.index ["account_key", "content", "title"], name: "index_search_records_13_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_13_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_14", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -502,12 +524,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_14_on_account_id"
t.index ["content", "title"], name: "index_search_records_14_on_content_and_title", type: :fulltext
t.index ["account_key", "content", "title"], name: "index_search_records_14_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_14_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_15", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -516,12 +539,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_15_on_account_id"
t.index ["content", "title"], name: "index_search_records_15_on_content_and_title", type: :fulltext
t.index ["account_key", "content", "title"], name: "index_search_records_15_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_15_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_2", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -530,12 +554,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_2_on_account_id"
t.index ["content", "title"], name: "index_search_records_2_on_content_and_title", type: :fulltext
t.index ["account_key", "content", "title"], name: "index_search_records_2_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_2_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_3", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -544,12 +569,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_3_on_account_id"
t.index ["content", "title"], name: "index_search_records_3_on_content_and_title", type: :fulltext
t.index ["account_key", "content", "title"], name: "index_search_records_3_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_3_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_4", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -558,12 +584,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_4_on_account_id"
t.index ["content", "title"], name: "index_search_records_4_on_content_and_title", type: :fulltext
t.index ["account_key", "content", "title"], name: "index_search_records_4_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_4_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_5", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -572,12 +599,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_5_on_account_id"
t.index ["content", "title"], name: "index_search_records_5_on_content_and_title", type: :fulltext
t.index ["account_key", "content", "title"], name: "index_search_records_5_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_5_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_6", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -586,12 +614,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_6_on_account_id"
t.index ["content", "title"], name: "index_search_records_6_on_content_and_title", type: :fulltext
t.index ["account_key", "content", "title"], name: "index_search_records_6_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_6_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_7", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -600,12 +629,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_7_on_account_id"
t.index ["content", "title"], name: "index_search_records_7_on_content_and_title", type: :fulltext
t.index ["account_key", "content", "title"], name: "index_search_records_7_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_7_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_8", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -614,12 +644,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_8_on_account_id"
t.index ["content", "title"], name: "index_search_records_8_on_content_and_title", type: :fulltext
t.index ["account_key", "content", "title"], name: "index_search_records_8_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_8_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_9", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -628,7 +659,7 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_9_on_account_id"
t.index ["content", "title"], name: "index_search_records_9_on_content_and_title", type: :fulltext
t.index ["account_key", "content", "title"], name: "index_search_records_9_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_9_on_searchable_type_and_searchable_id", unique: true
end