In my last weblog, I asked whether you, the readers, would be interested in architectural issues. Based upon your response, I will start this series with an application pattern that occurs in one form or another in many applications: application server-side buffering of business data, and the coordination of these buffers with the database and enqueue server.
Normally, it looks okay to just select the data that is needed within a method or function module from the database. What is the problem? Well, for starters, all of these calls (several, if the same method or function module is called several times within a transaction) could lead to physical access to the data on the hard drives; the slowest possible access. If we are lucky, the database cache of the RDBMS we use might still contain the data. However, even if we are that lucky, there is still network traffic between the application server and the database server.
The SAP DDIC allows to declare database tables as “buffered”. This adresses the issue of network traffic: the data is buffered on the application server side. Therefore repeated read access does not cause additional network traffic. However, buffering a database table has the following side effects:
- The SQL interface is called regardless of the fact that the data is buffered. This incurs performance overhead.
- If data is updated, it takes some time (possibly minutes, depending on the system setup) for the changes to be propagated to all application servers.
Therefore, buffering via the DDIC mechanism is okay for customizing data and master data that rarely changes, and / or where it is not mission-critical to have the changed data available as soon as possible. If, however, we are dealing with transactional data or time-critical master data, this is not a feasible option.
What can be done in this case is application-side buffering. Here, typically a dedicated persistency layer stores the information read from the database, and if the same data is accessed a second time, it is read from the application-side buffer.
If we combine this with enqueues, we can ensure that the data that we have in your application-side buffer is both current and that we have the necessary access rights, if we want to change it later, or if we want to ensure that no one else changes it. Also, we do not need access to the SQL interface via the usage of open SQL commands.
How do we implement such a buffer? Well, there are several possibilities, depending on your specific needs in our application. In many applications, I have seen internal tables in a persistency layer. On top of that, some applications, including the one I am responsible for, use object buffering: here, there is an object layer that structures the data of the objects which are processed in a way in which the business logic needs it. This can be different from the way they are structured in a persistency layer: the persistency layer is geared towards efficient database operations, and the object layer towards optimised maintenance of the relevant application objects.
Potential uses of buffered application data
The separation between a persistency layer buffer and another object layer buffer can benefit object-oriented systems. Normally, object-oriented systems are instance-based. However, if we implement a persistency layer based upon class methods and attributes or singletons, it is possible to represent a database table with a single internal table. In this situation, we can use SELECT…INTO TABLE to read a set of instance data in one go, again minimising database access and network traffic. Once the data is in the application buffer, normal object instances can be created one after the other using this data. This is a useful pattern in package-oriented (mass-processing) scenarios.
Another benefit of this kind of buffering is that we can go even further in optimising the communication with the database. Here, we will look shortly at the writing side of the story. The persistency layer buffer can handle write operations basically in two ways:
- As a write-through buffer. Here, whenever a method of the persistency layer is called that changes the data, it is written to the buffer and to the database.
- As an event-based buffer. In this scenario, changes to the data are made in the buffer only, with some additional information being stored in the buffer regarding the nature of the change (data newly created, data changed, data to be deleted). The persistency layer is registered to a “flush” event that triggers the writing of the changes to the database. Advantages: in the case of multiple changes, the data is only written once (in the latest version). Also, the changes can be done via “INSERT /UPDATE / DELETE …FROM TABLE”. Disadvantages: the complexity of the persistency layer is higher, and the overall software architecture of the application must ensure that the flush event is triggered right before a COMMIT WORK, or at least in a way that ensures that changes are not lost.
Detour: LUW in an ESA environment
At this point I have to make a short detour into some general architectural issues, which are especially relevant for ESA-compliant applications. Some of these points are important for BAPI scenarios too.
In a scenario where other applications call our application using the BAPI programming model, it is imperative that we do not close the SAP LUW. Therefore, “COMMIT WORK” and “ROLLBACK WORK” are not allowed in application coding.
I can hear you say “but wait, what about my old SAP GUI access I still have to support in addition to the core services, or reports that have to run over thousands of items?”. This is a valid point. What I meant by “application coding” above is the business logic that provides services. On top of that business logic, one can depict something like a “channel layer” for these pre-ESA access methods, where both the SAP GUI and reports triggered manually or automatically are located. And here it is okay to close a SAP LUW. But please remember: both in a BAPI- or BAPI-like and an ESA-scenario you are not the owner of the SAP LUW anymore! In the BAPI case, the SAP LUW is owned by the client, and in the ESA case by the runtime.
The following picture is an attempt to show how these things work together:
Buffers, Enqueues and the end of a Logical Unit of Work
As has been mentioned above, the use of locks (“enqueues” in SAP speak) is necessary to ensure data consistency. The tricky part is what to do about the release of these locks.
Normally, if we acquire a lock and we do not worry about the scope (that is, we leave the import parameter “_scope” of the enqueue module at its default value), the ABAP command “COMMIT WORK” will start all updates in update task and release the locks afterwards. This default behaviour can be observed in a lot of applications, and it fits most application needs.
However, this means that once a COMMIT has occured, our application buffers need to react to it. The reason is that once the locks are released, another process can grab the object from the database, acquire a lock and change the data. In other words, we can no longer rely on our process being in charge of that data.
How do we cope with this? Well, there are two important steps:
- Find out about this fact.
- React to it.
The second step is relatively easy: we create a notifier class that raises an event. All the persistency classes provide handlers (methods) that are registered to that event. These handlers can either try to reacquire the lock (risk: some other process might have changed the data!) or, normal case, delete the buffers, so that the items will have to be reread from the database (in case someone changed them in the meantime) and the locks reacquired that way.
Now for the trickier part: how do we find out about the fact that there was a COMMIT, or that a COMMIT is imminent, and we are to dump our buffers?
In the non-ESA case, the notifier class can subscribe to the event TRANSACTION_FINISHED of the class CL_SYSTEM_TRANSACTION_STATE. This basis event is raised whenever COMMIT WORK or ROLLBACK WORK has been called.
In the ESA case, to the best of my knowledge there will be a core service that will be called from the framework around the save time. Our implementation of this service would be a method that calls the notifier class, which then raises our internal event to flush the buffer.
As of today I don’t have detailed knowledge about these core services; sorry about that. As an architect, I would make sure that all the event handler methods that flush the buffers do nothing but flush the buffers. After all, I cannot say whether the event will happen before or after the actual COMMIT, so doing anything else, like writing change docs or application log entries, might break the ESA services programming model.
So here we are. I talked about why to use application-side buffers, how to do it, how buffers interact with enqueues and LUWs, and how to get rid of them. Of course this merely scratches the surface of the subject. In an object-oriented environment, for example, instances could be buffered and reused, too. This would enable us to write applications that create and destroy millions of objects in an overnight batch run from a functional point of view while internally using the same chunks of memory again and again without the garbage collector ever kicking in. Buffering of application log entries would be another interesting topic.
However, I should better leave some issues for future weblogs.