Donnerstag, 6. November 2014

Performance beim Erzeugen eines Oracle Text Index

Häufig erhalten wir die Frage, ob und wie sich die Performance beim Erzeugen eines Oracle Text Index beeinflussen lässt. Die Frage taucht in der Regel dann auf, wenn dem Anwender das Indexanlegen zu lange dauert. Eine Überprüfung der Oracle Text Memory Einstellung ist eine erste einfache Massnahme bei dieser Fragestellung. Setzt man nämlich einen Oracle Text Index ohne zusätzliche Memory Parameter Einstellung ab, wird die Default Einstellung, die häufig zu klein ist, genommen.

Wie kann man nun die Default Einstellungen einsehen? Eine einfache Abfrage auf die Tabelle CTX_PARAMETERS zeigt die Einstellungen für den Oracle Text Index.
col PAR_NAME format a40
col PAR_VALUE format a30
select * from ctx_parameters where PAR_NAME like '%MEMORY%';

PAR_NAME                                 PAR_VALUE
---------------------------------------- ------------------------------
DEFAULT_INDEX_MEMORY                     67108864
MAX_INDEX_MEMORY                         274877906944
Die Defaulteinstellung DEFAULT_INDEX_MEMORY liegt also bei 64 MB. Das bedeutet, beim CREATE INDEX werden normalerweise 64 MB verwendet. Der zweite Parameter gibt die maximale Einstellung an, die beim Indexanlegen überhaupt genutzt werden können. Hier - in einer 12.1.0.2 Umgebung - ist der Wert des Parameters 256 GB.

Es gibt nun zwei Möglichkeiten, die Memoryeinstellung zu ändern. Entweder während der Laufzeit über das CREATE INDEX Statement oder über die Änderung der Gesamteinstellung mit dem Package CTX_ADM. Folgendes Beispiel zeigt die Nutzung beim Indexanlegen.
create index my_idx on customers(CUST_STREET_ADDRESS) 
indextype is ctxsys.context parameters ('memory 100M');
Folgendes Beispiel zeigt die Verwendung von CTX_ADM.
begin
ctxsys.ctx_adm.set_parameter('MAX_INDEX_MEMORY','100G');
end;
/

begin
ctxsys.ctx_adm.set_parameter('DEFAULT_INDEX_MEMORY','512M');
end;
/
Ziel sollte sein, das Index Memory so hoch wie möglich zu setzen - allerdings unter Vermeidung von Paging. Einerseits kann mit dieser Massnahme das Indexanlegen beschleunigt werden, andererseits wird dabei auch die Fragmentierung des Index geringer.

Wichtiger Hinweis: Die maximale Einstellung für MAX_INDEX_MEMORY hat sich übrigens von 11g nach 12c verändert. In 11g lag der Wert noch bei 2 GB, in 12c ist dieser Wert auf 256 GB erhöht worden. Dies wird übrigens auch in den Defaulteinstellung sichtbar (siehe obige Abfrage auf CTX_PARAMETERS). Beachten Sie auch: 256 GB ist eine theoretische Grenze, die tatsächliche Grenze von MAX_INDEX_MEMORY könnte je nach Umgebung niedriger ausfallen.

Dienstag, 23. September 2014

Ergebniscache für TEXT-Abfragen: Oracle12c TEXT Query Filter Cache

Heute geht es um den mit Oracle12c neu eingeführten Query Filter Cache. Das ist ein Ergebnis-Cache, speziell für Oracle TEXT Abfragen. Ergebnisse von Oracle TEXT-Abfragen werden - ganz ähnlich zum "normalen" SQL Result Cache - in einem separaten Hauptspeicherbereich abgelegt bei Bedarf wiederverwendet. Kann ein Ergebnis aus dem Cache geholt werden, muss die eigentliche Oracle TEXT Abfrage nicht mehr ausgeführt werden; die Antwortzeit sollte dann wesentlich besser sein.
Gut geeignet ist der Query Filter Cache zum nachträglichen Filtern von Abfrageergebnissen. Dabei kann es um das Anwenden von Zugriffsregeln gehen (Sicherheit); aber auch Anwendungen, die die nachträgliche An- und Abwahl von Dokumentkategorien erlauben, können so durch einen Ergebniscache unterstützt werden. Denn jede An- oder Abwahl einer Kategorie (bspw. durch Setzen einer Checkbox in der Anwendung) führt ja zu einer erneuten Ausführung der Query. Nutzt man den Query Filter Cache, so müssen die Ergebnisse nur noch aus dem Cache geholt und nachgefiltert werden. Das folgende Beispiel stellt den Query Filter Cache vor - los geht's wie immer mit dem Erstellen einer Tabelle.
create table texttabelle(
  id          number(10),
  ressort     varchar2(20),
  dokument    clob
)
/

insert into texttabelle values ( 1, 'Politik',    'A-Partei gewinnt Wahl in Hansestadt');
insert into texttabelle values ( 2, 'Panorama',   'Terror in Nahost: Kriminalität steigt immer weiter an');
insert into texttabelle values ( 3, 'Wirtschaft', 'Wirtschaft: Erneuter Gewinnzuwachs in diesem Jahr');
insert into texttabelle values ( 4, 'Sport',      'Olympia rückt näher: Der Fackellauf ist in vollem Gange');
insert into texttabelle values ( 5, 'Politik',    'Wer wird US-Präsident? Obama und Clinton machen Wahlkampf');
insert into texttabelle values ( 6, 'Politik',    'Papst bestürzt über jüngsten Skandal!');
insert into texttabelle values ( 7, 'USA',        'Wahlkampf in den USA: Clinton und Obama LIVE zu sehen');
insert into texttabelle values ( 8, 'Wirtschaft', 'Software-Kenntnisse werden immer wichtiger');
insert into texttabelle values ( 9, 'Wirtschaft', 'Umfrage:  Alle wollen mehr Geld!');
insert into texttabelle values (10, 'Panorama',   'Der Papst liest seine erste Messe in den USA!');

commit
/
In der Spalte DOKUMENT soll gesucht werden, anhand der Spalte RESSORT soll dann nachträglich gefiltert werden. Um den Query Filter Cache nutzen zu können, muss eine explizite Storage Preference erstellt und im Index genutzt werden. Dabei wird die Größe des Cache mit dem Attribut QUERY_FILTER_CACHE_SIZE in Bytes angegegen. Dieser Speicherbereich wird dann in der SGA allokiert - die Größe sollte also mit Bedacht - und nach Absprache mit dem DBA bestimmt werden.
begin
 ctx_ddl.create_preference('my_idx_storage', 'basic_storage');
 ctx_ddl.set_attribute('my_idx_storage', 'query_filter_cache_size', '10M');
end;
/  
 

create index idx_text on texttabelle (dokument)
indextype is ctxsys.context
filter by ressort
parameters ('storage my_idx_storage sync (on commit)')
/
Die Data Dictionary View CTX_FILTER_CACHE_STATISTICS gibt Informationen über den Cache - Zu Beginn stehen alle Einträge natürlich auf Null.
SQL> select * from ctx_filter_cache_statistics

FCS_INDEX_OWNER FCS_INDEX_NAME FCS_PARTITION_NAME   FCS_SIZE FCS_ENTRIES FCS_REQUESTS   FCS_HITS
--------------- -------------- ------------------ ---------- ----------- ------------ ----------
TEXT            IDX_TEXT                                   0           0            0          0
Nun geht es daran, den neuen Cache zu nutzen. Das geschieht zunächst, ganz normal, mit Hilfe des Oracle TEXT SQL-Operators CONTAINS. In diesem wird nun aber die spezielle Abfragefunktion CTXFILTERCACHE verwendet - und zwar wie folgt.
select * from texttabelle 
where contains(dokument, 'ctxfiltercache((Wirtschaft), true, true)') > 0;

        ID RESSORT              DOKUMENT
---------- -------------------- ---------------------------------------------------------------------------
         3 Wirtschaft           Wirtschaft: Erneuter Gewinnzuwachs in diesem Jahr
Der neue Cache wird also mit dem Schlüsselwort CTXFILTERCACHE explizit angesprochen. Der erste Parameter (Wirtschaft) ist die Text-Query, die anderen beiden bestimmen das Verhalten des Cache näher. Der zweite Parameter (true) legt fest, ob der SCORE-Wert eines Ergebnisses ebenfalls im Cache abgelegt werden soll. Standardmäßig steht dieser Parameter auf FALSE: Ein aus dem Cache geholtes Ergebnis hat dann einen Score von 100. Wird der Parameter, wie oben, auf TRUE gesetzt, werden die SCORE-Werte ebenfalls in den Cache gelegt und wiederverwendet. Der dritte Parameter bestimmt, ob nur die "TOP-N"-Ergebnisse in den Cache gelegt werden sollen - wieviele das sind, bestimmt die Datenbank automatisch; eine manuelle Festlegung ist nicht möglich. Um TOP-N auf TRUE zu setzen, muss auch der zweite SCORE-Parameter auf TRUE gesetzt sein. Die Dokumentation enthält nähere Details.
Nach der ersten Ausführung der Query kann man anhand der Statistik-View schon erkennen, dass mit dem Cache gearbeitet wurde.
SQL> select * from ctx_filter_cache_statistics

FCS_INDEX_OWNER FCS_INDEX_NAME FCS_PARTITION_NAME   FCS_SIZE FCS_ENTRIES FCS_REQUESTS   FCS_HITS
--------------- -------------- ------------------ ---------- ----------- ------------ ----------
TEXT            IDX_TEXT                               13096           1            1          0
Anhand von FCS_ENTRIES sieht man, dass nun eine Abfrage mit dem Cache gearbeitet hat. Es erfolgte eine Anfrage an den Cache (FCS_REQUESTS), aber da die Abfrage zum ersten Mal abgesetzt wurde, ergab sich noch kein Hit (FCS_HITS). Anders sieht es aus, wenn man die Abfrage nochmals absetzt: Nun ergeben sich zwei Anfragen und ein Cache-Hit. Die Abfrage wurde also aus dem Cache bedient.
SQL> select * from ctx_filter_cache_statistics

FCS_INDEX_OWNER FCS_INDEX_NAME FCS_PARTITION_NAME   FCS_SIZE FCS_ENTRIES FCS_REQUESTS   FCS_HITS
--------------- -------------- ------------------ ---------- ----------- ------------ ----------
TEXT            IDX_TEXT                               13096           1            2          1
Nun zu einem der eigentlichen Anwendungsfälle. Wir suchen in der oberen Tabelle nach Obama.
SQL> select * from texttabelle where contains(dokument,  'ctxfiltercache((Obama), true, true)') > 0 ;

        ID RESSORT              DOKUMENT
---------- -------------------- ----------------------------------------------------------------------------
         5 Politik              Wer wird US-PrSsident? Obama und Clinton machen Wahlkampf
         7 USA                  Wahlkampf in den USA: Clinton und Obama LIVE zu sehen

2 Zeilen ausgewählt.

SQL> select * from ctx_filter_cache_statistics;

FCS_INDEX_OWNER FCS_INDEX_NAME FCS_PARTITION_NAME   FCS_SIZE FCS_ENTRIES FCS_REQUESTS   FCS_HITS
--------------- -------------- ------------------ ---------- ----------- ------------ ----------
TEXT            IDX_TEXT                               13096           1            1          0

1 Zeile wurde ausgewählt.
Diese Abfrage liefert Ergebnisse aus zwei Ressorts. Wieder haben wir einen Request an den Cache und (noch) keinen Hit. Nun nehmen wir aber an, dass der Anwender in der Benutzeroberfläche in der Checkbox für die Ressorts klickt - und zwar wählt er das Ressort "USA" ab. Das führt zu einer neuen Query, bei der ein SDATA-Ausdruck anstelle einer AND-Verknüpfung auf SQL-Ebene verwendet wird. Dies dient der Vermeidung von Mixed Queries - was bei der Arbeit mit Oracle TEXT generell zu empfehlen ist (siehe Blog Posting Abfrage-Optimierung mit Composite Domain Index).
select * from texttabelle where contains(
  dokument,  
  'ctxfiltercache((Obama), true, true) and sdata(ressort = ''Politik'')'
) > 0;

        ID RESSORT              DOKUMENT
---------- -------------------- --------------------------------------------------------------------------------
         5 Politik              Wer wird US-PrSsident? Obama und Clinton machen Wahlkampf

1 Zeile wurde ausgewählt.

SQL> select * from ctx_filter_cache_statistics;

FCS_INDEX_OWNER FCS_INDEX_NAME FCS_PARTITION_NAME   FCS_SIZE FCS_ENTRIES FCS_REQUESTS   FCS_HITS
--------------- -------------- ------------------ ---------- ----------- ------------ ----------
TEXT            IDX_TEXT                               13096           1            2          1

1 Zeile wurde ausgewählt.
Obgleich die Gesamt-Abfrage eine andere war, wurde der Cache angefragt und genutzt. Der Query Filter Cache ist also eine gute Unterstützung für das nachträgliche Filtern von Oracle TEXT Abfrageergebnissen, wie es in vielen Anwendungen mittlerweile Standard ist.
Der Filter Cache muss in der Anwendung jedoch explizit angesprochen werden; eine automatische Nutzung für existierende Anwendungen ist nicht möglich - dazu sind die möglichen Seiteneffekte, speziell beim Score, einfach zu wesentlich. Solange also CTXFILTERCACHE nicht innerhalb CONTAINS verwendet wird, wird auch kein Cache genutzt. Als Entwickler muss man eine explizite Entscheidung für die Nutzung des Cache treffen und dies die CONTAINS-Abfragen entsprechend kodieren.

Freitag, 18. Juli 2014

FILE_DATASTORE und Security oder: Was ist die FILE_ACCESS_ROLE?

Dass Oracle TEXT einen FILE_DATASTORE anbietet, mit dem man Dateien im lokalen Dateisystem des Datenbankservers indizieren kann, ist den meisten bekannt. Dazu braucht es (wie immer) eine Tabelle, in der sich nun aber keine Dokumente, sondern Dokumentpfade befinden, wie folgendes Beispiel zeigt.
create table index_files (
  id number(10), 
  path varchar2(500)
)
/

insert into index_files (id, path) values (1, '/home/oracle/twp_apex_best_practices.pdf')
/

commit
/
Wenn nun ein Textindex auf die Spalte PATH erzeugt wird, ist es klar, dass nicht die Pfade selbst, sondern die Dateien, die sich hinter dem Pfad befinden, indiziert werden sollen. Das wird mit der Datastore-Preference FILE_DATASTORE bestimmt. Ein erster Versuch wird allerdings mit einer Fehlermeldung quittiert.
create index ft_files on index_files (path)
indextype is ctxsys.context
parameters (
'filter ctxsys.auto_filter 
 datastore ctxsys.file_datastore
')
/

create index ft_files on index_files (path)
*
FEHLER in Zeile 1:
ORA-29855: Fehler bei Ausführung der Routine ODCIINDEXCREATE
ORA-20000: Oracle Text error:
DRG-10758: index owner does not have the privilege to use file or URL datastore
ORA-06512: in "CTXSYS.DRUE", Zeile 160
ORA-06512: in "CTXSYS.TEXTINDEXMETHODS", Zeile 366
Diejenigen, die Oracle TEXT schon länger kennen, dürften sich nun wundern; denn "früher" wäre dieses CREATE INDEX-Kommando fehlerfrei durchgelaufen. Nun fehlt ein Privileg - und das ist auch gut so - denn über den Umweg des Textindex ließen sich ja ansonsten große Teile des Filesystems auf dem Datenbankserver auslesen. Also wurde hier ein zusätzlicher Schutz eingeführt. Der Eigentümer des Oracle-Text-Index braucht eine zusätzliche Rolle. Der Name dieser Rolle ist vom Oracle TEXT Development allerdings flexibel gehalten worden (und genau das führt oft zu Konfusion). Im Oracle TEXT Dictionary wird eine Datenbankrolle als sog. FILE_ACCESS_ROLE registriert. Diese Rolle muss der Eigentümer des Textindex dann zugewiesen bekommen.
  • Also legen wir zunächst eine Rolle an, welche die FILE_ACCESS_ROLE sein soll. Das geschieht als DBA.
    SYS@[sccloud033:1521/orcl]
    SQL> create role text_dateizugriff not identified;
    
    Rolle wurde erstellt.
    
  • Dann legen wir diese Rolle (hier also TEXT_DATEIZUGRIFF) als die FILE_ACCESS_ROLE für Oracle TEXT fest.
    SYS@[sccloud033:1521/orcl]
    SQL> begin
      2    ctxsys.ctx_adm.0set_parameter ('FILE_ACCESS_ROLE', 'TEXT_DATEIZUGRIFF');
      3  end;
      4  /
    
    PL/SQL-Prozedur erfolgreich abgeschlossen.
    
  • Schließlich wird die Rolle TEXT_DATEIZUGRIFF dem Datenbankschema zugewiesen, welches den Textindex erzeugen möchte, also SCOTT oder wie hier: TESTIT.
    SYS@[sccloud033:1521/orcl]
    SQL> grant text_dateizugriff to testit;
    
    Benutzerzugriff (Grant) wurde erteilt.
    
Versucht der User TESTIT nun nochmals, den Index mit CREATE INDEX zu bauen, so wird er nun erfolgreich sein.
SQL> create index ft_files on index_files (path)
  2  indextype is ctxsys.context
  3  parameters (
  4  'filter ctxsys.auto_filter
  5   datastore ctxsys.file_datastore
  6  ')
  7  /

Index wurde erstellt.

SQL> select * from index_files where contains(path, 'application express') > 0
/

        ID PATH
---------- --------------------------------------------------
         1 /home/oracle/twp_apex_best_practices.pdf
Also alles ganz einfach. Zusammengefasst kann man sagen:
  • Möchte man mit Oracle TEXT den FILE_DATASTORE verwenden, um Inhalte des lokalen Dateisystems zu indizieren, so braucht der Datenbankuser eine zusätzliche Rolle.
  • Diese Rolle heißt nicht FILE_ACCESS_ROLE - vielmehr muss man (als DBA) eine eigene Rolle (mit eigenem Namen) erzeugen, diese als FILE_ACCESS_ROLE im Oracle TEXT Dictionary registrieren und dem Datenbankschema zuweisen.
Das ist auch in der Oracle TEXT Dokumentation - im Abschnitt FILE_DATASTORE and Security beschrieben.

Montag, 12. Mai 2014

11.2.0.4 und Oracle Text - das sollte man wissen!

Nach einem Upgrade auf 11.2.0.4 können nach einem DELETE auf Tabellen mit Oracle Text Index Fehler wie folgt eintreten:
ERROR at line 1: 
ORA-00604: error occurred at recursive SQL level 1 
ORA-06550: line 1, column 7: 
PLS-00306: wrong number or types of arguments in call to 'SYNCRN' 
ORA-06550: line 1, column 7: 

PL/SQL: Statement ignored

Die genutzte Tabelle und die zugehörigen Text Indizes sind dabei vor dem Upgrade angelegt worden.

Was ist passiert?
Die Analyse des zugehörigen Bug 17501296 gibt als Ursache das Fehlen des Primary Keys der $R Tabelle an.

Was kann man tun?
Eine ausführliche Antwort hierzu liefert My Oracle Support Note 1608029.1 (Oracle Text Release 11.2.0.4.0 mandatory Patches). Hier wird das notwendige Vorgehen beschrieben. Kurz zusammengefasst handelt es sich dabei im Wesentlichen um 2 Dinge: 
  1. Die Ausführung von Patch 17501296 korrigiert das fehlerhafte Upgrade und Downgrade Script, so dass nachfolgende Upgrades erfolgreich sind. 
  2. Ein zusätzliches SQL Skript korrgiert die schon existierenden und migrierten Tabellen mit Text Indizes, falls die Constraints für die $R Tabellen nicht existieren.
Und fertig!

Beliebte Postings