Monday, 18 December 2023

Using class CL_ABAP_PARALLEL for mass parallel dialog work processes

Using class CL_ABAP_PARALLEL is a convenient way to mass process in parallel dialog work processes. This can be especially powerful in a system with more dialog vs other types of work processes. Due to limited documentation, there was a small learning curve when I first implemented this in an S4/HANA on-premise system on SAP_BASIS version 7.54. I’m writing this blog to help others with their implementation of this class.

Setup overview


In this example there will be two local classes (to make it an easy copy/paste test). The XSTRING data type is used to universally pass different kinds of data between the two classes.

  1. Local class MAIN
    • The start and end point for the main logic outside of parallel processing
  2. Local class SINGLE_TASK
    • Inherits from CL_ABAP_PARALLEL
    • Contains logic to process one of the parallel task records at a time

Logic overview for class MAIN


  • Collect the necessary data for all the tasks
  • EXPORT all the task data and any shared task data
  • Create an instance of SINGLE_TASK passing parameters to control how many resources are used
    • See the Constructor parameters listed below
  • Call the RUN Method of SINGLE_TASK passing all the collected data for all tasks and receiving the results for all tasks
    • See the RUN Method parameters listed below, including the results/output structure details
  • IMPORT the task results to process them

Logic overview for class SINGLE_TASK


  • The DO Method is called once per task data record
  • IMPORT the single task record and any shared task data
  • Process the data as necessary
  • EXPORT the task results

Constructor parameters of CL_ABAP_PARALLEL


  • P_NUM_TASKS
    • If supplied, this will simply override P_NUM_PROCESSES
  • P_TIMEOUT
    • Amount of seconds the dialog process can run before automatically timing out
  • P_PERCENTAGE
    • Limits the number of simultaneous parallel processes by calculating the system allowable max parallel tasks across all application servers and reduces it according to the percentage.
    • For example, if there are two application servers with each having 100 as their max parallel task count and P_PERCENTAGE = 75; then the internal logic would limit the number of simultaneous parallel processes to 150
    • This value is ignored if P_NUM_PROCESSES is less than the % calculation
  • P_NUM_PROCESSES
    • Limits the number of simultaneous parallel processes
    • This is ignored if the number is greater than the allowable maximum parallel tasks across all application servers.
    • This value is also ignored whenever P_PERCENTAGE calculates to a lower number
  • P_LOCAL_SERVER
    • Optionally restrict the simultaneous parallel processes to only the current application server
    • This also impacts the internal calculations used for P_PERCENTAGE and P_NUM_PROCESSES

Notes: Class CL_SSI_DISPATCH is primarily controlling the server dispatch logic. Function Module RS_ABAP_PARALLEL is used internally for executing the parallel tasks.

RUN Method parameters of CL_ABAP_PARALLEL


  • P_IN_TAB
    • Table of XSTRING values used as input for the parallel dialog tasks
    • One input row per dialog task
  • P_IN_ALL
    • Single XSTRING value
    • Shared record that’s available in all parallel dialog tasks
  • P_DEBUG
    • Debugging flag is used to skip the parallel framework and directly/sequentially call the DO Method
  • P_OUT_TAB
    • Table of XSTRING values
    • One output row per dialog task
    • Structure fields
      • RESULT – XSTRING of results populated within the DO Method
      • INDEX – Index of the table in P_IN_TAB
      • TIME – Seconds of runtime
      • MESSAGE – Error message for timeouts and other failures

Conclusion


Hopefully these details save you some time when implementing class CL_ABAP_PARALLEL yourself. I’ve found this especially useful when programing a mass data correction utility and the timeframe to run it is limited. I’ve attached an example program which is self contained by using local classes. There are also some test classes for CL_ABAP_PARALLEL which provide useful examples.

Using class CL_ABAP_PARALLEL for mass parallel dialog work processes

REPORT zparallel_test.

CLASS main DEFINITION.

  PUBLIC SECTION.

    TYPES:
      BEGIN OF shared_record,
        process_mode TYPE c LENGTH 1,
      END OF shared_record,

      single_record TYPE scarr-carrid,

      BEGIN OF task_result,
        carrid TYPE scarr-carrid,
        count  TYPE i,
      END OF task_result.

    CLASS-METHODS process.

ENDCLASS.

CLASS single_task DEFINITION
  INHERITING FROM cl_abap_parallel.

  PUBLIC SECTION.

    METHODS: do REDEFINITION.

ENDCLASS.

*This is the only statement outside of the classes
main=>process( ).

CLASS main IMPLEMENTATION.

  METHOD process.

    DATA: tasks_input        TYPE cl_abap_parallel=>t_in_tab,
          task_input_single  TYPE xstring,
          single_record      TYPE single_record,
          tasks_input_shared TYPE xstring,
          shared_record      TYPE shared_record,
          task_result        TYPE task_result.

    shared_record-process_mode = 1.

*   Since the process_mode value is shared across all tasks, store 
*   it in the shared variable (tasks_input_shared) instead of
*   repeating it for every task input record (tasks_input)

*   To be imported by SINGLE_TASK->DO
    EXPORT buffer_task_shared = shared_record 
      TO DATA BUFFER tasks_input_shared.

*   Get data to be processed.  EXPORT each record and collect them all into
*   table tasks_input.
    SELECT
      carrid
    FROM scarr
    INTO @single_record.

*     To be imported by SINGLE_TASK->DO
      EXPORT buffer_task = single_record TO DATA BUFFER task_input_single.
      INSERT task_input_single INTO TABLE tasks_input.

    ENDSELECT.

*   Create the instance while configuring the resource usage
    DATA(parallel) = NEW single_task(
*                          p_num_tasks     = 8
*                          p_timeout       = 200
*                          p_percentage    = 50
*                          p_num_processes = 20
*                          p_local_server  =
                         ).

    DATA(debug) = ' '.

*   Perform the tasks in parallel
    parallel->run(
      EXPORTING
        p_in_tab  = tasks_input
        p_in_all  = tasks_input_shared
        p_debug   = debug
      IMPORTING
        p_out_tab = DATA(tasks_output)
    ).

    LOOP AT tasks_output ASSIGNING FIELD-SYMBOL(<task_output_single>).

*     Something went wrong, like a timeout, if the message has a value
      IF <task_output_single>-message IS NOT INITIAL.
        WRITE: / 'Task Error:', <task_output_single>-message.
      ENDIF.

      IF <task_output_single>-result IS NOT INITIAL.
*       Exported from SINGLE_TASK->DO
        IMPORT buffer_result = task_result 
          FROM DATA BUFFER <task_output_single>-result.

        WRITE: / task_result-carrid, task_result-count.
      ENDIF.

    ENDLOOP.

  ENDMETHOD.

ENDCLASS.

CLASS single_task IMPLEMENTATION.

  METHOD do.

*   I referenced the MAIN local class instead of using the data dictionary
*   for the ease for copy/paste
    DATA: shared_record TYPE main=>shared_record,
          single_record TYPE main=>single_record,
          task_result   TYPE main=>task_result.

*   Exported by MAIN->PROCCESS
    IMPORT buffer_task_shared = shared_record FROM DATA BUFFER p_in_all.

*   Exported by MAIN->PROCCESS
    IMPORT buffer_task = single_record FROM DATA BUFFER p_in.

*   Other shared values I like to include are a simulation/update flag
*   and a level for the logging detail
    CASE shared_record-process_mode.
      WHEN '1'.

*       Incredibly simplistic example which doesn't warrant parallel processing
        SELECT
          carrid,
          COUNT( * )
        FROM sflight
        WHERE
          carrid = @single_record
        GROUP BY
          carrid
        INTO ( @task_result-carrid, @task_result-count ).

        ENDSELECT.

      WHEN '2'.
    ENDCASE.

*   To be imported by MAIN->PROCESS
    EXPORT buffer_result = task_result TO DATA BUFFER p_out.

  ENDMETHOD.

ENDCLASS.

No comments:

Post a Comment