Триггеры в TimesTen (XLA application)

Иногда, заказчики задают вопрос относительно поддержки триггеров в TimesTen.
TimesTen поддерживает PL/SQL (процедуры, функции, пакеты и др.), но поддержка триггеров отсутствует, т.к. триггеры пагубно влияют на производительность. Но что делать, если необходимо реализовать триггерную логику?

Ответ - написать XLA приложение. Написать можно на С или Java, кому что ближе.
Ниже я буду описывать пример с использованием java :).

В документации (Java Developer's Guide) сказано:

"You can use the TimesTen JMS/XLA API (JMS/XLA) to monitor TimesTen for
changes to specified tables in a local data store and receive real-time notification of these changes. One of the purposes of JMS/XLA is to provide a high-performance, asynchronous alternative to triggers."

Т.е. вы можете написать java приложение, которое может использовать JMS/XLA API для получения сообщений (в асинхронном режиме) об изменениях в TimesTen. JMS/XLA использует JMS publish-subscribe interface для доступа к XLA изменениям. Подробнее про JMS можно почитать здесь (http://download.oracle.com/javaee/1.3/jms/tutorial).

Далее попробуем создать такое приложение.

Первоначально, создадим объекты в TimesTen.
[oracle@tt1 xla]$ ttisql dbxla

Copyright (c) 1996-2010, Oracle.  All rights reserved.
Type ? or "help" for help, type "exit" to quit ttIsql.

connect "DSN=dbxla";
Connection successful: DSN=dbxla;UID=oracle;DataStore=/u01/app/oracle/datastore/dbxla;DatabaseCharacterSet=WE8MSWIN1252;ConnectionCharacterSet=US7ASCII;DRIVER=/u01/app/oracle/product/11.2.1/TimesTen/tt1/lib/libtten.so;PermSize=32;TempSize=50;TypeMode=0;PLSQL=0;CacheGridEnable=0;
(Default setting AutoCommit=1)
Command> CREATE USER oratt IDENTIFIED BY oracle;

User created.

Command> grant create session, create table, XLA to oratt;
Command> connect "DSN=dbxla;UID=oratt;PWD=oracle;";
Connection successful: DSN=dbxla;UID=oratt;DataStore=/u01/app/oracle/datastore/dbxla;DatabaseCharacterSet=WE8MSWIN1252;ConnectionCharacterSet=US7ASCII;DRIVER=/u01/app/oracle/product/11.2.1/TimesTen/tt1/lib/libtten.so;PermSize=32;TempSize=50;TypeMode=0;PLSQL=0;CacheGridEnable=0;
(Default setting AutoCommit=1)
con1: Command> create table xlatest ( id   NUMBER NOT NULL PRIMARY KEY,
             >                        name VARCHAR2(100) );
con1: Command>

Теперь, создадим закладку (bookmark). XLA закладки используются для отметки позиции чтения в журналах транзакций. Данную закладку булем использовать для отслеживания изменений в таблице xlatest.
con1: Command> call ttXlaBookmarkCreate('bookmark');
con1: Command>

Далее, определим, изменения какой таблицы будем наблюдать. Для этого вызовем процедуру ttXlaSubscribe. В данном случае будем наблюдать за изменениями с таблицей xlatest с использованием закладки bookmark.
con1: Command> call ttXlaSubscribe('xlatest','bookmark');
con1: Command>

Далее, перейдем к настройке приложения.
Для соединения с XLA необходимо установить соединение с JMS Topic, который связан с опреденной базой данных TimesTen. Кофигурационный файл JMS/XLA обеспечивает привязку между именем топика и базой данных. По умолчанию, приложение ищет данный файл, названный jmsxla.xml, в текущей директории, но при желании можно определить другое имя и местоположение данного файла (см. документацию).

В данном случае я использую следующий файл jmsxla.xml:
<xlaconfig>
  <topics>

    <!-- topic for Xla demo -->
    <topic name="xlademo"
           connectionString="DSN=dbxla"
           xlaPrefetch="100"
    />

  </topics>
</xlaconfig>

Как видно, я связал имя топика xlademo с базой данных dbxla.

Теперь приступим, непосредственно, к написанию java Приложения.
Первоначально, инициализируем контекст.
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.timesten.dataserver.jmsxla.SimpleInitialContextFactory");
InitialContext ic = new InitialContext(env);

Далее, используем JMS connection factory для соединения с XLA. После чего, вызываем метод start() у соединения для активации отправки сообщений. После этого, используя данное соединение создаем сессию.
private javax.jms.TopicConnection connection; /** JMS connection */
private TopicSession session;  /** JMS session */
...
TopicConnectionFactory connectionFactory = (TopicConnectionFactory)ic.lookup("TopicConnectionFactory");
connection = connectionFactory.createTopicConnection();
...
// get Session
session = connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
...
Также при создании сессии необходимо указать транзакционность сессии и тип модели подтверждения (acknowledgment modes).
JMS/XLA поддерживается три модели (AUTO_ACKNOWLEDGE,DUPS_OK_ACKNOWLEDGE, CLIENT_ACKNOWLEDGE), подробнее про модели можно почитать в документации. В примере я использую первую модель и транзакционность сессии устанавливаю в значение false.

Далее, необходимо определиться с режимом получения сообщений. Возможны два варианта: синхронный и асинхронный.

В синхронном варианте, сообщения обрабатываются последовательно (одно за другим). Это означает, что пока сообщение не обработано, другое ожидает.
Для синхронного варинта - вызываем метод start() у соединения, для активации отправки сообщений, создаем Topic, затем создаем подписчика и получаем сообщения с помощью методов receive() и receiveNoWait().

connection.start();
Topic topic = session.createTopic(topicName);     
TopicSubscriber subscriber = session.createDurableSubscriber(topic, bookmark);                 
..
MapMessage message = (MapMessage)subscriber.receive(); 
...

В асинхронном режиме необходимо создать листенер и в нем обрабатывать сообщения.

MyListener myListener = new MyListener(outStream);

Topic xlaTopic = session.createTopic(topic);
TopicSubscriber subscriber = session.createDurableSubscriber(xlaTopic, bookmark);
..
subscriber.setMessageListener(myListener);
connection.start();
..

Ниже представлен пример класса, реализующего синхронный режим (файл DemoXLA.java).
import java.util.Enumeration;
import java.util.Hashtable;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Session;
import javax.jms.Topic;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicConnection;
import javax.jms.TopicSession;
import javax.jms.TopicSubscriber;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class DemoXLA {

    private TopicConnectionFactory connectionFactory;
    private TopicConnection connection;
    private TopicSession session;
    private Topic topic;
    private TopicSubscriber subscriber;

    public DemoXLA( String cf, 
                    String topicName, 
                    String selector) throws JMSException, NamingException {
        String key;
        Context messaging = getInitialContext(); // getting the context

 connectionFactory = (TopicConnectionFactory)messaging.lookup(cf);        
 connection = connectionFactory.createTopicConnection();
 connection.start();                        
 session = connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);                
        
 topic = session.createTopic(topicName);     
        subscriber = session.createDurableSubscriber(topic, selector);                 
        int i=0;
        while (i<10) {
            MapMessage message = (MapMessage)subscriber.receive();                        
            Enumeration e = message.getMapNames();

            while (e.hasMoreElements()) {
                key = (String)e.nextElement();
                System.out.println("[ " + key + " = " + message.getObject(key) + " ]");
            }
            System.out.println("----------------------------------------");                        
        }
        
        session.unsubscribe(selector);
        subscriber.close();
        session.close();
        connection.stop();
    }

    private Context getInitialContext() throws NamingException {
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.timesten.dataserver.jmsxla.SimpleInitialContextFactory");        
        InitialContext initialContext = new InitialContext(env);
        return initialContext;
    }

    public static void main(String[] args) throws JMSException, NamingException {
        DemoXLA demo = new DemoXLA("TopicConnectionFactory", "Level2Demo", "bookmark");
    }
}
Ниже представлен пример классов, реализующих асинхронный режим (MyListener.java, DemoXLA2.java). MyListener.java
import java.util.Enumeration;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageListener;


public class MyListener implements MessageListener   {
    public MyListener() {}

    public void onMessage(Message message) {
        MapMessage mp = (MapMessage)message;
        Enumeration e;
        try {
            e = mp.getMapNames();
        } catch (JMSException s) {
            e = null;
            System.out.println("error 1");
        }
        while (e.hasMoreElements()) {
            String key = (String)e.nextElement();
            try {
                System.out.println("[ " + key + " = " + mp.getObject(key) + " ]");
            } catch (JMSException f) {
                System.out.println("error 2");
            }
        }
        System.out.println("----------------------------------------");
    }
}
DemoXLA2.java
import java.util.Hashtable;
import javax.jms.JMSException;
import javax.jms.Session;
import javax.jms.Topic;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicSession;
import javax.jms.TopicSubscriber;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class DemoXLA2 {

    private javax.jms.TopicConnectionFactory connectionFactory;
    private javax.jms.TopicConnection connection;
    private TopicSession session;
    private Topic topic;
    private TopicSubscriber subscriber;

    public DemoXLA2( String cf,
                    String topicName,
                    String selector) throws JMSException, NamingException, InterruptedException {

        Context messaging = getInitialContext();
        Object connectionFactoryObject = messaging.lookup(cf);
        connectionFactory = (TopicConnectionFactory)connectionFactoryObject;
        connection = connectionFactory.createTopicConnection();

        MyListener myListener = new MyListener();
        session = connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
        topic = session.createTopic(topicName);
        subscriber = session.createDurableSubscriber(topic, selector); 
 
subscriber.setMessageListener(myListener);
        connection.start();
        Thread.sleep(60000);

        session.unsubscribe(selector);
        subscriber.close();
        session.close();
        connection.stop();
    }

    private Context getInitialContext() throws NamingException {
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.timesten.dataserver.jmsxla.SimpleInitialContextFactory");
        InitialContext initialContext = new InitialContext(env);
        return initialContext;
    }


    public static void main(String[] args) throws JMSException,
                                                  NamingException,
                                                  InterruptedException {
        DemoXLA2 demo = new DemoXLA2("TopicConnectionFactory", "xlademo", "bookmark");

    }

}
Далее, запускаем любой из примеров и попытаемся внести, изменить и удалить данные из таблицы xlatest.
Command> insert into xlatest values (2, 'w');
1 row inserted.
Command> update xlatest set name = 'test' where id=2;
1 row updated.
Command> delete from xlatest;
1 row deleted.
Command>
Соответственно, в приложении получаем:
[ __TYPE = 10 ]
[ __COMMIT = true ]
[ __FIRST = true ]
[ __NULLS =  ]
[ __TBLNAME = XLATEST ]
[ __TBLOWNER = ORATT ]
[ __mver = 5621355056449191939 ]
[ __mtyp = null ]
[ ID = 2 ]
[ NAME = w ]
----------------------------------------
[ __TYPE = 11 ]
[ __COMMIT = true ]
[ __FIRST = true ]
[ __UPDCOLS = NAME ]
[ __NULLS =  ]
[ __TBLNAME = XLATEST ]
[ __TBLOWNER = ORATT ]
[ __mver = 5621355056449191942 ]
[ __mtyp = null ]
[ _ID = 2 ]
[ ID = 2 ]
[ _NAME = w ]
[ NAME = test ]
----------------------------------------
[ __TYPE = 12 ]
[ __COMMIT = true ]
[ __FIRST = true ]
[ __NULLS =  ]
[ __TBLNAME = XLATEST ]
[ __TBLOWNER = ORATT ]
[ __mver = 5621355056449191945 ]
[ __mtyp = D ]
[ ID = 2 ]
[ NAME = test ]
----------------------------------------

Как видно, мы получили сообщения о прошедших с таблицей xlatest операциях.
Сообщения имеют следующий формат:
Системные атрибуты начинаются с двойного подчеркивания, например:
__TYPE - тип операции (Insert (10), Update(11), Delete(12)) также возможны другие типы (см. документацию). Для определения типа операции присутствуют константы.
__COMMIT - сигнализирует об завершении транзакции (если true).
__FIRST - сигнализирует о первой операции в транзакции (если true).
__TBLNAME - имя таблицы
__TBLOWNER - владелец таблицы
__NULLS - сигнализирует об атрибутах, в которых содержется значение null
__mver и __mtyp - системные атрибуты.
и т.д. (см. документацию)
Атрибуты без подчеркивания - колонки таблиц, имеющие определенные значения, например:
[ ID = 2 ]
[ NAME = test ]
Атрибуты начинающиеся на одно подчеркивание - старые значения полей (появляются при операции Update), например:
[ _ID = 2 ]
[ ID = 2 ]
[ _NAME = w ]
[ NAME = test ]

Итог

Следовательно, имея достаточно поверхностные знания по java, можно написать XLA приложение, которое может обрабатывать различные сообщения, полученнные из TimesTen. Кроме того, XLA приложение работает в асинхронном режиме, что практически не влияет на производительность Oracle TimesTen.

Доступ ко всем данным из TimesTen (Passthrough=1)

Недавно ко мне обратился заказчик с задачей:
существуют "огромные партиционированные таблицы (терабайты данных), к части из которых идет частое обращение. Возможно ли чтобы из ТТ были доступны определенные партиции, а остальные брались из оракла по тому же подключению ТТ".

Попробуем решить данную задачу.

Предположим у насть есть партицированная таблица в Oracle Database.

[oracle@tt1 cache_article]$ sqlplus oratt/oracle@orcl

SQL*Plus: Release 11.1.0.7.0 - Production on Tue May 24 09:22:15 2011

Copyright (c) 1982, 2008, Oracle.  All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.2.0 - Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options

SQL> create table accounts (
  2  Id number,
  3  Name varchar2 (50),
  4  Surname varchar2(50),
  5  constraint accounts_pk primary key (id)
  6      using index (create unique index account_pk_idx on accounts(id))
  7  )
  8  partition by range (id) (
  9  partition part1 values less than (1000),
 10  partition part2 values less than (2000),
 11  partition part3 values less than (3000)
 12  );

Table created.

SQL> begin
2  for i in 1 .. 100 loop
3    insert into accounts values (i, 'Pit'||i, 'Jounes'||i);
4  end loop;
5 end;
6/
  
PL/SQL procedure successfully completed.

SQL> begin
2  for i in 1001 .. 1100 loop
3    insert into accounts values (i, 'Pit'||i, 'Jounes'||i);
4  end loop;
5 end;
6/
  
PL/SQL procedure successfully completed.

SQL> begin
2  for i in 2001 .. 2100 loop
3    insert into accounts values (i, 'Pit'||i, 'Jounes'||i);
4  end loop;
5 end;
6/
  
PL/SQL procedure successfully completed.

SQL> select count(*) from accounts;

  COUNT(*)
----------
       300

SQL> grant select on accounts to cacheadmin;

Grant succeeded.


Предположим, что частое обращение происходит к первой партиции, т.е. будем кэшировать первую партицию (ограничим ее условием where в определении кэш группы).

Command> CREATE READONLY CACHE GROUP read_cg
       >   AUTOREFRESH INTERVAL 5 SECONDS
       > FROM oratt.accounts (   Id number not null primary key,
       >                       Name varchar2 (50),
       >                    Surname varchar2(50)
       > ) where id <= 999;
Command> load cache group read_cg commit every 265 rows;
100 cache instances affected.


Но как получить данные из других партиций?
Для этого будем использовать параметр Passthrough.
Если данный параметр имеет значение 1, то каждый запрос, выполненный к несуществующим обектам в TimesTen будет перенаправлен в Oracle Database для исполнения.

Следовательно, создаем представление в Oracle Database, которое будет содержать данные из остальных партиций.

SQL> create view accounts_all as select * from accounts where id > 999;

View created.

SQL> grant select on accounts_all to cacheadmin;

Grant succeeded.


После чего можем иметь доступ ко всей информации через одно подключение (но, к сожалению, через разные таблицы).

Command> connect "DSN=db_cache1;UID=oratt;PWD=oracle;";
Connection successful: DSN=db_cache1;UID=oratt;DataStore=/u01/app/oracle/datastore/db_cache1;DatabaseCharacterSet=WE8MSWIN1252;ConnectionCharacterSet=US7ASCII;DRIVER=/u01/app/oracle/product/11.2.1/TimesTen/tt1/lib/libtten.so;PermSize=32;TempSize=50;TypeMode=0;PLSQL_TIMEOUT=1000;CacheGridEnable=0;OracleNetServiceName=ORCL;
(Default setting AutoCommit=1)
Command> select count(*) from accounts;
< 100 >
1 row found.
Command> select count(*) from accounts_all;
 2206: Table ORATT.ACCOUNTS_ALL not found
The command failed.
Command> set autocommit 0;
Command> call ttOptSetFlag('PassThrough', 1);
Command> select count(*) from accounts_all;
< 200 >
1 row found.
Command>
Command> set showplan 1;
Command> select count(*) from accounts;

Query Optimizer Plan:

  STEP:                1
  LEVEL:               1
  OPERATION:           TblLkSerialScan
  TBLNAME:             ACCOUNTS
  IXNAME:              
  INDEXED CONDITION:   
  NOT INDEXED:         

< 100 >
1 row found.
Command>  select count(*) from accounts_all;

Query Optimizer Plan:

  STEP:                1
  LEVEL:               1
  OPERATION:           Oracle PassThrough
  TBLNAME:             
  IXNAME:              
  INDEXED CONDITION:   
  NOT INDEXED:         

< 200 >
1 row found.
Command>


Следовательно, получили доступ ко всей таблице Accounts через одно подключение в Oracle TimesTen.

Igor Melnikov's Blog!!!

I wrote about Russian Exadata-man - Igor Melnikov! 
And now he is starting the technical blog!
Welcome!!!

In-Memory Database Cache Grid

Ну вот я наконец то добрался до рассмотрения появившегося в TimesTen 11g функционала - In-Memory Database Cache Grid.
Начнем с настройки. В документации процесс создания Cache grid на нескольких машинах описан, на мой взгляд, не досточно четко, поэтому я и решил начать с его конфигурации.

Используемое окружение (три виртуальные машины):

OEL 5.3 (x86) - Oracle Database EE 11.2.0.2.0 – 192.168.2.131 (hostname - db)
OEL 5.3 (x86) - Oracle TimesTen 11.2.1.7.0 (32 bit Linux/x86) – 192.168.2.132 (hostname – tt1)
OEL 5.3 (x86) - Oracle TimesTen 11.2.1.7.0 (32 bit Linux/x86) – 192.168.2.133 (hostname – tt2)

Предварительная настройка для кэш грида, в общем, очень похожа на настройку TimesTen для работы с Oracle Database (об этом можно почитать здесь).


Tech: Настройка In-Memory Database Cache 11g

Давно я что-то не писал не чего технического (да и на русском :) ), вот и решил это исправить. Так как литературы по TimesTen мало, то я решил обновить статью Jonathan Gennick -а о кэшировании. В данном посте рассматривается возможность кэширования данных из Oracle Database в Oracle TimesTen.

Примечание: в данной статье не будет рассматриваться функциональность In-Memory Database Cache Grid, т.е. будет рассмотрена возможность создания только локальных кэш групп.

Используемое окружение (две виртуальные машины):
OEL 5.3 (x86) - Oracle Database EE 11.2.0.2.0 (Linux/x86), ip - 192.168.2.131 (hostname - db)
OEL 5.3 (x86) - Oracle TimesTen 11.2.1.7.0 (Linux/x86), ip - 192.168.2.132 (hostname - tt1)






Russian Exadata-man!

Update 1. The Igor Melnikov's presentation about loading data in Exadata, you can download here

On foto, my colleague Igor Melnikov. He is testing some application on Oracle Exadata V2-8 in Edinburgh now, and i think he is one of the first people in Russia, who can looking on this machine in real life :))

PS. Igor developed a very good method of loading a lot of data in Exadata (tens of terabytes) and tested it in Edinburgh. He promised, that he posted the presentation about it in blog.

Oracle TimesTen 11.2.1.8.0 is now available in OTN

New version Oracle TimesTen is now available on OTN.

I have written about "feature" Oracle TimesTen in Active-Standby pair configuration. In previous versions, if you wanted to add some abject (table, index and etc/) in your Active-Standby pair replication you had to restart your replication (stop, add your tables and start). It it not convenient, isn't it?
In 11.2.1.8.0 version you can create or drop a table, index or synonym in an active standby pair without stopping the replication agent. You can choose to have these statements replicated to the standby database. You can include a table in an active standby pair when the table is created. Use the DDLReplicationLevel and DDLReplicationAction connection attributes to control what is replicated.

More information you can find in documentation

PS. Foto - Santorini, Greece. I like this island :)).