Share via


Monitoring an SSA's index

Within an SSA, the search index is divided into discrete portions called Partitions. However, a Partition is a logical division to which each Index Component belongs as a replica; ULS logs and PowerShell output generally refer to Fragments to which documents and queries are routed. Likewise, the topology component names such as IndexComponent1 are not commonly used to refer to the replicas, which log as, for example, NodeRunnerIndex1 and are referred to in various operations as Cells with their own unique identifiers. The terms Partition & Fragment are interchangeable because while Fragments refer to physical divisions of documents in the search index, and Partitions refer to logical divisions of Index Components that contain the search index, their identifiers are the same integer values. As replicas have no particular identifiers associated with them, Cells are referred to on their own in ULS logs and PowerShell output, sometimes replaced entirely by a Cell's name, such as I.1.0, for Index Cell 1 of Partition 0.

The Search Administration Component is responsible for managing both the overall topology of the search system and the search index states. For the most part, the topology only helps identify what SharePoint server hosts which component, but not the index states (such as what Cells are primary for their Partition, and whether all Cells in a Partition are in-sync with one another). To retrieve that sort of diagnostic information for monitoring purposes, Get-SPEnterpriseSearchStatus can be used. This CmdLet can provide a broad SSA-level report, or component-level reports for each component that supports that type of health reporting. To report on the search index as a whole, the SSA-level report and current topology can be used to determine where to retrieve the component-level reports from. The script below combines these reports into a single summary that can be used to check the overall status of an SSA's search index and interpret ULS log events relating to that search index.

$ssa = Get-SPEnterpriseSearchServiceApplication
$ssaStatus = Get-SPEnterpriseSearchStatus -SearchApplication $ssa
$constellation = $ssa.SystemManagerLocations.Segments[1].replace("/","")
$primarySearchAdmin = $ssa.SystemManagerLocations.Segments[2].replace("/","")
$adminStatus = Get-SPEnterpriseSearchStatus -SearchApplication $ssa -Component $primarySearchAdmin -HealthReport
$indexSystem = $adminStatus | ? {$_.name.startswith("active_index")} | % {$_.name -replace '.*\[(.*)\]','$1'}
$indexers = $ssa.ActiveTopology.GetComponents() | ? {$_ -is [Microsoft.Office.Server.Search.Administration.Topology.IndexComponent]}
$statusUnknown = $ssaStatus | ? {$_.name -in $indexers.name -and $_.state -eq "Unknown"} 
$indexerStatus = $ssaStatus | ? {$_.name -in $indexers.name -and $_.state -ne "Unknown"}
$statusDetails = @()
$indexerStatus | % {
  $health = Get-SPEnterpriseSearchStatus -HealthReport -Component $_.name -SearchApplication $ssa
  $out = New-Object PSObject
  foreach ($detail in $_.details) {
    switch ($detail.key) {
      "Primary"   {$out | Add-Member $detail.key ($detail.value -match 'true')}
      "Partition" {$out | Add-Member $detail.key ([int]$detail.value)}
      default     {$out | Add-Member $detail.key $detail.value}
    }
  }
  $out | Add-Member Name $_.name.replace("IndexComponent","NodeRunnerIndex")
  $out | Add-Member State $_.state
  $merge = ($health | ? {$_.name.startswith("plugin: master merge running")})
  $out | Add-Member Merging ($merge.message -match 'true')
  $out | Add-Member Cell ([int]($merge.name -split '\.')[2])
  $out | Add-Member CellName ("[I.{0}.{1}]" -f $out.Cell,$out.Partition)
  $out | Add-Member TotalDocs ($health | ? {$_.name.startswith("part: number of documents including duplicate")} | % {[int]$_.message} | measure-object -sum).sum
  $out | Add-Member ActiveDocs ($health | ? {$_.name.startswith("plugin: number of documents")}).message
  $out | Add-Member Generation ($health | ? {$_.name.startswith("plugin: newest generation id")}).message
  $out | Add-Member CheckpointSize ($health | ? {$_.name.startswith("plugin: size of newest checkpoint")}).message
  $statusDetails += $out
}
 
"`nHome directory for {0}'s Constellation:`n{1}\Search\Nodes\{2}" -f $ssa.Name,$ssa.AdminComponent.IndexLocation,$constellation
"`nPrimary Search Admin for Constellation {0}: {1} on {2}" -f $constellation,$primarySearchAdmin.replace("AdminComponent","NodeRunnerAdmin"),$ssa.SystemManagerLocations.Host
"`nHealth Reports for Index System {0} of Constellation {1}:" -f $indexSystem,$constellation
 
$adminStatus | sort name | ft -auto Name,Message,Level
$statusDetails | sort Partition,Cell | ft -auto Host,Name,State,Primary,Partition,CellName,ActiveDocs,TotalDocs,Merging,Generation,CheckpointSize
$statusUnknown | % {"{0} of Partition {1} cannot be contacted on server {2}" -f $_.name,$_.details["Partition"],$_.details["Host"]}
$indexers | select -expand RootDirectory -unique | ? {$_ -ne ""} | % {"`nCustom index directory found: {0}" -f $_}