Monday, October 20, 2008

Hit or MIIS

While working out at a client site on their MIIS implementation, occasionally during a sync on one of their MAs, the Identity Manager Application would become almost completely unresponsive. I couldn't start or stop the running of an MA, execute a Preview, complete an MV Search, perform any manual Joins or Disconnections. It was all very frustrating, I would have to wait a few minutes until MIIS came back from the abyss to continue my work. The only other information that I had was from performing a SQL Profiler Trace. MIIS was definitely working on something, but what and why? <spoiler>The Answer: it has to do with the Joining activity that MIIS does while sync'ing an object.</spoiler> So, here's a little more background...

While configuring a Management Agent, you are given the ability to define rules that MIIS can use to determine if the object that its currently looking at should be linked to user data that it already has from other systems. This can be done by simply going to the Configure Join and Projection Rules in the Metaverse Designer and clicking on the New Join Rule button at the bottom:

In its simplest form, a Join Rule can be designed that directly correlates data in the source with data in MIIS. For example, if the First and Last Name are the same, join the identities together:

On the backend, MIIS is executing a query against the Metaverse table to get all of the object_ids of user's with data matching the join criteria:
select distinct [mms_metaverse].object_id from [mms_metaverse] with (holdlock) where (([<MVAttributeName>] =N'<CSAttributeValue>'))
So if we are sync'ing an individual named Joe Smith, we get a query like (and, yes, there really are both sets of parentheses around each expression):
select distinct [mms_metaverse].object_id from [mms_metaverse] with (holdlock) where (([givenName] =N'Joe')) and (([sn] =N'Smith))
It will then run the following series of queries to build a portfolio about every object_id returned:
exec mms_getmvsvall_xlock @mvobjid = '<object_id>';
exec mms_getmvmulti @mvobjid = '<object_id>';
exec mms_getmvrefasobjid @mvobjid = '<object_id>';
Once MIIS has all of this data, it can join with an existing identity for Joe Smith, if one is found. In this scenario, if more than one Joe Smith is found, MIIS will consider this a Non-match and move onto the next join rule. It will continue to execute the join rules one after the other, in turn, until either a join is made or it reaches the end of the list. If no join is made, MIIS will then move onto projection, if this option has been defined.

Now, In an actual deployment these join scenarios are usually a little more complicated. To resolve a more complicated search/match, we usually have to go out to our rules extension code. There are two places you can inject code during the join procedure. The first of these is done by using the Rules extension mapping type. This will allow you to add some additional logic around the attribute equivalency search. Here is what I mean... you are trying to join based on Social Security Number and the source system has SSNs stored as 123-456-789 but MIIS has it stored as 123456789, while these two are not technically equivalent if doing a direct evaluation, we can add code to ensure that evaluation is done "correctly" via code executed in the MapAttributesForJoin Method.

The second place we can inject code is to pick the proper join out of the X number of candidates found by the Join search. So continuing from our example above, if we have defined a join based on SSN, but our data tends to be pretty dirty and multiple people have the same SSN. We have ensured that our SSN matching is performed properly above in the MapAttributesForJoin by reformatting the data, but the match/search logic finds 5 people that could potentially match my source SSN of 123-456-789. We can add additional code in the ResolveJoinSearch method to determine, based on other data elements, say last name, that number 3 of the 5 people found is the right individual to join with.

So, here is a more real world example. What if we want to join people base on their birth date with a last name or previous last name match? In this case we will have to go out to code to determine whether a join candidate can be found. We would start by creating a join rule based on birth date:

We have decided to use a Mapping Type of Rules extension to allow us to go out to code to reformat the birth date to ensure that it matches the format in MIIS. In our rules extension, MIIS would be looking for an entry point of "cd.person#1:mccdDOB->mccdDOB" in the MapAttributesForJoinMethod:
switch (FlowRuleName)
{

     case "cd.person#1:mccdDOB->mccdDOB":

          if ( csentry["mccdDOB"].IsPresent )
          {

               //reformat the dob with style used in the MV
               values.Add( System.Convert.ToDateTime(csentry["mccdDOB"].StringValue).ToString("yyyy-MM-dd") );

          }

          break;

     default:
          throw new EntryPointNotImplementedException();

}
We have also selected the Use rules extension to resolve option. This will allow us to add code to check current and previous last names against those individuals found with the same birth date. This is done using the following code in the ResolveJoinSearch Method:
//set index of final join candidate to -1 to indicate no join found
imventry = -1;

//create a variable to keep our place in the loop
int curIndex = 0;

switch (joinCriteriaName)
{

     case "cd.student#1":

          //loop through each person to check current/prev last name
          foreach (MVEntry mventry in rgmventry)
          {

               if ( csentry["sn"].IsPresent && mventry["sn"].IsPresent)
               {

                    //try to join on last name
                    if ( csentry["sn"].StringValue == mventry["sn"].StringValue )
                    {

                         //its a match, return the index of this individual
                         imventry = curIndex;

                    }

               }
               else if ( csentry["prevSn"].IsPresent && mventry["sn"].IsPresent)
               {

                    //try to join on previous last name
                    if ( csentry["prevSn"].StringValue == mventry["sn"].StringValue )
                    {

                         //its a match, return the index of this individual
                         imventry = curIndex;

                    }

               }

               //we are moving to the next object, increment the counter
               curIndex++;

          }

          break;

     default:
          throw new EntryPointNotImplementedException();

}

//return index of individual found
return imventry;
So, now that we have all of the basics of joining down, we can get back to the problem. Did you catch it? When MIIS is performing the join search, it executes a set of stored procedures to get all of the data about the matching Metaverse objects using an exclusive lock, locking up our Metaverse table from any other reads or updates until the search is complete. This usually isn't a problem because this process happens so quickly you usually won't notice it. With that said, be very careful not to create inefficient join rules that return more than a few results. If more than a few records need to be returned and evaluated, not only will being to see a degradation in the UI response of Identity Manager, it will also take exponentially longer for the MA to complete.

1 comment:

Joe Stepongzi said...

I would suggest you also index the metaverse attributes in which you are trying to join upon, This will speed up the process quite a bit..

Also make sure you are doign db mait..

Its amazing how much faster your process will be...

Great Article.. :)