"
Apoiado por

Ensina o ABAP a fazer malabarismo

O que podemos fazer quando as noites não são suficientes para os processamentos diários?


Porquê?

Com o passar dos tempos os sistemas SAP executam cada vez mais um volume de jobs diários durante o período da noite. Mas o conceito de noite (entenda-se com menos utilizadores no sistema) facilmente se dissolve se considerarmos um sistema multi-geografia em que há utilizadores espalhados por todo o planeta. Estes jobs são muitas vezes processos de integração/recálculo que podem envolver um volume considerável de dados.

Este tipo de processos normalmente são independentes, isto é, não é necessário esperar pela execução de um item do processo para podermos executar o próximo. Considerando estes pressupostos seria extremamente útil conseguir executá-los simultaneamente. Esta separação é possível implementando uma infraestrutura que permita processamento paralelo.


O que é?

O processamento paralelo não é mais do que, como o nome indica, o processamento em simultâneo de tarefas independentes entre si. Um conceito similar às threads de algumas linguagens de programação OOP (C#, C++, Java).
Na prática o processamento paralelo é uma execução de RFC Assíncronos utilizando um RFC server group. Um RFC server group é um conceito criado pela SAP que permite configurar um identificador e associar múltiplos servidores aplicacionais bem como a percentagem de recursos disponíveis.

Como funciona?


CALL FUNCTION func STARTING NEW TASK task 
              [DESTINATION {dest|{IN GROUP {group|DEFAULT}}}] 
              [{PERFORMING subr}|{CALLING meth} ON END OF TASK] 
              parameter_list.

O elemento atómico desta abordagem será um módulo função RFC enabled que se destine a processar um único item (ou um bloco de ítens, mas vamos simplificar para agora). E o ponto de ligação entre um RFC e o RFC server group é a keyword IN GROUP <nome definido na RZ12>.

É também possível especificar um callback de retorno, que será executado quando este processo terminar, keyword PERFORMING subr ON END OF TASK ou CALLING method ON END OF TASK.

Comparação entre processamento paralelo e processamento sequencial?

Vamos ver um exemplo em que comparamos dois programas cujo objetivo é o mesmo. Vamos considerar o seguinte objetivo:

“Para cada país configurado no sistema na tabela T005 pretendemos efectuar um determinado cálculo e guardar o resultado numa tabela Z.”

Para este efeito temos disponível um módulo de função “Z_PP_UNIT_TESTS” que será invocado pelos dois programas.


FUNCTION Z_PP_UNIT_TESTS.
*"----------------------------------------------------------------------
*"*"Interface local:
*"  IMPORTING
*"     VALUE(DATA) TYPE  T005 OPTIONAL
*"  EXPORTING
*"     VALUE(RETURNINFO) TYPE  ZPP_EXECUTIONINFO
*"----------------------------------------------------------------------
  data: random_num  type qfranint,
        l_odd       type int4,
        l_count     type int4.

  returninfo-server = sy-host.
  CALL FUNCTION 'TH_GET_OWN_WP_NO'
    IMPORTING
      wp_pid = returninfo-wpinfo-wp_pid.

  WHILE 1 = 1.

    CALL FUNCTION 'QF05_RANDOM_INTEGER'
      EXPORTING
        ran_int_max = 100
        ran_int_min = 10
      IMPORTING
        ran_int     = random_num.

    l_odd = random_num mod 2.

    IF l_odd = 0.
      add 1 to l_count.
    ENDIF.

    IF data-XADDR = 'X' and l_count > 100000.
      exit.
    ELSEIF l_count > 150000.
      exit.
    ENDIF.

Processamento sequencial

Pela sua extensão e complexidade apresentamos o código num ficheiro em separado que podes ver aqui.

Mostro aqui a parte principal, o loop:


  loop at it_t005 into wa_005.
    clear l_returninfo.

    CALL FUNCTION 'Z_PP_UNIT_TESTS'
      EXPORTING
        DATA       = wa_005
      IMPORTING
        RETURNINFO = l_returninfo.
    DO.
      ASSIGN COMPONENT sy-index OF STRUCTURE wa_005 TO .
      IF sy-subrc NE 0.
        EXIT.
      ENDIF.
      move  TO tp_written.
      CONCATENATE tp_string_concat tp_written INTO tp_string_concat.
    ENDDO.
    wa_proc-info = tp_string_concat.
    wa_proc-server = l_returninfo-server.
    append wa_proc to git_processed.
  ENDLOOP.

A medição:

processamento_sequencial_analise

O trace:

processamento_sequencial_trace

Processamento paralelo

O programa exemplo do processamento paralelo pode ser visto aqui.

Mosto aqui apenas as partes principais, o loop e a rotina de callback:


  loop at it_t005_data into wa_005_data.
    l_tabix = sy-tabix.
    CONCATENATE 'Task' l_tabix into taskname.
    clear l_returninfo1.

    CALL FUNCTION 'Z_PP_UNIT_TESTS'
      STARTING NEW TASK taskname
      DESTINATION in group groupname
      PERFORMING callback_meth ON END OF TASK
      EXPORTING
        DATA                  = wa_005_data
      EXCEPTIONS
        resource_failure      = 1
        system_failure        = 2
        COMMUNICATION_FAILURE = 3.
    IF sy-subrc = 0.
      add 1 to gv_send.
      DO.
        ASSIGN COMPONENT sy-index OF STRUCTURE wa_005_data TO .
        IF sy-subrc NE 0.
          EXIT.
        ENDIF.
        WRITE  TO tp_written1.
        CONCATENATE tp_string_concat1 tp_written1 INTO tp_string_concat1.
      ENDDO.
      wa_proc1-info = tp_string_concat1.
      append wa_proc1 to git_processed1.
    else.
      " Erro no processamento, efectuar nova tentativa? registar em log? ...
      " esperar pela libertação de recursos?
      " ...
    ENDIF.

  ENDLOOP.

  WAIT UNTIL gv_send >= gv_receive.
  " parallel processing is now finalized

----------------

FORM callback_meth using taskname.
  data: l_retinfo type ZPP_EXECUTIONINFO.

  RECEIVE RESULTS FROM FUNCTION 'Z_PP_UNIT_TESTS'
  IMPORTING
    returninfo = l_retinfo
    EXCEPTIONS
        resource_failure      = 1
        system_failure        = 2
        COMMUNICATION_FAILURE = 3.
  IF sy-subrc <> 0.
    " controlo de erros ... retry? log?
    " ...
  ENDIF.
  add 1 to gv_receive.

ENDFORM.                    "callback_meth

A medição:

processamento_paralelo_analise

Os traces (são vários porque há vários processos a correr):

processamento_paralelo_trace4

processamento_paralelo_trace3

processamento_paralelo_trace2

processamento_paralelo_trace1

Estou convencido, como utilizar?

Para utilizarmos esta ferramenta temos que considerar alguns aspectos, entre eles:

  • Invocação do RFC com o respetivo RFC Server Logon Group;
  • Falta de recursos no RFC Server Logon Group de momento;
  • Necessidade de codificar um callback por código.

Para contemplar estes e outros aspectos temos a necessidade de andar sempre com algum código atrás, isto é, a velha técnica do copiar, colar e alterar algumas coisas.

Para efeitos de simplificação criei uma framework que permite simplificar este processo agrupando toda a lógica comum para que seja simples e práctico efectuar alterações de uma forma centralizada. Segue-se um exemplo da sua utilização:


report  zrep_pp_unit_test.

include zrep_pp_unit_test_top.
include zrep_pp_unit_test_dat.
include zrep_pp_unit_test_frm.

initialization.
  clear sp_loggr.
  sp_maxt = 3600.
  sp_tswt = 2.
  sp_maxr = 5.
  sp_tstn = 1.
  sp_logl = 0.

start-of-selection.

  " ****************************************************************
  " Retrieve some example data
  data: it_t005 type table of t005,
        l_input type int4.
  l_input = sp_lins.
  IF l_input > 0 and l_input < 255.
    " do nothing on purpose
  else.
    l_input = 10.
  ENDIF.

  select * into table it_t005 up to l_input rows from t005.

  try .
      " ************************************************************
      " Build instance
      l_pp_ref = zcl_pp_factory=>build_instance( 
        pit_raw_data            = it_t005
        p_rfc_name              = 'Z_PP_UNIT_TESTS'
        p_logon_group           = sp_loggr
        p_max_execution_time    = sp_maxt
        p_task_wait_time        = sp_tswt
        p_task_max_retries      = sp_maxr
        p_task_wait_no_resource = sp_tstn
        p_log_level             = sp_logl ).

      " ************************************************************
      " Execute the processing
      l_pp_ref->run( ).

      " ************************************************************
      " Display results
      perform display_result using
                                l_pp_ref
                              changing
                                git_processed
                                git_unprocessed
                                git_error.

    catch zcx_pp_exception into cx.
      l_err = cx->if_message~get_text( ).
      message l_err type 'S' DISPLAY LIKE 'E'.
  endtry.

Onde posso obter a framework?

Esta framework foi elaborada para simplificar e mitigar a complexidade inerente à implementação de um processo que recorra ao processamento paralelo (criação do método com uma assinatura específica para callback, código para controlar repetições e reprocessamentos que era constantemente copiado e colado, manutenção do código mais complexa, entre muitos outros problemas associados).

Aqui está disponível a documentação associada a esta Framework bem como todo o código fonte disponibilizado no formato SAPLINK.

O António Vaz saúda-vos.

O Abapinho agradece a António Vaz e saúda-o.

2 comentários a “Ensina o ABAP a fazer malabarismo”

  1. José Vília Diz:

    Bom dia,

    Já utilizo este tipo de processamento de há dois anos para cá e acho que é uma das melhores ferramentas que temos para poder paralelizar processos grandes alem de que as primeiras vezes que o montamos é desafiante! :)

    Só ha um ponto que gostaria de partilhar:

    No meu ver, este tipo de processamento não pode ocupar todos os recursos que temos disponíveis no server group.

    Dando um exemplo prático:
    Se vou implementar um processo paralelo, e antes do meu processo começar, na cadeia batch, temos outro idêntico que não restringe o numero de processos que lança, consumindo todos os recursos do server group, bem que posso fazer código corrido porque sempre que chamar a minha função sairá sempre com a excepção resource_failure e terei que a chamar online.

    Para controlar o número de processos que chamamos, habitualmente utilizo duas funções que me dão a informação que preciso. São as funções: SPBT_INITIALIZE e SPBT_GET_CURR_RESOURCE_INFO e chamo-as assim:

    IF p_paral EQ ‘X’.
    * Se foi marcada opção de processamento paralelo no ecrã de seleção..

    * Inicializamos o server group e obtemos os processos máximos e os disponíveis:
    CALL FUNCTION ‘SPBT_INITIALIZE’
    EXPORTING
    group_name = lv_group_name “nome do server group da RZ12
    IMPORTING
    max_pbt_wps = lv_max_pbt_wps
    free_pbt_wps = lv_free_pbt_wps
    EXCEPTIONS
    invalid_group_name = 1
    internal_error = 2
    pbt_env_already_initialized = 3
    currently_no_resources_avail = 4
    no_pbt_resources_found = 5
    cant_init_different_pbt_groups = 6
    OTHERS = 7.

    IF sy-subrc = 3.
    * Como já foi inicializado, apenas obtemos os recursos do grupo de funções:
    CALL FUNCTION ‘SPBT_GET_CURR_RESOURCE_INFO’
    IMPORTING
    max_pbt_wps = lv_max_pbt_wps
    free_pbt_wps = lv_free_pbt_wps
    EXCEPTIONS
    internal_error = 1
    pbt_env_not_initialized_yet = 2
    OTHERS = 3.
    ENDIF.

    IF sy-subrc EQ 0.

    * Neste perform, apenas faço uma regra:
    * Se os processos máximos são mais que 30 (um valor que me lembrei :) ) e temos pelo menos 60% disponíveis
    * Utilizo 4 processos paralelos, caso contrario, chamo todas as sequencias de threads online…

    PERFORM check_number_of_processes USING lv_max_pbt_wps
    lv_free_pbt_wps
    CHANGING lv_tasks_to_use.

    * Estas 4 tarefas, no meu caso eram suficientes, os valores que menciono não têm qualquer tipo de
    * critério adicional, a não ser o bom senso VS a necessidade do processo! :)
    ENDIF.

    ENDIF.

    * Qualquer outra excepção que ocorra, executo o processamento online pois deverá haver alguma indisponibilidade…

  2. Alfredo Araujo Diz:

    Antônio,

    Estou tentando replicar esse código, as vc não disponibilizou essa função Z_PP_UNIT_TESTS
    Poderia me enviar esses códigos para estudar ?

    Obrigado

Deixe um comentário


Acerca do Abapinho
O Abapinho é suportado pelo WordPress
Artigos (RSS) e Comentários (RSS).