How to trigger automatic updates for ICustomSearchIndex when the objects it indexes are updated?

Joe Krill asked on November 25, 2014 20:05

I've created a custom search index by implementing ICustomSearchIndex in a custom class. I want this index to be updated whenever the underlying data that it relies on is updated, but I can't figure out any way to do that (that seems correct, at least).

For example, let's say I've created a custom module, my.custommodule, which has a custom class called my.customclass (represented in code by CustomClassInfo). Let's also say I've create a custom search index (because I want to be able to search on some sort of derived or calculated value that isn't directly stored in my class):

public class MyCustomSearchIndex: ICustomSearchIndex 
{
     public void Rebuild(SearchIndexInfo srchInfo)
     {
         // Some code here to create search documents and add them to the index.             
     }
}

Now I need to update this index whenever one of my CustomClassInfo objects is updated. How exactly do I do this? I've tried something like this:

public class MyCustomModule: Module
{
    protected override void OnInit()
    {
        base.OnInit();
        CustomClassInfo.TYPEINFO.Events.Update.After += OnObjectChange;
        CustomClassInfo.TYPEINFO.Events.Insert.After += OnObjectChange;
        CustomClassInfo.TYPEINFO.Events.Delete.After += OnObjectChange;
    }

    void Update_After(object sender, ObjectEventArgs e)
    {
        CustomClassInfo ccInfo = e.Object as CustomClassInfo;
        if (ccInfo != null)
        {
            SearchTaskInfoProvider.CreateTask(SearchTaskTypeEnum.Rebuild, "my.customclass", "CustomClassID", ccInfo.CustomClassID.ToString(), ccInfo.CustomClassID, new bool?());
        }
    }
}

The tasks are indeed created and run, but they don't actually rebuild any of my indexes or even update them. Which really makes total sense, because how would the system even know that MyCustomSearchIndex indexes my.customclass objects?

As an alternative, I tried creating my own SearchIndexer class, which I registered in my module's OnInit:

SearchIndexers.RegisterIndexer<MyCustomSearchIndexer>("my.customclass");

But after a rebuild that doesn't show up as an available "Index type" when creating a new index. (And, indeed, it wouldn't, because after looking into the IndexTypeSelector control, all those values are pretty much hard coded!). What's going on here? Shouldn't I be able to register an indexer and have it show up here? I mean, sure, I can add it myself manually to the IndexTypeSelector control, but shouldn't that be populated automatically based on the registered indexers? And even if I did change it manually, what happens when an update or hotfix reverts my changes?

Another option is to find all indexes that use my custom class by using a LIKE query, then updating those:

    protected override void OnInit()
    {
        base.OnInit();
        CustomClassInfo.TYPEINFO.Events.Delete.After += OnCustomClassInfoChanged;
        CustomClassInfo.TYPEINFO.Events.Update.After += OnCustomClassInfoChanged;
        CustomClassInfo.TYPEINFO.Events.Insert.After += OnCustomClassInfoChanged;
    }


    void CustomClassInfo(object sender, ObjectEventArgs e)
    {
        CustomClassInfo ccInfo = e.Object as CustomClassInfo;
        if (ccInfo != null)
        {
            var indexes = SearchIndexInfoProvider.GetSearchIndexes("IndexSettings LIKE '%ClassName=\"My.Assembly.MyCustomSearchIndexer\"%'", null);
            foreach (var index in indexes)
            {
                SearchTaskInfoProvider.CreateTask(SearchTaskTypeEnum.Rebuild, index.IndexType, null, index.IndexName, 0, true);
            }
        }
    }

This actually works, but it seems like a very roundabout way of having to do this. Not only that, it requires a complete rebuild of the index every single time an object is updated (which, I guess, is an inherent limitation of the fact that ICustomSearchIndex only exposes a RebuildIndex method).

Is this the correct way of handling it? Am I missing something here? Is there a better way to do this that's just completely gone over my head? I'd rather not have to rebuild the index each time. Is there a different way to implement a custom SearchIndex-derived class such that it can be selected from the UI?

Recent Answers


Brenden Kehren answered on November 26, 2014 00:12

Why don't you create a custom scheduled task to run at a given interval to run the re-index vs. attempting to find out when to update it? If it runs and doesn't need to reindex, it will only be a few seconds it runs. VS trying to execute the index every time the object is updated. That could cause a pretty hefty strain on your site.

1 votesVote for this answer Mark as a Correct answer

Reza Zareian Fard answered on November 26, 2014 04:26

I Agree with @Brenden Kehren, instead of this, just create a custom schedule task then manage the interval based on your changes interval.

0 votesVote for this answer Mark as a Correct answer

Joe Krill answered on November 26, 2014 14:20

I guess that's an option. But depending on the interval there will be periods where the index will be out-of-sync with the underlying data. I was hoping to prevent that kind of thing as much as possible.

Being able to implement my own complete SearchIndexer would really be the best option, I think. If the IndexTypeSelector control were updated to show custom registered indexers in addition to the defaults, that would really solve everything, because then I could simply update/delete/insert specific documents to the index instead of only having the option to do complete rebuilds. I'll shoot an email to support about that and see if that's something they'd consider adding as a feature.

0 votesVote for this answer Mark as a Correct answer

Brenden Kehren answered on November 27, 2014 03:23

Yes what you explain would be ideal in a perfect world but how large do you think your index will be? How many records? Size?

Updating an index takes time, the larger your index is, the longer it takes. What about indexing errors? If someone is deleting a record and another is updating or inserting, you'll get no benefit attempting to reindex every time those CRUD actions take place because the index will already be running. So why not schedule your re-index to run every 30 minutes or whatever frequency you find is needed. Less stress on the system, doesn't add additional overhead to the CRUD actions and you're not relying on data entry to have a current index. Plus it takes less than 30 minutes to code and test.

0 votesVote for this answer Mark as a Correct answer

Joe Krill answered on December 5, 2014 18:50

At this point it's ~2000 records. But that's just in this particular case.

And what you're saying -- that updating an index can take a long time -- is exactly what I'm trying to avoid. I want to be able to insert/update/delete individual documents within the index instead of having to reindex/rebuild the entire thing. I could then simply optimize on a scheduled basis, but my index would always be up-to-date otherwise.

0 votesVote for this answer Mark as a Correct answer

Roman Koníček answered on January 13, 2015 15:17

Hi Joe,

Custom search index is used for indexing the data which are not Kentico info objects. To index info objects you could use general index, in which you can select the given type. Then the index will be automatically updated on update, insert and delete actions. What exactly you want to index you could select directly in the UI in Search fields section of the general index.

As for the part where you created you own search indexer class, you mentioned "after a rebuild that doesn't show up as an available "Index type" when creating a new index.". You should create a new general index with the correct type. General indexer is used when there isn‘t any specific indexer for the given object type. Also please note that your indexer should inherit from SearchIndexer class. As you could see here:

var indexer = mIndexers[objectType] ?? DefaultIndexer; -> where the default is the class SearchIndexer, and this default value is used in general index where there is no custom indexer specified.

Regards, Roman Konicek

0 votesVote for this answer Mark as a Correct answer

   Please, sign in to be able to submit a new answer.