This document discusses the implementation of the PowerBuilder® DataStore and its impact on the scalability of applications within Sybase® EAServer. While the information presented below is specifically related to the supported Microsoft® Windows® platforms, portions of the discussion have relevance to the UNIX® platforms supported by the PowerBuilder Virtual Machine (PBVM) in EAServer - namely SolarisTM, HP-UX, and AIX®. The specific scope of the information contained in this paper is PowerBuilder versions 7 and later and EAServer versions 3.0 and higher; however, modifications introduced into PowerBuilder 8.0.3 and continued into 9.0 mitigate many of these limitations.
The PowerBuilder DataStore object was introduced in version 5 of PowerBuilder as a non-visual alternative to the PowerBuilder DataWindow® control. The DataStore implements much of the same functionality as the DataWindow and, like its cousin, depends on the association of the dataobject attribute with a PowerBuilder DataWindow object. Given the similarities of the DataWindow and the DataStore, one might assume - correctly - that the underlying implementations of these objects are similar. Major facets of this implementation that affect scalability, particularly in application server environments, include
- physical and virtual memory
- user object handles
- Graphics Device Interface (GDI) object handles
- kernel objects (and the related resources of thread stack and thread local storage)
- desktop heap
The remainder of this document explains these resources and their limitations, how the DataStore implementation makes use of these resources, and how you can maximize their use within the EAServer environment.
Physical and Virtual Memory
Obviously, any variable instantiated within a component is going to occupy memory. An integer variable occupies 2 bytes, a long variable occupies 4 bytes, and a string variable can use up to 2GB of memory. A DataStore instance is no different, just more complicated because of its internal structure. The internal representation of the DataStore must retain the metadata, formatting rules, and validation criteria, not to mention the actual data retrieved. Steps you can take to make the DataStore's memory footprint as small as possible include:
- Calling the Reset() function whenever you are finished with data in a non-local DataStore, especially when the encompassing component is to be deactivated and pooled. Data stored within a deactivated component merely claims resources that cannot be used until that instance is reactivated, at which point its semantic state would actually be undefined anyway. The process of component deactivation within EAServer will not automatically clear the data stored within a DataStore; this must be done within PowerScript code explicitly.
- Filtering data at the database versus within the DataStore. In general, re-evaluate the need to bring large result sets into server components and forward them to client applications. In most cases, streaming thousands of rows to the client interface results in an amount of data too large for an end-user to process effectively.
While you can use the DataStore functionality to filter the data within the server component before forwarding it to the client, consider using backend processes in the database (e.g., stored procedures) to reduce the amount of data needed in the server component. Alternatively, using a cursor in the PowerBuilder component to filter data row by row (rather than en masse via the DataStore Filter method or other PowerScript code) could substantially reduce memory requirements. Finally, if there is no alternative but to process an extremely large data set, consider setting the datawindow.table.data.storage property to "Disk" to offload the data to disk during processing. Both cursor processing and offloading data to disk can result in a negative impact to performance, so it is incumbent on the component developer to weigh different options to meet the application's memory and performance requirements.
- Eliminating unnecessary objects from the DataWindow design. A DataStore is non-visual; therefore, presentation characteristics are often moot and could be eliminated. If you are using the Get/SetFullState design pattern, consider using a DataWindow object on the client to handle the presentation and sharing that DataWindow with the DataStore object reconstructed from the blob obtained from the server component. From a design and maintainability perspective alone this has merit, because it decouples the business logic from the presentation layer. One notable exception would be a requirement to print reports from an application server. In this case the presentation necessarily must reside on the server; however, the design could still accommodate this requirement by employing a 'presentation DataStore' only when specific printing functionality is executed.
- Using explicit DESTROYs of DataStore objects when they are no longer needed. This would specifically apply to DataStores with local variable scope (because instance variables would generally remain instantiated for the lifetime of the component). Although local variable references will be recycled (via automatic garbage collection) when they lose scope, the garbage collection task runs at a low priority, so explicitly DESTROYing references can prove advantageous. In a similar vein, some component developers have reported further benefit, especially in high load conditions, by explicitly invoking the GarbageCollect() function as part of the component's deactivation code.
There are some common misconceptions that designating the DataWindow object as read-only (by setting the datawindow.readonly property) or making the DataWindow non-updateable via the Rows->Update Properties... dialog in the DataWindow painter will conserve memory. These settings, in and of themselves, have little or no effect on memory allocation; rather, it is the modification of the data itself in the DataWindow that impacts storage requirements.
When the data originally retrieved into a DataStore are changed programmatically, additional memory will be allocated to retain the original data for use in generating update statements. The memory for the 'original buffer' is not allocated all at once, but rather on a row by row basis. When the first value in a given DataStore row is modified, memory is allocated for pointers to data for all columns in that row, and those pointers are initialized to null. At that point, the pointer corresponding to the modified column is also allocated and assigned a memory location to contain the originally retrieved data value for that row and column. When a subsequent column in that same row is modified, the previously null pointer for that column will also be allocated so the original value can be stored for later use. If no values within a particular row are modified, then there is no memory allocation required for that row.
Making a DataWindow object read-only eliminates the possibility of user entry via the keyboard, but it does nothing to prevent modification via script - which is the only way to modify a DataStore within an EAServer component anyway. Similarly, even if a DataWindow object is marked as non-updateable, there is nothing to prevent modification of the data via script. Changes made programmatically to a non-updateable DataWindow object can, of course, not be automatically updated to the target database; however, since the possibility exists that a DataStore be made updateable dynamically (via the DataStore Modify() function, for example), it is clear that the original data must be retained.
To monitor physical and virtual memory usage, use the Performance Monitor utility located in the Administrative Tools group on Windows NT and Windows 2000. Task Manager does not report all of the required memory values and is less useful at spotting patterns of anomalous memory usage. Items to monitor include the Committed Bytes and Commit Limit counters for the Memory object and the Virtual Bytes counter for the jagsrv process. As Committed Bytes approaches the Commit Limit, physical memory and swap file is being exhausted. If the virtual memory configuration allows for the swap file size to increase, the Commit Limit may increase as the operating system deems necessary. Virtual Bytes measures process address space, which represents the amount of memory reserved but not yet used (committed) by the process. This is not the same metric as the VM Size statistic that can be monitored in Task Manager. By default, all processes have a 2GB limit on process address space, but that is configurable to 3GB on some operating systems1.
User Object Handles
User objects are one of three categories of objects handled by the Windows NT® and 2000 operating systems. User objects include items such windows, menus, cursors and icons. The other two categories, GDI and kernel objects, are discussed in subsequent sections.
There is a system wide limit of 65,536 user handles per system. According to Microsoft, there is no per-process limit2; however, testing yielded the following results on Windows NT 4.0 SP6a and Windows 2000 SP2:
|Operating System||Default Limit||Maximum|
|Windows NT||No default||No per-process maximum|
|Windows 2000||10000 (0x2710)||10000 (0x2710)|
On Windows NT, although it appears that the per-process maximum is theoretically the same the system maximum of 65,536, the actual maximum is dependent on the type of user objects as well as other system resources such as desktop heap (which is discussed later). On Windows 2000, the maximum number of user handles per process can be set in the following registry entry; however, the default value of 10000 (0x2710) seems to also be the maximum value.
Since the implementation of the PowerBuilder DataStore relies on an underlying window handle (also referred to as an hWnd), each DataStore requires at least one hWnd, even if no DataWindow object assignment has been made. Depending upon the design of the DataWindow object, more hWnds could be required. Essentially, each type of edit control contributes one or more hWnds to the DataStore resource cost upon instantiation. For example, a DataWindow object with one column that is a standard edit style, another that is a checkbox, and a third that is a dropdown list box would require an additional three hWnds per DataStore instance. If multiple columns have the same edit style, no additional hWnd cost is incurred since those column definitions can share the single handle. Similarly, the number of rows of data within the DataStore is not a factor as far as this resource is concerned.
To preserve the true nature of the DataStore as a non-visual object, one recommendation is to remove the display characteristics from the DataStore altogether by selecting all of the visual elements in the DataWindow design pane and deleting them. Doing so will reduce the user handle count to the absolute minimum of one per DataStore instance.
In Windows 2000, user object handle counts can be monitored via the Processes tab of Task Manager by selecting View->Select Columns... and selecting the "USER object" check box. While this option also exists in Task Manager on Windows NT, it is not functional on that platform. For Windows NT, tools such as ResourceMonitorTM from PRUDENS, Inc. ( http://www.spywindows.com) and other shareware programs available from the Internet can be used to monitor this resource.
GDI Object Handles
The Graphics Device Interface is the means by which applications communicate with graphical devices (such as monitors and printers) in a device-independent fashion. GDI objects include pens, brushes, bitmaps, fonts, etc. The per-process defaults and limits for the number of these objects varies between the NT and Windows 2000 operating systems. Additionally, the registry entry where the per-process limit can be set varies slightly; however, that key is found in the HKEY_LOCAL_MACHINE/Software/Microsoft/Windows NT/CurrentVersion/Windows hive in both operating systems. Details on GDI limits and the registry entry name are provided in the table below.
|Operating System||Default Limit||Maximum?||Registry entry (DWORD)|
|Windows NT||12288 (0x3000)||16384 (0x4000)||ProcessHandleQuota|
|Windows 2000||10000 (0x2710)||16384 (0x4000)||GDIProcessHandleQuota|
? Although this is a documented maximum, systems become fairly unstable
after about 15000 GDI handles have been allocated.
As visual elements, GDI resources are related to user object handles; therefore, by eliminating the visual elements from the DataWindow object, you will correspondingly reduce the need for associated GDI handles to render those elements. Each component instance itself, whether or not a DataStore is being used, requires five GDI handles, including three pens, one brush, and one bitmap. These resources are claimed upon the creation of each component instance and not released until that instance is destroyed, which in most scenarios would never occur as EAServer would be configured to pool instances for reuse and scalability. These five GDI resources are for component instances only; custom class user objects (NVOs) that are instantiated within a component via the CREATE keyword do not incur a GDI cost.
Like user object handles, GDI handles can be monitored on Windows 2000 via Task Manager. On Windows NT, GDI handle monitoring within Task Manager is unavailable, so some alternative tool, such as ResourceMonitor, is needed to monitor this resource.
Kernel Object Handles
The final category of Windows objects is kernel objects, which include system level entities like processes, threads, events, mutexes and files. Each process has a limit of 230 (1073741824) kernel objects.3 In this document which specifically focuses on the use of DataStores, the discussion of kernel objects deals exclusively with the use of threads and their associated memory resources.
A thread is the entity within a process (for EAServer, the process is jagsrv.exe) that the operating system schedules for executing a block of code. In EAServer, each component method invocation occurs on a single operating system thread. When a client makes a request to EAServer, a thread is created for that client, and that thread is generally used to execute the client request. The number of simultaneous client requests therefore has an upper bound equal to the number of threads that can be created within a process.
The number of threads a process can create is also limited by the available virtual memory. By default, when a thread is created, it is assigned one megabyte of stack space, therefore, a process can create at most 2028 threads4. Thread stack size can be modified using the EDITBIN utility provided with Microsoft Visual Studio. For EAServer, a minimum stack size of 32K is recommended, and modification of the default is discouraged if components implement JavaTM Native Interface (JNI) calls to native libraries or invoke methods in 3rd party DLLs. Before attempting this modification, consult the EAServer System Administration Guide for specific instructions and caveats.
The thread stack provides each thread with thread local storage5 (TLS), which is memory associated exclusively with that thread. TLS enables each thread to maintain data that are not shared among other threads but that do persist between method calls serviced by that thread. In EAServer, the use of TLS requires that a component instance be bound to the thread on which it was created for the lifetime of that instance - including when the component is pooled (inactive). To mark a component with this constraint, the com.sybase.jaguar.component.bind.thread component property should be set to true either within Jaguar Manager or within the PowerBuilder EAServer Component project painter prior to deploying the component.
When threads remain bound to component instances, the scalability of the application server is reduced because those threads cannot be reused for subsequent client requests until the associated component instances are destroyed. Nevertheless, bound threads are a requirement in EAServer when using ActiveX® components or PowerBuilder components that declare DataStore objects as instance or global variables. This requirement for DataStore objects is as a direct result of the basis of the implementation on an hWnd. The bound thread requirement does not apply to DataStores declared as local variables. Local variables have a lifetime equal to the scope of a single method call. Since each EAServer method call is serviced by one and only one thread, a local DataStore will by definition be associated with the same thread (and the same TLS) for its lifetime.
Thread usage can be monitored using Task Manager or the Performance Monitor tool in both Windows NT and 2000. Other tools such as SysinternalsTM ProcessExplorer ( http://www.sysinternals.com) and the ProcessViewer included in the Windows NT and 2000 Resource Kits provide more detailed information about currently running threads within a given process.
Desktop heap is a memory resource used to store data relating to graphical objects (such as windows and menus) created within an application. Any executable or library that is statically or dynamically linked with USER32.DLL (such as the PowerBuilder VM) may make use of desktop heap. Due to the interrelationship of user and GDI objects with the desktop heap, limited heap resources can reduce the effective limits on the per-process maximum numbers of user and GDI objects mentioned earlier in this document.
A desktop heap is allocated for each desktop associated with a window station. Each window station contains a default desktop and other optional desktops that might be created programmatically. In turn, each desktop can contain one or more windows. There is exactly one interactive window station (WinSta0) which is created automatically by winlogon.exe process and includes three desktops: the application (default) desktop, the Winlogon desktop and the screen-saver desktop. The application (default) desktop of WinSta0 has an associated heap of default size 3MB. (The Winlogon desktop requires 128KB.)
Programs defined as services are also associated with window stations. There are three different options for running services, and each of these options results in a different association with a window station.
- A non-interactive service running under the Local System account is assigned to the default desktop within the window station named service-0x0-3e7$. By default, the size of the heap associated with this desktop is 512KB.
- A service running under the Local System account that is allowed to interact with the desktop is assigned to the interactive window station, WinSta0 and shares the application heap associated with that station, which by default has a size of 3MB.
- A service that runs under a specific user account is assigned to a unique window station whose name is derived from the Login Security Identifier (SID) of the user. Multiple services that run under the same user account are assigned different window stations because the SID differs. The use of different window stations directly implies different default desktops, so as a result, each such service will be assigned a separate desktop heap of default size 512KB.
The combined size of all desktop heaps has an upper limit of 48MB on Windows NT and 2000.6 If Terminal Server is executing, the 48MB limit is reduced to 22MB. (On Windows XP the desktop heap is configurable to nearly 500MB). The amount of desktop heap assigned to the interactive window station and to window stations associated with services can be configured via a registry setting.7 The setting resides in the string value at
The value is a string that contains the following sequence SharedSection=1024,3072,512 (or perhaps, SharedSection=1024,3072, where the third value is not specified). The first parameter specifies the size of the heap common to all desktops. The second parameter is the size of the default heap for the application desktop (namely, the default desktop in WinSta0). The final parameter specifies the default heap size for desktops created as part of window stations associated with non-interactive services. If this parameter is not specified, all non-interactive services will share the same 3MB heap as the application desktop. If the third value is given, each non-interactive service will be assigned a desktop heap of the specified size. The minimum size for this parameter is 128KB. There is no specific maximum; however, given that the available combined desktop heap allocation can not exceed 48MB, the larger this value the fewer the number of non-interactive services that can be supported.
To examine what window stations are in existence, several utilities exist, including WinObj from SysInternals, Inc. Unfortunately, no utility or programmatic interface exists to measure desktop heap, so experimentation with the third parameter in the SharedSection value is the only way to determine its effect. Since desktop heap is associated with the creation of user objects, components that maintain large numbers of DataStores are the most likely candidates to be affected by this resource. Symptoms of exhausted desktop heap are similar to out-of-memory conditions and can result in general instability, null object references, and failed DataWindow object assignments.
PowerBuilder 8.0.3 Maintenance Release and Beyond
This document has gone into some rather extensive technical detail to explain the constraints that the Windows operating system may place on PowerBuilder components executing within EAServer, specifically when those components interact with DataStore objects. PowerBuilder 8.0.3 introduces some major enhancements to scalability via a new underlying memory management architecture as well as the elimination of the reliance of the DataStore object on hWnds (and thus TLS). In many applications experiencing scalability issues with previous versions of PowerBuilder, just applying the maintenance release (from http://downloads.sybase.com) can introduce some great benefits. PowerBuilder 9.0 released in March 2003 continues the effort toward efficiency of operation in EAServer.
1 Information on Application use of 4GT RAM Tuning, Microsoft Knowledge Base Article Q171793, http://support.microsoft.com/default.aspx?scid=kb;EN-US;q171793
2 Platform SDK : Helper Libraries : User Objects, Microsoft Developer Network (MSDN®) article, http://msdn.microsoft.com/library/default.asp?url=/library/en-us/helplib/handobj_5y7n.asp
3 Platform SDK : Helper Libraries : Kernel Objects, MSDN article, http://msdn.microsoft.com/library/default.asp?url=/library/en-us/helplib/handobj_2vsj.asp
4Platform SDK : DLLs, Processes, and Threads : CreateThread, MSDN, http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/prothred_4084.asp
5 Visual C++ Reference : C Language Reference, MSDN, http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclang/html/_clang_Thread_Local_Storage.asp
6 PRB: "Failed initialization on Comctl32.DLL" from a Service, Microsoft Knowledge Base Article Q225102, http://support.microsoft.com/default.aspx?scid=kb;en-us;Q225102
7 Increasing the Desktop Heap, Microsoft Knowledge Base Article Q126962, http://support.microsoft.com/default.aspx?scid=kb;en-us;Q126962
Sybase and DataWindow are registered trademarks of Sybase, Inc.
Sybase and DataWindow are registered trademarks of Sybase, Inc.