When a Windows account tries to access a secured Windows resource, an access token is constructed. The access token is used to determine whether access should be granted and how much access should be granted. Tokens are built by the server that hosts the resource. The server queries the appropriate domain controllers to obtain the token information.
The access token consists of several pieces of information, most notably the security identifiers (SIDs) for the user account and for the security groups to which the user account belongs. After a user authenticates to a server, the appropriate SIDs that are associated with the user and the user's group memberships are put in an access token. A SID is a string of numbers that uniquely identifies a Windows security principal or security group. For more information, view the "Security Identifiers Technical Reference" document. To view this document, visit the following Microsoft Web site:
SIDs are no more secret than logon names. SIDs are unique numeric identifiers that are associated with object names. The SID stays the same for the lifetime of an Active Directory object. Therefore, the SID can be used to conclusively identify an object regardless of whether other object attributes change.
Each secured resource on a server has a discretionary access control list (DACL) associated with it. The DACL lists the SIDs that are allowed or denied access to the resource.
When a user tries to access a secured resource, the list of SIDs in the user's access token is compared to the list of SIDs in the DACL of the resource. If a SID in the token matches a SID in the DACL of the resource, appropriate access is granted. You cannot reliably determine the number of security groups to which a user account belongs by counting the number of groups that are listed on the Member Of
property of the user object. This is because of the following four factors:
- Group nesting
In native mode Microsoft Windows 2000 domains or Windows Server 2003 functional mode domains, groups can be more flexibly nested than in mixed-mode domains. When a group is added to a user's token, the SIDs of nested groups are also added.
- Universal group membership
If the user account domain is in mixed mode, universal groups will not be added to the access token. As soon as the domain to which the account belongs is converted to native mode or one of the Windows Server 2003 functional modes, universal group memberships will be added to the token.
Accounts that are migrated from Microsoft Windows NT 4.0 domains or other Active Directory domains may have many group memberships in their SIDHistory attributes. For more information about SIDHistory, visit the following Microsoft Web site:SIDHistory is available only for user account domains that are already in Windows 2000 native mode or in Windows Server 2003 functional modes. If the user account domain is in mixed mode, groups from SIDHistory will be ignored. In practice, these groups should not exist.
- Domain local groups
If a secured resource is hosted in a Windows 2000 native mode or Windows Server 2003 functional mode domain, domain local groups in the resource domain to which the user account belongs will be added to the token. For example, suppose that a user in Domain A tries to access a resource in Domain B. In Windows 2000 native mode or in Windows Server 2003 functional modes, all domain local groups in Domain B to which the user belongs will be added to the access token. Domain local groups in Domain A to which the user belongs will not be added to the token that is generated by a server in Domain B. This is because domain local groups from Domain A are irrelevant to Domain B.
A user's access token is stored on the server in paged pool kernel memory. At any time, there are likely to be multiple copies of each user's token in memory. For example, if a client maps a share on a Windows Server 2003-based server by using the NET USE
command, two copies of the user's token will be held on the server to support this connection.
Each client application that connects to an Exchange server is likely to generate multiple copies of the user token, depending on the application and its configuration.
There is a finite amount of paged pool memory available. Therefore, there is a limit to the number of client connections that a server can maintain at the same time. On a Windows-based server that has more than 1 gigabyte (GB) of physical memory installed, the maximum paged pool memory is about 350 megabytes (MB). This amount may be reduced by memory tuning in favor of other resources that may be in shorter supply.
Memory tuning recommendations for a large-scale Exchange server include the use of the /3GB
boot.ini switch. This reduces the maximum paged pool memory to less than 250 MB. In this context, a large-scale Exchange server is one that hosts thousands of mailboxes and that has more than 1 GB of RAM installed.
If you do not use the /3GB
switch, it is likely that Exchange Server services will have to be restarted periodically to defragment virtual memory. Trading off paged pool kernel memory for additional application memory is a worthwhile tradeoff. However, this tradeoff means that you must monitor the use of paged pool memory more closely. For more information about memory tuning for Exchange Server, click the following article number to view the article in the Microsoft Knowledge Base:
How to optimize memory usage in Exchange Server 2003
Additionally, view the "Ruling Out Memory-Bound Problems" section of the "Troubleshooting Exchange Server 2003 Performance" white paper. To view this white paper, visit the following Microsoft Web site:
Client tokens are usually the biggest single consumer of paged pool memory on an Exchange server. If the average user token is large, paged pool memory consumption is likely to become an important bottleneck for Exchange Server scalability.
How to calculate token size
Access token size in bytes can be estimated by using the following formula:
[12 x number of user rights] + [token overhead] + [44 x number of group memberships] = token size in bytes
- User rights include rights such as "Log on locally" or "Access this computer from the network." The only user rights that are added to an access token are those user rights that are configured on the server that hosts a secured resource. Most Exchange Server users are likely to have only two or three user rights on the Exchange server. Administrators may have dozens of user rights. Each user right requires 12 bytes to store it in the token.
- Token overhead includes multiple fields such as the token source, expiration time, and impersonation information. For a typical domain user who has no special access or restrictions, token overhead is likely to be between 400 and 500 bytes. Typically, estimating 500 bytes for both user rights and token overhead is more than sufficient.
- Each group membership adds the group SID to the token together with an additional 16 bytes for associated attributes and information. The maximum possible size for a SID is 68 bytes. However, it is rare for a SID to be this large. In Windows Server 2003 and in earlier versions of Windows, the typical SID for a user or a group is 28 bytes in length. Therefore, each security group to which a user belongs typically adds 44 bytes to the user's token size.
Token memory allocation
If a token is less than 4 kilobytes (KB), the amount of kernel memory that is allocated for it is exactly what is required to hold the token. For example, consider a typical user who belongs to 30 security groups. By using the formula that is mentioned in the "How to calculate token size" section, this user's token will be about 1,820 bytes (44 bytes x 30 groups + 500 overhead bytes = 1,820).
But if a token is even slightly larger than 4 KB (4,096 bytes), the amount of memory that is allocated per copy will jump to exactly 8 KB (8,192 bytes). If a token is even slightly larger than 8 KB, the memory allocation will jump to exactly 12 KB. Therefore, every time that the token sizes crosses one of these critical 4-KB boundaries, there is a sudden jump in the use of paged pool memory.
Generally, a user who belongs to more than 80 security groups will be near or will go over the 4-KB boundary. Therefore, the user will require an 8-KB token. If a user belongs to more than 170 groups, the token is likely to require 12 KB, and so on.
The following example illustrates how important it is to monitor and control average client token size. Consider an Exchange 2003 Service Pack 2 server on which all clients use Microsoft Office Outlook 2003 in cached mode. A typical cached-mode client causes seven or eight copies of its token to be generated on a Windows Server 2003-based computer. If the average client token is exactly 4 KB, each cached-mode client requires up to 32 KB of paged pool memory.Note
The Microsoft Exchange Information Store service hotfix that is described in the "Introduction" section can reduce the number of token copies for each cached-mode user to four or five instead of seven or eight. This hotfix is scheduled to be included in Microsoft Exchange Server 2003 Service Pack 3.
If the server is configured by using the /3GB
switch, there will be about 250 MB of paged pool memory allocated on the server. We recommend that the typical paged pool consumption for the server should be no more than 200 MB. You must reserve sufficient memory for spikes in the server load. If paged pool memory consumption is typically more than 220 MB, you should take immediate steps to reduce the load on the server.
Assume that 150 MB of paged pool memory is available for Exchange Server client tokens. If each client token is 4 KB, the server can comfortably support more than 4,500 concurrent Outlook cached-mode users before token use will become a bottleneck. Note that applying hotfix 912480 would increase this maximum to 7,300 cached-mode users. If token size were to jump to 8 KB, the maximum number of clients would be reduced by half, regardless of whether hotfix 912480 was applied.Note
If Outlook 2003 is run in online mode, there will typically be three or four token copies for each client regardless of whether hotfix 912480 was applied.
Symptoms of kernel memory depletion
If kernel memory resources are close to being exhausted, the server becomes slow or refuses additional requests and connections. Applications may fail suddenly. Additionally, attempts to connect to the affected server may return error 1450, "Insufficient System Resources." In extreme cases, the server may display an error message on a blue screen and stop responding.
Additionally, the following events may be logged in the System log:
Event ID: 2019
Description: The server was unable to allocate from the system nonpaged pool because the pool was empty.
Event ID: 2020
Description: The server was unable to allocate from the system paged pool because the pool was empty.
Event ID: 2000
Description: The server's call to a system service failed unexpectedly.
If a paged pool memory shortage is transient, the server will likely recover. Applications can be somewhat resilient to temporary shortages of memory. However, no application can run forever if critical resource requests are not satisfied. If the paged pool memory shortage lasts very long, it is likely to trigger cascading bottlenecks. In such a case, the server will probably have to be restarted to make it functional again.
Under standard load, there should be approximately 50 MB of available paged pool memory. If you have less than 30 megabytes free, you should take immediate steps to reduce the load on the server.
Paged pool memory is allocated statically during Windows startup. The pool cannot be increased without reconfiguring and restarting the server. The amount of available paged pool memory depends on several factors. These factors include boot switches such as /USERVA
, registry settings, and physical RAM.
How to reduce the size of user access tokens
You can use the following three strategies to reduce token size:
- Reduce the number of security groups to which each user belongs.
- Host Exchange servers in a different domain from the users who connect to the Exchange servers.
This strategy can reduce the size of user tokens by stripping domain local groups for the user account domain from the token that is presented to the Exchange server. This works because domain local groups from one domain are not kept in the token that is generated on a server in a different domain.
- When you can, convert security groups to distribution groups.
Token size is increased by membership in security groups, not distribution groups. Users can belong to thousands of distribution groups that do not affect token size. If a group is not being used to deny or grant access to resources, it should be a distribution group, not a security group.
How to reduce the number of access tokens in memory on the server
As soon as you have reduced the typical token size to the practical minimum, the next step is to manage the number of simultaneous connections that are made to the server. You can manage the number of simultaneous connections by using the following methods:
- Restrict unauthorized clients and applications.
Each client may make multiple connections to the server. Additionally, different clients make different numbers of connections based on a wide variety of factors. You may not even have a full list of all the clients that connect to the server. Users may install Outlook add-ins that make additional connections. Developers may run applications that make many connections or that do not shut down connections when they are finished. Therefore, you should analyze what kinds of clients connect to the server and what kinds of effects they have on kernel memory usage. For more information, see the "How to view token allocation sizes" section.
- Remove the public folder store from the server. Then, direct clients to public folders on a different server.
This action eliminates public folder connections that are made by clients.
- Remove specific public folders that account for many client connections.
Good candidates for removal are the Schedule+ Free/Busy folder and the offline address book. Clients must make additional connections to these folders when they schedule appointments or download the address book.
- Add replicas of heavily accessed public folders to distribute the number of clients who connect to them across multiple servers.
- Install dedicated public folder servers to eliminate all public folder connections from mailbox servers.
- Distribute heavy-connection users evenly across multiple servers. Heavy-connection users are likely to be those who have multiple computers or devices and those who are mobile users.
- Distribute users who have large security tokens across multiple servers.
- Apply hotfix 912480 for token optimization. For more information about hotfix 912480, click the following article number to view the article in the Microsoft Knowledge Base:
An Exchange Server 2003 server that hosts many Outlook client sessions may run out of paged pool memory
How to monitor paged pool memory on an Exchange server
Generally, you should have 50 MB of free paged pool memory available under typical server load conditions. Additionally, you should have 30 MB free under peak loads.
It is easy to determine how much paged pool memory is currently being used. Windows Task Manager displays paged pool usage in the Kernel Memory
area on the Performance
tab. You can also monitor the use of paged pool memory over time with the Memory\Pool Paged Bytes counter in Windows System Monitor.
An Exchange server that is configured to use the /3GB
boot switch will have a maximum possible paged pool memory size of about 250 MB. Additionally, this server will have a non-paged-pool-memory maximum of 128 MB. Without the /3GB
switch, the maximums are 350 MB for paged pool memory and 256 MB for non-paged pool memory.
Therefore, a typical large-scale Exchange server should use no more than 200 MB of paged pool memory under typical conditions. Paged pool memory use of more than 220 MB requires immediate attention.
If you are within these limits, and the server is reporting errors that are related to paged pool memory depletion, it is likely that the initial paged pool memory allocation is less than expected. This can be caused by hardware demands, by device drivers, or by memory tuning that reduces initial paged pool memory allocation even more. Large memory configurations, for example, more than 4 GB of physical RAM, are the most common cause of this problem.
Each byte of physical RAM that is installed in a server requires some kernel memory to address and manage it. The more RAM that is installed, the more kernel address space must be reserved for it. Address space may be borrowed from paged pool memory to satisfy this demand.
We recommend that you do not install more than 4 GB of physical RAM in a server that is dedicated to running Exchange Server 2003. Exchange Server will make efficient use of up to 4 GB of RAM. However, Exchange Server will not take advantage of additional RAM even if it is available. Servers that support the Hot Add Memory feature can also cause significant reductions in the availability of paged pool memory. Even if no more than 4 GB of RAM is installed, address space may be reserved for the theoretical maximum amount of hot-add RAM that could be installed.
You can use a kernel debugger to view the size of initial paged pool memory and other kernel memory allocations.Important
Commands that can be used during a kernel debugging session can cause the system to become unstable or stop. We recommend that you stop all Exchange Server services before you initiate a kernel debugging session and that you restart the server after the session.
Setting up a traditional kernel debugging session for Windows 2000 can be a complex task. This task typically requires an extra computer, specialized cabling, and a server restart.
Alternatively, the LiveKD utility from Sysinternals can be used to start a kernel debugging session from the server console. LiveKD does not require that you restart the server. For more information, click the following article number to view the article in the Microsoft Knowledge Base:
The Performance tool does not accurately show the available Free System Page Table entries in Windows Server 2003
For Windows Server 2003, the KD kernel debugger supports debugging directly from the server console without special preparation or hardware. To obtain the Debugging Tools for Windows, visit the following Microsoft Web site:
Start the debugger by using the KD.EXE -KL
command. Then, run the !vm
command to view the maximum paged pool memory. For example, run the following commands:
How to view token allocation sizes
Outlook is not the only client that can connect to an Exchange Server database. Outlook add-ins, desktop search engines that include mail search functionality, instant messaging clients, and custom applications can all make additional connections and cause the generation of additional token copies.
You can verify the effect of a client or application by using the Poolmon.exe utility in a laboratory environment. To do this, follow these steps:
- Generate an isolated laboratory Exchange organization.
- Install Poolmon on the Exchange server. For more information about how to configure Poolmon.exe, click the following article number to view the article in the Microsoft Knowledge Base:
How to use Memory Pool Monitor (Poolmon.exe) to troubleshoot kernel memory leaks
- Run Poolmon.exe with the /iToke switch (Poolmon /iToke). Note that the /iToke parameter is case sensitive. This will configure Poolmon.exe to display only token allocations. You can also use this command on a production server to view total token allocations in real time.
- Configure an Active Directory user account that is similar to the typical Exchange Server user in your environment. That is, configure a user account that has an equivalent number of security group memberships, a similar permissions profile, and so on.
- Log on to Exchange Server as the test user who has the client applications and configurations that you want to test. Wait several minutes after you log on for the client application to completely load and stabilize.
- Exit the client application when you notice the change in the token bytes in Poolmon.exe. You may have to do this several times to obtain an accurate reading of how many bytes are freed when you exit the client. Other token allocations may be created or destroyed at the same time during the test.
If you change the user account, such as by adding or deleting security group memberships, you must log off the account from Windows and then log back on before these changes will be reflected in the access token.
How to audit group memberships
The following script examples contain command-line parameters and instructions at the top of each script. You can paste the scripts into Notepad and then save them as .vbs files. Do not save the files as .txt files.
- The Groups.vbs script prints Exchange Server mailbox user account names and the security groups to which they belong. Additionally, the printout contains a separate column that lists groups from SIDHistory. You can restrict the script to a single Exchange server or obtain a report for multiple Exchange servers by using a wildcard character.
Note You cannot use a wildcard character (*) to access all Exchange servers. You must provide at least a partial server name. For example, you can use a string that is similar the following:
- The Groups_statistics.vbs script provides a text-based histogram view that shows you how many users belong to 50 groups, 60 groups, 70 groups, and so on. This can help you determine the likely average token size for users.
See the "How to calculate token size" and "Token memory allocation" sections for detailed information about token sizes.
'==============================================================================' NAME: Groups.vbs' AUTHOR: Kyryl Perederiy, Microsoft IT, MACS Engineering' DATE : 12/15/2005' COMMENT: The script runs through all mailbox enabled user objects in the ' forest and calculates the number of security groups and groups in SID ' history for each object. User objects can be filtered by Exchange home server.' PARAMETERS: <output file> <GC Domain Controller> <Domain Naming Context> [<Exchange Server(s)>]' EXAMPLE: CSCRIPT groups.vbs groups.tsv EXCH-DC-01 dc=root,dc=company,dc=com EXCH-MBX-*' Version 1.0'==========================================================================On Error Resume NextSet strArgs = WScript.ArgumentsSet fso = CreateObject("Scripting.FileSystemObject")Set fileStream = fso.OpenTextFile(strArgs(0), 2, True, TristateTrue)fileStream.WriteLine "DN Mail Domain Login Server GRP SIDHISTORY"Count=0DCS = strArgs(1) ' Domain ControllerstrDomainNC = strArgs(2) ' Domain Naming Context for the foreststrFilter = "(&(mail=*)(objectCategory=person)(objectClass=user)" &_ "(msExchHomeServerName=*" & strArgs(3) & "))" 'Mail users search filterSet oConnection = CreateObject("ADODB.Connection") ' Setup the ADO connectionSet Com = CreateObject("ADODB.Command")oConnection.Provider = "ADsDSOObject"oConnection.Open "ADs Provider"Set Com.ActiveConnection = oConnection ' Create a command object on this connectionCom.CommandText = "<LDAP://" & DCS & ":3268/" & strDomainNC & ">;" &_ strFilter & ";distinguishedName,mail,sAMAccountName," &_ "msExchHomeServerName,SIDHistory,homeMDB;subtree"' Set search preferencesCom.Properties("Page Size") = 1000Com.Properties("Asynchronous") = TrueCom.Properties("Timeout") = 120 ' secondsset oRecordSet = Com.ExecuteoRecordSet.MoveFirstWhile Not oRecordset.Eof Count=Count+1 DN = oRecordset.Fields("distinguishedName").Value Mail = oRecordset.Fields("mail").Value Server = oRecordset.Fields("msExchHomeServerName").Value Server = Mid(Server,InStrRev(Server,"=")+1) Domain = Split(DN,",DC=") Login = UCase(Domain(1)) & "\" & oRecordset.Fields("sAMAccountName").Value set oDirObject = GetObject("LDAP://" & DCS & "/" & replace(DN,"/","\/")) ' tokenGroups is a computed attribute that contains the list of SIDs ' due to a transitive group membership expansion operation on a given user oDirObject.GetInfoEx ARRAY("tokengroups"),0 ' Size of the array correspond to the number of groups GROUPS = ubound(oDirObject.GetEx("tokengroups"))+1 If IsNull(oRecordSet.Fields("SIDHistory").Value ) Then SIDHIST = "0" Else SIDHIST = ubound(oDirObject.GetEx("sidhistory")) End If WScript.Echo Count & CHR(9) & DN & CHR(9) & GROUPS fileStream.WriteLine _ DN & CHR(9) &_ Mail & CHR(9) &_ UCase(Domain(1)) & CHR(9) &_ Login & CHR(9) &_ Server & CHR(9) &_ GROUPS & CHR(9) &_ SIDHIST & CHR(9) oRecordset.MoveNextWendWScript.Echo "Total: " & Count & " users found on the server(s): " & strArgs(3)
'==========================================================================' NAME: groups_statistics.vbs' AUTHOR: Kyryl Perederiy, Microsoft IT, MACS Engineering' DATE : 12/15/2005' COMMENT: The script runs through all mailbox enabled user objects in the ' forest and calculates statistical distribution for group membership.' PARAMETERS: <output file> <GC Domain Controller> <Domain Naming Context> [<ExchHomeServerName>]' EXAMPLE: CSCRIPT groups_statistics.vbs groups_statistics.tsv EXCH-DC-01 dc=root,dc=company,dc=com EXCH-MBX-0*' Version 1.0'==========================================================================On Error Resume NextDim GROUPS(100)Set strArgs = WScript.ArgumentsSet fso = CreateObject("Scripting.FileSystemObject")Set fileStream = fso.OpenTextFile(strArgs(0), 2, True, TristateTrue)fileStream.WriteLine "Groups" & CHR(9) & "Users"Count=0DCS = strArgs(1) ' Domain ControllerstrDomainNC = strArgs(2) ' Domain Naming Context for the foreststrFilter = "(&(mail=*)(objectCategory=person)(objectClass=user)" &_ "(msExchHomeServerName=*" & strArgs(3) & "))" 'Mail users search filterSet oConnection = CreateObject("ADODB.Connection") ' Setup the ADO connectionSet Com = CreateObject("ADODB.Command")oConnection.Provider = "ADsDSOObject"oConnection.Open "ADs Provider"Set Com.ActiveConnection = oConnection ' Create a command object on this connectionCom.CommandText = "<LDAP://" & DCS & ":3268/" & strDomainNC & ">;" &_ strFilter & ";distinguishedName,sAMAccountName;subtree"' Set search preferences.Com.Properties("Page Size") = 1000Com.Properties("Asynchronous") = TrueCom.Properties("Timeout") = 120 'secondsset oRecordSet = Com.ExecuteoRecordSet.MoveFirstWhile Not oRecordset.Eof Count=Count+1 set oDirObject = GetObject("LDAP://" & strArgs(1) & "/" &_ replace(oRecordset.Fields("distinguishedName").Value,"/","\/")) oDirObject.GetInfoEx ARRAY("tokengroups"),0 GRP = ubound(oDirObject.GetEx("tokengroups"))+1 GROUPS(Int(GRP/10)) = GROUPS(Int(GRP/10)) + 1 WScript.Echo Count & CHR(9) & oRecordset.Fields("sAMAccountName").Value & CHR(9) & GRP oRecordset.MoveNextWendWScript.Echo "Total: " & Count & " users found"WScript.Echo "See " & strArgs(0) & " for details..."For i=0 to 100 fileStream.WriteLine i*10 & CHR(9) & GROUPS(i)Next
The third-party products that this article discusses are manufactured by companies that are independent of Microsoft. Microsoft makes no warranty, implied or otherwise, regarding the performance or reliability of these products.