Is your feature request related to a problem? Please describe.
We use Apartment (unfortunately), and we are currently using the workaround in the docs where we have one index per model per tenant. This really doesn't scale well at all, with N tenants in the hundreds and M models in the dozens, we end up with N * M models, and this becomes very heavy for elasticsearch.
We have tried implementing this using the current docs for search_document_id, but we run into problems around reindexing cross-tenant, and records that exist in another tenant being cleared out. There's no good way for us to reindex one account, switch schema, then reindex the next account, and keep the documents in elasticsearch.
To do that, we currently need to hook into Searchkick internals, and then:
- find or create a new index
- index for the current tenant
- switch tenant
- resume indexing on the next tenant on the same new index (can be done with resume = true in Searchkick internals)
But Searchkick seems to assume numeric sequential IDs when resuming indexes
def resume_relation(relation)
if relation.respond_to?(:primary_key)
# use total docs instead of max id since there's not a great way
# to get the max _id without scripting since it's a string
where = relation.arel_table[relation.primary_key].gt(index.total_docs)
relation = relation.where(where)
else
raise Error, "Resume not supported for Mongoid"
end
end
Since we have switched tenant, we now have a new relation on a different schema, and we're also not using numeric IDs but UUIDs, which aren't sequential anyways and cant be queries with .gt(..). Therefore we had to monkey patch this to just return the fresh new relation on the schema:
# Normally, when Searchkick tries to resume and index,
# it will look for ids greater than the last id it has in
# the index. This doesnt work for uuids, and it doesnt work
# in our multi-tenant setup since each tenant has its own
# set of ids. So we just disable this behavior
# and use resume: true to continue indexing from another tenant
Searchkick::RelationIndexer.define_method(:resume_relation) do |relation|
relation
end
Next, we found that we had to manually parse the ID back from GlobalID in the response unless we use load: false:
# This overrides Searchkick's hit parsing to convert GlobalID back to model IDs
# to allow us to store unique IDs across tenants in ElasticSearch
Searchkick::Results.define_method(:hits) do
if error
raise Error, "Query error - use the error method to view it"
else
@response["hits"]["hits"].map do |hit|
if parsed = GlobalID.parse(hit["_id"])
if parsed.model_id =~ /^\d+$/
hit["_id"] = parsed.model_id.to_i
else
hit["_id"] = parsed.model_id
end
end
hit
end
end
end
Describe the solution you'd like
What we would like, would be to use GlobalID (or another custom identifier) as the identifier for each record in the index, and instead use a single index per model, tagged with the tenant in search_data. We can achieve most of that with Searchkick today, but what's missing is:
- Indexing without resetting the index when switching tenant
- Loading records in a custom way, e.g using a different column than the primary key, or using GlobalID
For example, a custom load method for loading a list of IDs:
def self.searchkick_load(relation, ids)
relation.where(id: ids.map { |global_id| GlobalID.parse(global_id).model_id })
end
A customizable method for retrieving records for reindexing that allows custom batching and cross-tenant, e.g:
def self.searchkick_reindex_relation
Apartment::Tenant.each do |tenant|
self.by_tenant(tenant).in_batches do |batch_relation|
yield batch_relation
end
end
end
A customizable method for checking if IDs exist in the database before deletion:
def self.searchkick_exists?(ids)
existing = GlobalID::Locator.locate_many(ids).map(&:to_gid_param)
ids.map { |id| id.in?(existing) }
end
Is your feature request related to a problem? Please describe.
We use Apartment (unfortunately), and we are currently using the workaround in the docs where we have one index per model per tenant. This really doesn't scale well at all, with N tenants in the hundreds and M models in the dozens, we end up with N * M models, and this becomes very heavy for elasticsearch.
We have tried implementing this using the current docs for
search_document_id, but we run into problems around reindexing cross-tenant, and records that exist in another tenant being cleared out. There's no good way for us to reindex one account, switch schema, then reindex the next account, and keep the documents in elasticsearch.To do that, we currently need to hook into Searchkick internals, and then:
But Searchkick seems to assume numeric sequential IDs when resuming indexes
Since we have switched tenant, we now have a new relation on a different schema, and we're also not using numeric IDs but UUIDs, which aren't sequential anyways and cant be queries with
.gt(..). Therefore we had to monkey patch this to just return the fresh new relation on the schema:Next, we found that we had to manually parse the ID back from GlobalID in the response unless we use
load: false:Describe the solution you'd like
What we would like, would be to use GlobalID (or another custom identifier) as the identifier for each record in the index, and instead use a single index per model, tagged with the tenant in search_data. We can achieve most of that with Searchkick today, but what's missing is:
For example, a custom load method for loading a list of IDs:
A customizable method for retrieving records for reindexing that allows custom batching and cross-tenant, e.g:
A customizable method for checking if IDs exist in the database before deletion: