DBA Hub

📋Steps in this guide1/6

Decoupling to Improve Performance

Learn how to decouple processing to give the impression of improved performance.

oracle miscconfigurationintermediate
by OracleDba
13 views
1

The Problem

Let's assume we have an application that calls a database procedure to perform an action, but the users are complaining the screen is too slow. We've spent time trying to tune the process, but it is still considered too slow. We will represent this process with the following procedure, using to simulate a workload taking an excessive amount of time. When we call the procedure it takes approximately 10 seconds to complete, which is why the users find it unacceptable. We consult the users and they do not care if the processing is done immediately, they just want to guarantee their request has been made and will be processed at some point in the future, so we decide to decouple the request from the processing.

Code/Command (click line numbers to comment):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
CONN / AS SYSDBA

-- DROP USER testuser1 CASCADE;

-- Create test user.
CREATE USER testuser1 IDENTIFIED BY testuser1
  QUOTA UNLIMITED ON users;

GRANT CREATE SESSION, CREATE PROCEDURE TO testuser1;
GRANT EXECUTE ON DBMS_LOCK TO testuser1;


CONN testuser1/testuser1

CREATE OR REPLACE PROCEDURE slow_request (p_username      IN  VARCHAR2,
                                          p_request_info  IN  VARCHAR2) AS
BEGIN
  -- Pretend to do some work for 10 seconds.
  DBMS_LOCK.sleep(10);
END slow_request;
/
SHOW ERRORS

SQL> SET TIMING ON
SQL> EXEC slow_request('Tim', 'Pay me lots of money!');

PL/SQL procedure successfully completed.

Elapsed: 00:00:10.05
SQL> SET TIMING OFF
SQL>
2

Decoupling Setup

The first thing we need to do is to decide how we are going to decouple the process. There are several methods available to us including: - Insert the user requests into a table to be processed later. - Use Oracle Advanced Queuing to queue the user requests. - Use the Oracle scheduler to fire one-off jobs for each request, so they start to be processed instantly, but the user does not have to wait for them to complete before having control returned to them. In this example I will be using Oracle Advanced Queuing to manage the user requests. Make sure the test user has the correct privileges to manage queues. Define a payload object that matches the parameters to the procedure, then use that payload to create a queue table. Create a queue within the queue table and start the queue.

Code/Command (click line numbers to comment):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
-- (adjust usernames as required).
CONNECT / AS SYSDBA
GRANT CREATE TYPE TO testuser1;
GRANT AQ_ADMINISTRATOR_ROLE, AQ_USER_ROLE TO testuser1;
GRANT EXECUTE ON DBMS_AQ TO testuser1;

CONNECT testuser1/testuser1

-- Create payload database type.
CREATE OR REPLACE TYPE slow_request_type AS OBJECT (
  username      VARCHAR2(30),
  request_info  VARCHAR2(2000)
);
/

-- Create and start queue.
BEGIN
  DBMS_AQADM.create_queue_table (
    queue_table        => USER || '.slow_request_queue_tab',
    queue_payload_type => USER || '.slow_request_type');
  
  DBMS_AQADM.create_queue (queue_name  => USER || '.slow_request_queue',
                           queue_table => USER || '.slow_request_queue_tab');
  
  
  DBMS_AQADM.start_queue (queue_name => USER || '.slow_request_queue',
                          enqueue    => TRUE);
END;
/
3

Decoupling API

With the basic setup in place, it's time to define an API to manage the user requests. The following package contains procedures to enqueue and dequeue messages along with a procedure to actually process the contents of the queue.

Code/Command (click line numbers to comment):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
CREATE OR REPLACE PACKAGE slow_request_api AS
  PROCEDURE enqueue (p_username      IN  VARCHAR2,
                     p_request_info  IN  VARCHAR2);
  PROCEDURE dequeue (p_username      OUT  VARCHAR2,
                     p_request_info  OUT  VARCHAR2);
  PROCEDURE process_requests_job;
END slow_request_api;
/
SHOW ERRORS


CREATE OR REPLACE PACKAGE BODY slow_request_api AS

  g_schema            VARCHAR2(30) := USER;
  g_process_job_loop  NUMBER       := 100;

  -- -----------------------------------------------------------------
  PROCEDURE enqueue (p_username      IN  VARCHAR2,
                     p_request_info  IN  VARCHAR2) AS
  -- -----------------------------------------------------------------
    l_enqueue_options     DBMS_AQ.enqueue_options_t;
    l_message_properties  DBMS_AQ.message_properties_t;
    l_message_handle      RAW(16);
    l_slow_request_msg    slow_request_type;
  BEGIN
    l_slow_request_msg := slow_request_type(p_username, p_request_info);

    DBMS_AQ.enqueue(queue_name          => g_schema || '.slow_request_queue',        
                    enqueue_options     => l_enqueue_options,     
                    message_properties  => l_message_properties,   
                    payload             => l_slow_request_msg,             
                    msgid               => l_message_handle);
  END enqueue;
  -- -----------------------------------------------------------------


  -- -----------------------------------------------------------------
  PROCEDURE dequeue (p_username      OUT  VARCHAR2,
                     p_request_info  OUT  VARCHAR2) AS
  -- -----------------------------------------------------------------
    l_dequeue_options     DBMS_AQ.dequeue_options_t;
    l_message_properties  DBMS_AQ.message_properties_t;
    l_message_handle      RAW(16);
    l_slow_request_msg    slow_request_type;
  BEGIN
    -- Don't wait on the queue for a message to arrive.
    l_dequeue_options.wait := DBMS_AQ.no_wait;
    
    DBMS_AQ.dequeue(queue_name          => g_schema || '.slow_request_queue',
                    dequeue_options     => l_dequeue_options,
                    message_properties  => l_message_properties,
                    payload             => l_slow_request_msg,
                    msgid               => l_message_handle);

    p_username      := l_slow_request_msg.username;
    p_request_info  := l_slow_request_msg.request_info;
  END dequeue;
  -- -----------------------------------------------------------------


  -- -----------------------------------------------------------------
  PROCEDURE process_requests_job AS
  -- -----------------------------------------------------------------
    l_username      VARCHAR2(30);
    l_request_info  VARCHAR2(2000);
    
    ex_timeout      EXCEPTION;
    PRAGMA EXCEPTION_INIT(ex_timeout, -25228);
  BEGIN
    -- Dequeuing one message at a time. Could improve performance using
    -- DBMS_AQ.DEQUEUE_ARRAY for array processing.
    FOR i IN 1 .. g_process_job_loop LOOP
      dequeue(p_username => l_username,
              p_request_info => l_request_info);
              
      slow_request(p_username => l_username,
                   p_request_info => l_request_info);
    END LOOP;
  EXCEPTION
    WHEN ex_timeout THEN
      -- Queue is empty.
      NULL;
  END process_requests_job;
  -- -----------------------------------------------------------------

END slow_request_api;
/
SHOW ERRORS
4

Decoupling Test

With the API in place we can test the impact of decoupling the process on the user experience. Not surprisingly, five requests to the original procedure took approximately 50 seconds to process, while five decoupled requests completed almost instantly. This marks a dramatic improvement in user experience of our application, but we have to remember the work must get done at some point! As we can see, the processing still takes the same length of time once we process the queue.

Code/Command (click line numbers to comment):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
SET SERVEROUTPUT ON
DECLARE
  l_start  NUMBER;
  l_loops  NUMBER := 5;
BEGIN
  -- Time the original method.
  l_start := DBMS_UTILITY.get_time;

  FOR i IN 1 .. l_loops LOOP
    slow_request(p_username     => 'Tim',
                 p_request_info => 'Pay me lots of money!');
  END LOOP;
  
  DBMS_OUTPUT.put_line('Original Method : ' || 
                       (DBMS_UTILITY.get_time - l_start) || ' hsecs');

  -- Time the decoupled method.
  l_start := DBMS_UTILITY.get_time;

  FOR i IN 1 .. l_loops LOOP
    slow_request_api.enqueue(
      p_username    => 'Tim',
     p_request_info => 'This is dummy data');
  END LOOP;
  
  DBMS_OUTPUT.put_line('Decoupled Method: ' || 
                       (DBMS_UTILITY.get_time - l_start) || ' hsecs');

  -- Time the background processing.
  l_start := DBMS_UTILITY.get_time;

  slow_request_api.process_requests_job;
  
  DBMS_OUTPUT.put_line('Background Proc : ' || 
                       (DBMS_UTILITY.get_time - l_start) || ' hsecs');
END;
/
Original Method : 5006 hsecs
Decoupled Method: 1 hsecs
Background Proc : 5010 hsecs

PL/SQL procedure successfully completed.

Elapsed: 00:01:40.21
SQL>
5

Cleanup

The following code removes the sample code and the queue definition.

Code/Command (click line numbers to comment):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DROP PACKAGE slow_request_api;
DROP PROCEDURE slow_request;

BEGIN
  DBMS_AQADM.stop_queue (queue_name => USER || '.slow_request_queue',
                         enqueue    => TRUE);
  
  DBMS_AQADM.drop_queue (queue_name => USER || '.slow_request_queue');
  
  DBMS_AQADM.drop_queue_table (queue_table => USER || '.slow_request_queue_tab');
END;
/

DROP TYPE slow_request_type;
6

Conclusions

Here are a few of the conclusions we can draw from this experience of decoupling. - Decoupling processes allows us to greatly improve the user experience of our applications by making the processing asynchronous. - The users must understand the processing is decoupled or they may get confused when their changes are not instantly apparent. - We are not actually improving the performance of the processing, just moving the problem somewhere else. - We should only queue requests if the effort of queuing the request is significantly less than the effort of performing the processing immediately. - We would need to decide on a schedule for running the procedure. For more information see: Hope this helps. Regards Tim...

Comments (0)

Please to add comments

No comments yet. Be the first to comment!