Copying Data From A Ruby On Rails Session Into An Oracle Session
2008-04-27 Dan Bikle Motivation An Oracle session contains information about a current connection to the database. Three useful features of Oracle can be tied to an Oracle session:-
Historical reporting of session behavior (Auditing and Performance Tuning)
Attach resource constraints to active sessions
Attach security constraints to active sessions
http://download.oracle.com/docs/cd/B19306_01/server.102/b14220/process.htm#sthref1481 An Oracle session, in some ways, is similar to an an RoR session. A Ruby On Rails (RoR) session is an object which keeps track of a connection to an RoR website. RoR Session Documentation:
-
http://api.rubyonrails.com/classes/ActionController/SessionManagement/ClassMethods.html#M000273
http://api.rubyonrails.com/classes/ActionController/Base.html#M000460
-
search
search_remove
search_search
at
innerhtml
-
A user with books in his cart and is "checking-out" should not be constrained
A user with books in his cart should get more resources than a user with no books in his cart
A user with no books in his cart should get more resources than a bot which is crawling the site
A bot crawling the site should get more resources than batch programs which create BI reports.
-
Performance Tuning combined with end-to-end tracing
Policy based resource constraints
Run-time configurable security constraints
502 bash-03:19:47-dbiklelt:~ $
502 bash-03:19:48-dbiklelt:~ $
502 bash-03:19:48-dbiklelt:~ $
502 bash-03:19:49-dbiklelt:~ $ sqlplus system/manager
SQL*Plus: Release 10.2.0.1.0 - Production on Sun Apr 27 03:19:57 2008
Copyright (c) 1982, 2005, Oracle. All rights reserved.
Connected to:
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Production
With the Partitioning, OLAP and Data Mining options
03:19:57 orcl SQL >
03:20:05 orcl SQL >
03:20:05 orcl SQL > desc v$session
Name Null? Type
----------------------------------------- -------- ----------------------------
SADDR RAW(4)
SID NUMBER
SERIAL# NUMBER
AUDSID NUMBER
PADDR RAW(4)
USER# NUMBER
USERNAME VARCHAR2(30)
COMMAND NUMBER
OWNERID NUMBER
TADDR VARCHAR2(8)
LOCKWAIT VARCHAR2(8)
STATUS VARCHAR2(8)
SERVER VARCHAR2(9)
SCHEMA# NUMBER
SCHEMANAME VARCHAR2(30)
OSUSER VARCHAR2(30)
PROCESS VARCHAR2(12)
MACHINE VARCHAR2(64)
TERMINAL VARCHAR2(16)
PROGRAM VARCHAR2(64)
TYPE VARCHAR2(10)
SQL_ADDRESS RAW(4)
SQL_HASH_VALUE NUMBER
SQL_ID VARCHAR2(13)
SQL_CHILD_NUMBER NUMBER
PREV_SQL_ADDR RAW(4)
PREV_HASH_VALUE NUMBER
PREV_SQL_ID VARCHAR2(13)
PREV_CHILD_NUMBER NUMBER
MODULE VARCHAR2(48)
MODULE_HASH NUMBER
ACTION VARCHAR2(32)
ACTION_HASH NUMBER
CLIENT_INFO VARCHAR2(64)
FIXED_TABLE_SEQUENCE NUMBER
ROW_WAIT_OBJ# NUMBER
ROW_WAIT_FILE# NUMBER
ROW_WAIT_BLOCK# NUMBER
ROW_WAIT_ROW# NUMBER
LOGON_TIME DATE
LAST_CALL_ET NUMBER
PDML_ENABLED VARCHAR2(3)
FAILOVER_TYPE VARCHAR2(13)
FAILOVER_METHOD VARCHAR2(10)
FAILED_OVER VARCHAR2(3)
RESOURCE_CONSUMER_GROUP VARCHAR2(32)
PDML_STATUS VARCHAR2(8)
PDDL_STATUS VARCHAR2(8)
PQ_STATUS VARCHAR2(8)
CURRENT_QUEUE_DURATION NUMBER
CLIENT_IDENTIFIER VARCHAR2(64)
BLOCKING_SESSION_STATUS VARCHAR2(11)
BLOCKING_INSTANCE NUMBER
BLOCKING_SESSION NUMBER
SEQ# NUMBER
EVENT# NUMBER
EVENT VARCHAR2(64)
P1TEXT VARCHAR2(64)
P1 NUMBER
P1RAW RAW(4)
P2TEXT VARCHAR2(64)
P2 NUMBER
P2RAW RAW(4)
P3TEXT VARCHAR2(64)
P3 NUMBER
P3RAW RAW(4)
WAIT_CLASS_ID NUMBER
WAIT_CLASS# NUMBER
WAIT_CLASS VARCHAR2(64)
WAIT_TIME NUMBER
SECONDS_IN_WAIT NUMBER
STATE VARCHAR2(19)
SERVICE_NAME VARCHAR2(64)
SQL_TRACE VARCHAR2(8)
SQL_TRACE_WAITS VARCHAR2(5)
SQL_TRACE_BINDS VARCHAR2(5)
03:20:10 orcl SQL >
03:20:11 orcl SQL >
03:20:11 orcl SQL >
Name Null? Type
----------------------------------------- -------- ----------------------------
USERNAME VARCHAR2(30)
MODULE VARCHAR2(48)
ACTION VARCHAR2(32)
CLIENT_INFO VARCHAR2(64)
CLIENT_IDENTIFIER VARCHAR2(64)
SELECT COUNT(USERNAME), USERNAME FROM v$session GROUP BY USERNAME;
The MODULE column is also useful. It is well suited for holding the
name of the RoR-controller associated with this session.
The ACTION column is well suited for holding the name of the current
method inside the RoR-controller associated with this session.
The CLIENT_INFO is well suited for holding any piece of information from the RoR-session.
For example the number of books in the shopping cart might be useful.
The CLIENT_IDENTIFIER is well suited for holding information about the
end-user such as login-name. If you collect login information into
CLIENT_IDENTIFIER, then you will be able to identify all the SQL for
that end user (since we can easily query information about all SQL
statements generated by a given Oracle session).
For example if that user is complaining that the site is slow, you
should be able to quickly identify the SQL statements consuming most
of the resources for that user. This is a powerful technique.
Most webmasters of PHP or JDBC connected sites lack the ability to do
this. Often they need to load log files into a table and then run SQL
against that table.
Another sub-optimal approach webmasters use is correlation based.
Consider this line of thinking based on correlation logic, "Last night
we deployed a new release of the site, today the site is slow.
Therefore we must have a performance bug in the new code resident
within the new release."
An optimal approach to performance tuning is based on end-to-end
tracing. This is possible if the webmaster knows which
controller-methods are tied to which Oracle sessions (which can then
lead to specific resource intensive SQL statements).
A Sqlplus Demonstration
Using sqlplus to create a session and then add data values to that session is easily demonstrated:
502 bash-03:48:38-dbiklelt:~ $ sqlplus app10/a
SQL*Plus: Release 10.2.0.1.0 - Production on Sun Apr 27 03:48:53 2008
Copyright (c) 1982, 2005, Oracle. All rights reserved.
Connected to:
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Production
With the Partitioning, OLAP and Data Mining options
03:49:34 orcl SQL > SELECT username , module from v$session WHERE username='APP10';
USERNAME MODULE
------------------------------ ------------------------------------------------
APP10 ruby.exe
APP10 SQL*Plus
03:51:26 orcl SQL > EXEC dbms_session.set_identifier(client_id => 'bikle@bikle.com')
PL/SQL procedure successfully completed.
03:51:37 orcl SQL > SET lines 222 pages 33
03:52:27 orcl SQL > SELECT username, module, client_identifier FROM v$session WHERE username='APP10';
USERNAME MODULE CLIENT_IDENTIFIER
------------------------------ ------------------------------------------------ ----------------------
APP10 ruby.exe
APP10 SQL*Plus bikle@bikle.com
03:52:52 orcl SQL > EXEC dbms_application_info.set_module(module_name => 'sqlplus!!!', action_name => 'fake action')
PL/SQL procedure successfully completed.
03:55:21 orcl SQL > SELECT username, module, action, client_identifier FROM v$session WHERE username='APP10';
USERNAME MODULE ACTION CLIENT_IDENTIFIER
------------------------------ ------------------------------------------------ -------------------------------- ----------------------
APP10 ruby.exe
APP10 sqlplus!!! fake action bikle@bikle.com
03:56:16 orcl SQL >
03:56:39 orcl SQL >
03:56:55 orcl SQL >
class C10Controller < ApplicationController
def m10
conn = ActiveRecord::Base.connection.raw_connection
proc = conn.parse("BEGIN dbms_session.set_identifier(client_id => 'bikle@bikle.com'); END;")
proc.exec
@m10 = "hello world"
end
end
xyz = ActiveRecord::Base.find_by_sql ["SELECT my_function(?) FROM dual", some_variable]
-- session_setter12.sql
-- Run via sqlplus user/pass @session_setter12.sql
CREATE OR REPLACE FUNCTION session_setter12(
client_id_in VARCHAR2
,client_info_in VARCHAR2
,module_name_in VARCHAR2
,action_name_in VARCHAR2
) RETURN NUMBER IS
BEGIN
dbms_session.set_identifier(client_id => client_id_in);
dbms_application_info.set_client_info(client_info => client_info_in);
dbms_application_info.set_module(module_name => module_name_in, action_name => action_name_in);
RETURN 1;
END;
/
SHOW ERRORS
SELECT session_setter12 ('sqlplus@bikle.com', 'sqlplus cart', 'sqlplus', 'nada') FROM dual;
1* SELECT username, module, action, client_info, client_identifier FROM v$session WHERE username='APP10'
04:22:39 orcl SQL > /
USERNAME MODULE ACTION CLIENT_INFO CLIENT_IDENTIFIER
----------- ------------- -------------------------------- ---------------------- ----------------------------------------------------------------
APP10 sqlplus nada sqlplus cart sqlplus@bikle.com
04:22:40 orcl SQL >
04:22:55 orcl SQL >
class C10Controller < ApplicationController
# Trigger login via this URL: http://bikle.com/c10/login
def login
# Put some data in the session
session[:login_name] = 'bikle@bikle.com'
session[:cart_state] = 'cart empty'
@login = "You are now logged in."
end
# Trigger m11 via this URL: http://bikle.com/c10/m11
def m11
themodule = self.class.to_s
theaction = self.action_name
theclient_info = session[:cart_state]
the_client_id = session[:login_name]
# Copy RoR session data to the corresponding Oracle session
xyz = ActiveRecord::Base.find_by_sql ["SELECT session_setter12(?,?,?,?) FROM dual" , the_client_id, theclient_info,themodule, theaction]
@m11 = "hello world"
end
end
04:58:16 orcl SQL > l
1* select username, module, action, client_info, client_identifier from v$session where username='APP10'
05:03:30 orcl SQL > /
USERNAME MODULE ACTION CLIENT_INFO CLIENT_IDENTIFIER
----------- ------------- -------------------------------- ----------- ------------------------------------
APP10 C10Controller m11 cart empty bikle@bikle.com
05:03:31 orcl SQL >
class C10Controller < ApplicationController
after_filter :copy_data2oracle_session
# Trigger login via this URL: http://bikle.com/c10/login
def login
session[:login_name] = 'bikle@bikle.com'
session[:cart_state] = 'cart empty'
@login = "You are now logged in."
end
# Trigger m11 via this URL: http://bikle.com/c10/m11
def m11
@m11 = "hello world"
end
private
# Send some data about the RoR session to the corresponding Oracle session
def copy_data2oracle_session
themodule = self.class.to_s
theaction = self.action_name
theclient_info = session[:cart_state]
the_client_id = session[:login_name]
xyz = ActiveRecord::Base.find_by_sql ["SELECT session_setter12(?,?,?,?) FROM dual" , the_client_id, theclient_info,themodule, theaction]
end
end
dbms_session.set_identifier
dbms_application_info.set_client_info
dbms_application_info.set_module
We may call the Oracle supplied packaged procedures directly via the
OCI8 API or we may call them using a custom built Oracle function
which is accessed by ActiveRecord::Base.find_by_sql