Rambling about me

Oramai ho le idee molto confuse, per questo motivo il titolo di questo post è in inglese contrariamente alle linee guida che ho cercato di seguire dall’inizio, oltre quindici anni fa e per molto tempo. Negli ultimi ho avuto meno opportunità di lavorare strettamente su database Oracle ed ho fatto varie altre cose nell’ambito sempre del mio lavoro, sempre nella stessa azienda che ha continuato a crescere. Questa mattina riflettevo e ho deciso di buttare qui alcune riflessioni sparse, nella speranza di fare ordine nella mia testa e capire in che direzione mi sto muovendo. La verità è che sono un naufrago su un canotto in alto mare trasportato dalla corrente e sballottato dalle onde. Ho visto tante cose, anche interessanti ma tutte della serie “impara l’arte e mettila da parte”. Ho seguito con entusiasmo diversi progetti su cui ho lavorato con cose nuove, docker, kubernetes, openshift, jenkins, bamboo, PostgreSQL, Redis… Tutte cose che però poi sono rimaste li e che ho dovuto mettere da parte. Voglio dire, la competenza più grossa e appassionante che ho sviluppato negli ultimi vent’anni è senz’altro quella su RDBMS Oracle su cui ho avuto modo di lavorare tanto, dare un buon contributo alla mia azienda e su cui posso ancora lavorare e dire di avere una discreta competenza.

Ora si parla molto di nuove tecnologie di modernizzazione di innovazione ecc ecc. E’ vero che negli ultimi anni sono emerse tante nuove tecnologie anche in ambito database, si va dai mitici NoSQL ai distributed SQL. Io faccio fatica veramente a stare dietro a tutte queste cose nuove che portano senz’altro cose interessanti. Da conservatore quale sono rimango convinto della superiorità dei RDBMS SQL, pur ovviamente in determinati ambiti. Pare incredibile ma ancora adesso, dopo anni, sembra essere viva la disputa fra SQL e NoSQL, trovo nell’informazione diffusa tramite i social ancora molto “rumore” al riguardo. Si continua a contrapporre due cose che in realtà possono essere complementari o comunque ciascuna adatta ad ambiti diversi. Vi sono sicuramente punti di sovrapposizione, situazioni in cui usare l’uno o l’altro sia se non equivalente almeno possibile. Poi si può fare tutto ma a me sembra sono una questione di preferenze e non scientifica per cui una soluzione è migliore dell’altra.

Negli ultimi tempi per me le cose sono ancora più confuse perché ho riflettuto più volte su questi concetti negli ultimi anni, ho cercato di informarmi e capire e le mie conclusioni non sono ancora cambiate, sarà a causa della mia testardaggine ma io penso sia soprattutto perché non ho visto elementi concreti che potessero farmi cambiare idea.

Mi trovo quindi a disagio perché non riesco a approfondire qualcosa di specifico e non riesco a ampliare in modo soddisfacente le mie competenze e quindi non capisco in che direzione mi sto muovendo e dove sarò da qui a un anno. Uno degli elementi che conferma questa mia situazione è il fatto che scriva qui molto poco, a parte la mancanza di tempo non riesco a dedicare abbastanza tempo a un argomento da conoscerlo bene e aver voglia di scriverne che subito si passa ad altro. Sta andando avanti così da talmente tanto tempo che sono sempre meno fiducioso di arrivare da qualche parte, come questo post che alla fin fine dice poco.

AWS RDS e S3

Il titolo di questo post è senz’altro bizzarro, sono tre sigle che a molti potrebbero dire ben poco. AWS sta per Amazon Web Services, si tratta della piattaforma dei servizi cloud fornita da Amazon che è stata la prima a fornire questo tipo di servizi su larga scala, credo che sia ancora la più importante e sicuramente ha una offerta ampia e completa e per quel che è la mia esperienza servizi di alta qualità e affidabilità. Tutti i servizi cloud forniti da AWS sono identificati da sigle, RDS e S3 sono due di questi. RDS sta per Relational Database Service, è un servizio cosiddetto SaaS (per alcuni PaaS, il confine è labile e la stessa Amazon non sembra sbilanciarsi troppo), altra sigla Software As A Service. In pratica RDS è un database server già configurato e gestito automaticamente da AWS per cui l’utente deve solo fare le configurazioni “applicative”, creazione schemi/utenti e poco altro. Sotto c’è sempre una macchina ma non si ha accesso al sistema operativo e questo in rari casi è una limitazione. Non c’è accesso allo storage della macchina, però per le istanze RDS Oracle è predisposta una directory e il corrispondente oggetto Oracle Directory per gestire import/export con Data Pump ed external tables. La modalità di default per accedere a questo filesystem è il package PL/SQL DBMS_FILE_TRANSFER con le procedure GET_FILE e PUT_FILE che permettono di trasferire file da una istanza Oracle all’altra tramite DB LINK. Questa modalità funziona bene per i dump creati da DataPump, non funziona con i file di testo come i log a causa della limitazione che i file devono avere dimensione multipla di 512 byte (poi si parla solo di binary file e non ho capito se anche questo è un vincolo). Per accedere ai file di testo sulle istanze RDS Oracle è a disposizione un package PL/SQL RDSADMIN.RDS_FILE_UTIL con delle procedure o funzioni per mostrare il contenuto di file di testo, elencare i file presenti nella directory e per cancellarli.

S3 è un altro dei servizi AWS, Simple Storage Service, una sorta di filesystem, con diverse funzionalità accessorie. Tramite la “AWS CLI” è facile copiare da e verso “bucket” S3 file. Sulle istanze Oracle RDS è possibile attivare una opzione chiamata S3_INTEGRATION che aggiunge l’accesso a un package PL/SQL RDSADMIN.RDSADMIN_S3_TASKS che fornisce due funzioni per copiare dall’istanza RDS a un bucket S3 e viceversa dei file. Questo rappresenta quindi un modo più comodo per trasferire file da e verso istanze RDS, seppur sempre dovendo “triangolare”. Si tratta poi di gestire con attenzione le “policy” e i permessi vari. Uno dei vincoli da tenere presente nel modulo S3_INTEGRATION è che funziona solo se l’istanza RDS e il bucket S3 sono nella stessa Region, mentre poi, sempre gestendo in modo opportuno le policy dei bucket S3 è possibile copiare file tra bucket in region diverse e anche fra bucket in account diversi tramite CLI ad esempio. Occorre fare attenzione alle “ACL” ovvero ai permessi associati a file copiati nei bucket. Per default se copio da un account un file in un bucket in un altro account (dopo che a questo bucket ho associato una policy che mi permetta di copiarci file) il file mantiene i permessi di chi ha scritto il file, quindi, un po’ sorprendentemente e paradossalmente per me, il proprietario del bucket non può accedere in alcun modo al file. Per cambiare questo comportamento se si copia il file tramite CLI basta aggiungere l’opzione “–acl bucket-owner-full-control” che dice appunto che l’owner del bucket avrà pieno accesso al file copiato. Si può anche associare una policy al bucket che forzi l’uso dell’opzione, nel senso che se qualcuno cerca di copiare un file senza specificarla viene restituito un errore. Non mi risulta esista un modo per renderlo un default del bucket, ovvero fare si che qualunque file copiato nel bucket sia pienamente accessibile al proprietario del bucket.

Nelle funzioni di copia di S3_INTEGRATION non c’è la possibilità di specificare il parametro “–acl bucket-owner-full-control” per cui per copiare i file su bucket S3 di altri account occorre fare ulteriori passaggi. Ad esempio è possibile, una volta trasferito il file nel bucket usare il comando “aws s3api put-object-acl ….–acl bucket-owner-full-control

Vi è poi un’altra casistica con cui mi sono scontrato e per la quale credo di aver trovato la soluzione ma non ho capito perfettamente la motivazione. Copiando file da un bucket S3 in una regione a un bucket in un’altra regione tramite CLI (aws s3 cp) ricevo un errore del tipo “The eu-south-1 location constraint is incompatible for the region specific endpoint this request was sent to.” Questo mi è capitato con file sopra una certa dimensione e non con file piccoli (mi pare sotto gli 8 MB). Sembra che oltre gli 8 MB per default venga cambiata la modalità di caricamento del file utilizzando una modalità “multipart”, ma poi perché questo scateni l’errore non l’ho capito.

In un primo momento ho trovato (dopo estenuanti ricerche) come soluzione l’aggiunta del parametro “–metadata-directory REPLACE”, poi andando sulla documentazione di AWS CLI V2 ho trovato il suggerimento di usare il parametro “–copy-props”, ho provato con “metadata-directive” e funziona (dovrebbe funzionare anche con “none” ma non sono sicuro di aver provato)

Riporto un esempio di policy che ho utilizzato su un bucket S3 per poterci copiare (e leggere) file da un altro account:

Una delle cose belle, fra tutto il casino e la complessità che c’è dietro è che AWS fornisce fra le altre anche delle librerie per poter fare tutto tramite script in python

Basi di crittografia

Come ho spiegato nel mio post precedente sto cercando di mettere un po’ in sicurezza degli script in python che uso per l’automazione di alcune operazioni su database. Da questa esigenza mi è venuta voglia di aggiornarmi su quella che è attualmente il livello tecnologico della crittografia. Si tratta di un campo che deve evolvere velocemente perché da una parte i sistemi di elaborazione diventano più potenti rendendo deboli soluzioni che anni fa erano robustissime e dall’altro, il fatto che sempre più servizi informatici siano accessibili a tutto il mondo sulla rete rende necessario sistemi più sicuri. Facendo un po’ di ricerche ho trovato come base di partenza una ottima fonte, il libro di Svetlin Nakov “Practical Cryptography for developers”, un libro abbastanza aggiornato e che fa una buona panoramica, senza voler entrare troppo nei dettagli della matematica necessaria per comprendere il funzionamento degli algoritmi di crittografia. Per qualche motivo l’autore sembra non abbia portato a compimento le sue intenzioni e il libro sembra fermo al 2018 con delle parti rimaste incompiute. Ciò nonostante mi è piaciuto e mi è stato utile. Ho integrato la mia ricerca con delle verifiche che di solito finiscono sempre su wikipedia. Il mio intento non era entrare nei dettagli dei vari algoritmi ma avere una panoramica e senz’altro questo libro mi è stato molto d’aiuto. Ho raccolto un po’ di appunti sugli argomenti principali e li voglio riportare qui, come ho sempre cercato di fare.

Criptazione e chiavi

La criptazione ha l’obiettivo di modificare o meglio nascondere il testo di un messaggio affinché solo il destinatario del messaggio possa decodificarlo e comprenderlo. Per fare ciò è fondamentale il concetto di chiave, che permette di codificare (criptare) il messaggio in modo da renderlo incomprensibile a chi non ha la chiave per poterlo decodificare. Esistono due tipologie di crittografia: 

  1. simmetrica, in cui mittente e destinatario condividono la stessa chiave per codificare e decodificare i messaggi
  2. Asimmetrica, in cui il mittente usa una chiave per codificare e il destinatario usa un’altra chiave per decodificare. Di solito si parla di chiave pubblica e chiave privata, il destinatario condivide la chiave pubblica con cui il mittente codifica il messaggio che può essere decodificato solo con la chiave privata e quindi solo dal destinatario

Uno dei principi base della crittografia è che la codifica è tanto più sicura (quindi il messaggio non può essere decodificato da estranei) quanto più la chiave è lunga e casuale, per questo solitamente si usano algoritmi di derivazione delle chiavi.

Funzioni Hash (Hash Functions)

Sono funzioni deterministiche che effettuano una codifica unidirezionale, data una stringa di input generano un output a lunghezza fissa pseudocasuale dal quale non è teoricamente fattibile risalire alla stringa di partenza. Il risultato di questa funzione viene proprio chiamato “hash” ed una delle caratteristiche di queste funzioni è che la probabilità che due input diversi producano lo stesso hash è pressoché zero. Uno degli utilizzi di questo tipo di funzioni è quindi la generazione di codici per validare il contenuto di un messaggio o file. Avendo a disposizione l’hash del messaggio o file originale è possibile, riapplicando la stessa funzione di hash, verificare facilmente l’uguaglianza dell’hash calcolato con quello che accompagna il messaggio o file. Un esempio ormai obsoleto di tali funzioni è MD5 (dove se non ricordo male MD sta per message digest), essa viene ancora utilizzata, seppur sempre meno, per il controllo di integrità ovvero la verifica che un messaggio (per semplicità d’ora in poi parlerò solo di messaggi e non di file) arrivato a destinazione sia uguale a quello partito. 

Le funzioni di hash sono state una delle prime funzionalità per mettere in sicurezza le password salvate, tipicamente anziché salvare le password in chiaro si salva il loro hash e in fase di login si confronta l’hash calcolato sulla password fornita con l’hash memorizzato. Questo fa si che se un malintenzionato riesce ad accedere all’archivio delle password non è in grado di risalire alla password originale, per via di una una delle caratteristiche delle funzioni di hash  che quella di non essere “invertibili” e quindi di non permettere di  risalire agevolmente alle password originale dagli hash.

Altri funzioni di hash più comunemente utilizzate sono MD5, SHA-1, SHA-2, SHA-3, BLAKE2 e BLAKE3.  MD5, SHA-1 sono oramai considerati insicuri e da non usare crittografia ma ancora usati in altri ambiti quale il controllo di integrità, oppure, come nel caso di sha-1 come generatore di hash univoci (vedi id commit in git).  La prima versione di SHA (SHA-1) genera hash di 160 bit, le versioni 2 e 3 (SHA-2 e SHA-3) hanno varianti con hash a 256 e 512 per cui si trova la notazione SHA-256 o SHA3-512 (ci sono poi anche ulteriori varianti a 224 384 bit che sono in realtà derivate rispettivamente dalla variante a 256 bit e 512 bit) quindi se si trova la notazione SHA-XXX si tratta implicitamente di SHA-2 mentre per SHA-3 si trova la notazione SHA3-XXX. Lo esplicito perché io sinceramente quanto ho iniziato a studiare questo argomento ho fatto un po’ di fatica all’inizio a districarmi tra queste notazioni per capire esattamente a che versioni ci si riferisse. SHA-1 non ha la specifica del numero di bit dell’hash prodotto perché ha una unica variante a 160 bit. Per completezza esiste anche un SHA-0, obsoleto e un RIPEMD-160. Ce ne sono ancora altri ma non intendo fare una rassegna esaustiva, rimando all’ottimo libro di Nakov per gli approfondimenti.

E’ abbastanza evidente che con il passare del tempo, il progredire della ricerca e l’aumento delle prestazioni di elaborazione dei calcolatori gli algoritmi devono essere aggiornati e per questo osserviamo l’esistenza di diverse versioni o il nascere di nuove, se si vuole essere sicuri occorre aggiornare gli algoritmi utilizzati dai propri sistemi. Wikipedia al solito è una fonte di informazione ottima, segnalo solo che almeno un paio di pagine in italiano sono poco aggiornate, quindi consiglio di andare direttamente alla versione inglese. Qui si trova uno schemino di confronto dei vari algoritmi di hash usati in crittografia

Message Authentication Codes (MAC) e hash based MAC (HMAC)

Un MAC è un codice calcolato da una chiave e da un messaggio che serve a stabilire l’autenticità di un messaggio. Il principio è che mittente e destinatario in qualche modo condividono una chiave nota solo a loro. Il mittente che deve inviare un messaggio genera un codice (il MAC) usando il testo del messaggio e la chiave, invia il messaggio e il MAC al destinatario e quest’ultimo potrà verificare l’autenticità del messaggio confrontando il MAC ricevuto con quello calcolato da lui con il messaggio e la chiave che solo lui e il mittente conoscono.  I MAC hanno le stesse caratteristiche degli hash crittografici, sono irreversibili e generano codici praticamente unici. I MAC basati su algoritmi di hashing vengono chiamati hash based MAC e quindi identificato con la sigla HMAC. Gli utilizzi tipici degli HMAC sono l’autenticazione di messaggi, la verifica di integrità di messaggi e la derivazione di chiavi

Algoritmi di derivazione delle chiavi 

Si tratta di algoritmi utilizzati per derivare chiavi di criptazione (con determinate caratteristiche) da semplici password generate solitamente da umani. Esempi di questi algoritmi sono PBKDF2, bcrypt, argon2 e scrypt. Questi algoritmi servono a generare (o meglio derivare) delle chiavi che abbiano requisiti necessari a garantire la necessaria efficacia e sicurezza di criptazione nel caso di criptazione a chiave unica (simmetrica). La caratteristica più importante che deve avere una chiave, come abbiamo detto sopra, è quella di essere quanto più “casuale” possibile, questo permette di prevenire approcci basati sulla statistica. Per rendere difficile l’uso di dizionari di hash (chiamati “rainbow table”) queste funzioni aggiungono alla password di partenza un “salt”, un codice generato anche esso con qualche funzione “casuale” da aggiungere alla chiave di partenza, quindi generando hash diversi per le stesse password se si usano salt diversi. Per rendere poi infattibili i cosiddetti attacchi di forza bruta (brute force attacks) ovvero il calcolo di tutti i possibili hash, gli algoritmi di derivazione introducono un certo costo computazionale reiterando migliaia o centinaia di migliaia di volte la stessa operazione. Il numero di iterazioni è un compromesso fra la necessità di una autenticazione veloce (perché nei sistemi di autenticazione questi algoritmi hanno preso il posto delle semplici funzioni di hash per il salvataggio delle password) e la necessità di garantirne  la non decodificabilità. Infatti uno dei limiti delle semplici funzioni di hash è che moderni sistemi dedicati possono calcolare milioni di hash al secondo rendendo quindi un attacco di forza bruta fattibile, usando algoritmi più complessi questo diventa molto più complesso.

L’algoritmo PBKDF2 è oramai ritenuto non molto sicuro perché debole contro attacchi di forza bruta basati sull’uso di GPU o ASIC (circuiti di elaborazione dedicati); Bcrypt, Scrypt e Argon2 sono algoritmi più recenti e più robusti contro attacchi perché richiedono molte più risorse computazionali. Questi algoritmi prevedono esplicitamente dei parametri di input che incidono sull’uso di memoria e cpu mentre PBKDF2 prevede solo il numero di iterazioni che può incidere sull’uso di CPU.

Gestione script e password

Da un po’ di tempo a questa parte uso script python per gestire piccole attività ripetitive su database. Un tempo avevamo database in locale o presso i clienti, protetti da reti locali per cui non ci si poneva grandi problemi sulla gestione delle password e della sicurezza dell’accesso, salvo qualche rara eccezione. Ora i tempi sono cambiati, la mia azienda è cresciuta e sempre di più lavoriamo sul “cloud”. Questo ha portato a una moltiplicazione di destinazioni e alla complicazione di piccole attività come fare dump da un database all’altro. Da questa aumentata complessità è nato il mio bisogno di utilizzare script di automazione e un paio d’anni fa (forse anche prima…) ho iniziato a lavorare con python e devo dire che per questo tipo di cose è molto comodo, facile da imparare e usare e al tempo stesso potente e completo.

Siccome ancora non ho a disposizione uno strumento “comodo” di gestione delle password ho deciso di fare un passo ulteriore, rispetto al dover andare a recuperare la password e copiarla come parametro ogni volta che devo eseguire uno script. Un primo passo è stato usare un file di testo in chiaro sul pc, d’altra parte il pc è già protetto di suo da un accesso controllato, il che garantisce già un minimo livello di sicurezza.

L’argomento sicurezza è serio e complesso, secondo me in molti lo prendono seriamente, alcuni sono al limite della patologia, molti hanno un approccio serio ma pragmatico, altri secondo me non ne capiscono molto. Poi ci sono quelli che invece non se ne preoccupano, però sono sempre meno perché l’evoluzione dell’informatica, la diffusione dell’uso del cloud e dei servizi accessibili su internet costringe a fare attenzione. Io confesso di essere molto critico rispetto ai molti che ritengono ed esibiscono esperienza di sicurezza, sono spesso gli stessi che ti fanno mettere simboli del dollaro, punti esclamativi e maiuscole nelle password che però possono essere lunghe otto caratteri. Certamente, se uso come password la parola “password” mi espongo a un grosso rischio, ma non è che usando “Pa$sword1” possa essere molto più sicuro.

Premesso rapidamente il pensiero sull’argomento (era tanto che volevo esporlo), come dicevo che mi sono posto il problema di come gestire in sicurezza le password di accesso a sistemi sul mio pc per poter lanciare gli script in modo quanto più automatico e veloce. Si tratta di un piccolo pezzo di gestione della sicurezza su cui mi sono chiesto quali sono le buone pratiche standard (“best practice” come direbbero quelli che ne sanno) per gestire questa cosa. Alla fin fine queste password devono essere salvate da qualche parte, anche sia un password manager in piena regola è sempre un sistema a cui io devo poter accedere tramite una password. Il fatto di averle sul mio pc limita la gestione in quanto non è condivisa, ma questo è un passo ulteriore che per ora tralascio, altrimenti non si fa nulla.

Facendo una rapida ricerca su google qualche cosa di trova, la cosa più interessante che ho trovato è stata questa, da cui ho preso spunto. La prima cosa che ho voluto fare è quindi informarmi un po’ sulla libreria usata: criptography. Il link dell’articolo manda a una versione piuttosto vecchia ma in realtà la libreria pare essere ancora aggiornata ed attiva e ne ho trovato vari riferimenti in giro. Per la verità non ho trovato altre librerie analoghe per python…. Il caso descritto nell’articolo è un po’ basilare perché gestisce una sola password, la mia ambizione è gestire diverse, non tantissime, parliamo di una decina al momento. In ogni caso, andando sul sito della documentazione si trovano anche un po’ di esempi interessanti. La mia idea è di usare un sistema con una password più o meno mnemonica, altrimenti sono al punto di partenza e la c’è un esempio che si basa sull’uso di PBKDF2 che io sinceramente non conosco e su cui quindi mi devo informare, anche perché sulla documentazione stessa consigliano di usare “Scrypt“. Quindi mi rimane da fare qualche prova e trovare qualche informazione chiara sul livello di sicurezza di queste soluzioni, a quel punto spero di poter fare un nuovo post con i risultati di queste mie ricerche

Nuovi argomenti

Ormai da molto tempo la mia attività come DBA Oracle è diventata non dico marginale ma quanto meno non predominante. E’ stata una transizione lunga ed io ho fatto fatica a prenderne atto ed a capire in che direzione mi dovevo muovere ma soprattutto come potevo sfruttare al meglio la mia esperienza e le mie capacità coniugandole con quelle che sono le mie ambizioni i miei desideri e i miei gusti. Insomma ci sono dei tipi di attività che faccio più volentieri a cui mi dedico senza problemi, mentre ci sono altre attività che non amo e cerco sempre di schivare. Nel mio profondo rimango uno a cui piace approfondire le cose, entrare nei meccanismi, non gestire le cose dall’alto senza sapere come funzionano i dettagli. Questa in fondo temo sia una mia limitazione, però i gusti son gusti e rimango convinto che fin dove possibile è meglio fare quello che piace.

Negli ultimi due anni ho avuto modo di mettere le mani su quelle tecnologie che da qualche anno spopolano nel mondo dell’informatica e girano attorno al termine “container”. Una volta era tutto virtualizzazione, poi la tecnologia di Linux (perché direi che quella a permesso la grande diffusione di questa nuova architettura) ha permesso un ulteriore passaggio con la containerizzazione, un sistema più efficiente di virtualizzazione. In questo mondo della containerizzazione sono fioriti una serie di strumenti di corredo; all’inizio era Docker, poi è arrivato Kubernetes, containerd, CRI-O, podman, Openshift, helm, buildha e chissà quante altre cose. Ho lavorato e sto lavorando con alcuni di questi strumenti e vorrei fare un po’ di ordine nella mia mente, quindi sto cercando di riorganizzare i miei appunti. In quest’ambito penso e spero di poter scrivere qui qualche post che mi aiuti a fissare bene i concetti e a capire un po’ meglio i vari dettagli, spero di raggiungere questo obiettivo al fine di poter dire di conoscere queste tecnologie.

Caricamento veloce dati da file

una volta all’anno, o forse anche meno, mi capita di dover importare in velocità dei dati da file di testo (tipicamente csv o simili) ed ogni volta sono impreparato e in difficoltà. Il mio metodo preferito è l’uso di external tables ma questa volta dovevo importare un file con circa 150mila righe su un database cui non avevo accesso alla macchina per depositare il file. Siccome sto maturando una insana passione per Python ho pensato bene di fare al volo un programmino per fare questo lavoro. Effettivamente non ho speso più di mezz’ora per fare il programmino e caricare i dati. Poi non so perché ho fatto una piccola ricerca e mi sono reso conto se sono un po’ sciocco e che ho già a disposizione degli strumenti rapidi ed efficaci. Mi sono trovato su questa discussione su Stackoverflow dove Jeff Smith da degli spunti interessanti. Ne ha poi scritto in un post sul suo blog, ma c’è anche un post che riporta alcuni aggiornamenti della versione 20.2 che è quella che sto usando attualmente. A dirla tutta poi anche SQL Developer offre la stessa funzionalità in maniera guidata a prova di scimmia, senza dover impazzire con sql loader che risulta utile e necessario per ben altri volumi

import/export con Locator da 19c a 12c

Ultimamente mi dedico molto a fare dump da un database all’altro, spesso mi va dritta, ma spesso mi ritrovo errori più o meno noti. L’ultimo che mi è capitato mi era ignoto, mi è capitato facendo un dump da un Oracle 19c con Spatial (che dalla 19c è incluso nella standard edition) a una 12.1.0.2 con Locator:

Elaborazione dell'object type SCHEMA_EXPORT/TABLE/INDEX/DOMAIN_INDEX/INDEX
ORA-39083: Creazione dell'object type INDEX:"HR"."IDXDEPT_SP01" non riuscita con l'errore:
ORA-06550: riga 8, colonna 18:
PLS-00302: component 'CHANGE_IMP_SDO_GEOM_METADATA' must be declared
ORA-06550: riga 8, colonna 3:
PL/SQL: Statement ignored
L'istruzione SQL che contiene l'errore Þ:
BEGIN
declare
schema_name varchar2(200);
begin
select sys_context('userenv', 'CURRENT_SCHEMA') into schema_name from SYS.DUAL;
if ( (schema_name != sys_context('userenv','current_user')) or
(schema_

Una rapida ricerca su google mi ha portato alla nota “PLS-00302: component ‘CHANGE_IMP_SDO_GEOM_METADATA’ must be declared (Doc ID 2092034.1)” la quale spiega che all’oggetto MDSYS.SDO_META è stato aggiunto il metodo CHANGE_IMP_SDO_GEOM_METADATA per gestire l’opzione REMAP_SCHEMA di datapump. Per sistemare occorre quindi applicare la patch indicata (Patch.19501696). Non avendo io la possibilità di installare tale patch ho trovato come unica soluzione importare a mano i metadati e ricreare gli indici spaziali.

E’ curioso notare, leggendo la nota del bug che il problema riguarda la versione 11.2.0.3 (25 agosto 2014) ed è indicato come risolto nella versione 12.2 In effetti io importando prima da una versione 11.2.0.4 a 19c e non ho avuto problemi però ho anche fatto un import da 11.2.0.4 a 12.1 e non ho avuto problemi eppure trovo disponibile la patch anche per la versione 11.2.0.4, il che mi farebbe presumere che non sia inclusa in quella versione e di fatto non c’è menzione di questo, quindi, boh?

Oracle ed Espressioni Regolari, introduzione

Le espressioni regolari sono delle “funzioni” che permettono di identificare delle stringhe tramite semplici regole. Ho difficoltà a spiegarlo bene in italiano quindi farò una piccola deroga alla mia regola di evitare quanto possibile terminologia mista inglese/italiano; regola stabilita quando iniziai a scrivere su questo blog ma che per la verità sopratutto negli ultimi anni ho violato spesso. Dicevo, una espressione regolare serve a definire un “pattern” ovvero una caratteristica particolare di una stringa. Tramite poi questo pattern solitamente poi si va a filtrare un sottoinsieme di stringhe che corrispondono (“match”) a questo pattern da un insieme più grande.

Volendo fare una analogia di quanto si impara già dalle basi dell’SQL le espressioni regolari possono estendere le possibilità fornite dalla clausola LIKE con i caratteri speciali “%” e “_”.

Le espressioni regolari esistono da che esistono i computer e forse da prima; sistemi evoluti (*nix) forniscono da decenni programmi di utilità che implementano ricerche basate su espressioni regolari (grep, sed, ecc). Sono molto potenti ma possono essere anche molto complesse e quindi non è banale conoscerle e utilizzarle.

Oracle ha introdotto, già con la versione 10g, il supporto all’uso di espressioni regolari tramite l’introduzione di specifiche funzioni (che mi sembra siano standard ISO SQL) che sono:

  • REGEXP_LIKE
  • REGEXP_INSTR
  • REGEXP_REPLACE
  • REGEXP_SUBSTR

Non può sfuggire che sono l’analogo di funzioni/operatori già esistenti ma più semplici.

Ciclicamente mi capita di dover affrontare problemi in cui una buona conoscenza delle espressioni regolari aiuta molto; ogni volta mi trovo in difficoltà perché non riesco a memorizzare e capire a fondo il loro funzionamento. Il fatto di non usarle con costanza e regolarità non aiuta. Ecco perché a credo almeno 15 anni dalla loro introduzione in Oracle io mi trovo adesso a scrivere un post per spiegarne un po’ di basi, nella speranza che questo mi aiuti la prossima volta che mi serviranno.

Una espressione regolare è formata da uno o più “caratteri” e “metacaratteri”. Nella forma più semplice una espressione regolare può essere formata da da una o più lettere e/o cifre, ad esempio “a”, “a1”, “pappa”. I metacaratteri servono a indicare delle regole con cui processare e interpretare i caratteri della espressione regolare.

Vediamo le regole di base per definire una espressione regolare partendo da esempi semplici.

Il metacarattere “.” Si utilizza per indicare un qualunque carattere (ad esclusione dell’”accapo”). L’espressione “c.t” equivale a una stringa che contenga la lettera “c”, seguita da un qualunque carattere a sua volta seguito dalla lettera “t”. Quindi può corrispondere alla parola “cat”, ma anche “cut”, o “cutter” o “incitare”. Se vogliamo identificare solo parole che iniziano con “c”, seguita da un carattere qualunque seguito a sua volta dalla lettera “t” allora spossiamo usare i metacaratteri “^”, per indicare che il carattere che si trova dopo deve essere all’inizio della stringa e il metacarattere “$” per indicare che il carattere prima si deve trovare alla fine della stringa. Quindi ad esempio l’espressione regolare “^c.t$” corrisponde solo a stringhe di tre caratteri in cui il primo è la lettera “c”, l’ultimo è la lettera “t” e in mezzo va bene un carattere qualunque, come ad esempio “cit”, “c1t”, “cat” ma non “catalogo”.
Per indicare più occorrenze di un carattere si possono usare dei “quantificatori”; si possono usare i metacaratteri “?”, “+” e “*” o la notazione {n,m}, {n}, {n,}. “?” si usa per indicare zero o una occorrenze; “+” per indicare una o più occorrenze; “*” per indicare zero o più occorrenze. {n,m} dove n ed m sono due numeri, indica un numero di occorrenze compreso tra n ed m (estremi inclusi); {n} indica n occorrenze esatte; {n,} indica n o più occorrenze.
Quindi, l’espressione “^c.*t$” indica ora una stringa che inizia con la lettera “c” e finisce con la lettera “t”, in mezzo ci può essere un numero qualunque di qualunque carattere, ad esempio: “ct”, “caat”, “cavallot”, “cavolo t”. All’interno di una espressione regolare possiamo raggruppare sottoespressioni racchiudendole tra parentesi tonde (che sono quindi altri metacaratteri) Possiamo quindi definire l’espressione regolare “^c(as)*t$” che corrisponde ad esempio a “ct”, “cast”, “casast”.
Per definire una lista di caratteri si possono usare le parentesi quadre ([]) per delimitarli, ad esempio: “^c[auoei]t$” corrisponde a “cat”, “cit”,”cot” ma non “cyt”. Questa espressione può essere anche scritta usando le parentesi tonde e il metacarattere “|” che indica alternative, quindi: “^c(a|u|o|e|i)t$”. All’interno di parentesi quadre, all’inizio, il metacarattere “^” assume il significato di negazione, quindi l’espressione “^c[^hjk]t$” indica una stringa che inizia con c e finisce con t e in mezzo ha un qualunque carattere che non sia “h”, “j” o “k”. Fra le parentesi quadre si possono usare “liste” di caratteri usando il metacarattere “-“, ad esempio: “^c[d-z]t$” indica che tra “c” e “t” ci può essere un qualunque carattere compreso tra “d” e “z”. Se il carattere “-“ è all’inizio, come ad esempio “^c[-dys]t$” viene interpretato come carattere e non come metacarattere, quindi l’espressione in questo caso corrisponde ad esempio alla stringa “c-t”.

Esistono dei gruppi di caratteri definiti classi POSIX che vanno inseriti fra parentesi quadre e sono a loro definiti con la notazione di questo tipo: [:class:] , riport una tabellin di quelli più comuni:

Character ClassDescription
[:alpha:]Alphabetic characters
[:lower:]Lowercase alphabetic characters
[:upper:]Uppercase alphabetic characters
[:digit:]Numeric digits
[:alnum:]Alphanumeric characters
[:space:]Space characters (nonprinting), such as carriage return, newline, vertical tab, and form feed
[:punct:]Punctuation characters
[:cntrl:]Control characters (nonprinting)
[:print:]Printable characters
Classi di caratteri predefinite POSIX

Su wikipedia ce ne sono anche altre ma non ho verificato se Oracle le supporta tutte.

Il manuale Oracle dove vengono spiegate un po’ le espressioni regolari e il loro supporto e uso sul database è “Database Development Guide” a cui rimando per approfondimenti sulle espressioni regolari in genere ma soprattutto per le specificità. Spero di fare ulteriori post per approfondire meglio l’argomento e arrivare ad un uso più avanzato e proficuo di questo potente strumento.

SQLcl 19.4 e break on report

Scrivo un breve post per segnalare una cosa bizzarra che mi è capitata, un bug su SQLcl che mi perseguitava da diverso tempo ma che solo recentemente ho analizzato e individuato. Già qualche settimana fa, dopo che ero convintamente passato a utilizzare SQLcl al posto di SQLPlus mi sono accorto che alcuni miei script non funzionavano bene. Alla fine ho trovato un attimo per approfondire e cercare di riprodurre il problema, ho fatto un test su un database interno e fatto un trace della sessione per vedere cosa accadeva. Sono così riuscito a circoscrivere il problema e creare un banale “test case” che ho sottoposto via twitteral gruppo di sviluppo di SQLcl. Il codice è molto semplice:

create table t1(a number);
insert into t1 values (1);
insert into t1 values (2);
break on report
compute sum of uno on report
select t1a.a uno from t1 t1a join t1 t1b on (t1a.a=t1b.a);

Il problema si verifica quando si usa la direttiva “break on report” in associazione a una query di join. Per gestire questa direttiva a quanto pare la query viene riscritta e qui nasce il problema, perché dalla query riscritta spariscono le condizioni di join. Infatti uno dei miei script andava su viste di sistema, ci metteva una vita a dare il risultato che poi era anche sbagliato. La mia query veniva trasformata in un prodotto cartesiano….

Nel mio caso di test anche facendo un prodotto la query restituirebbe solo due record, quindi non sarebbe un problema.  In realtà l’unica query che vedo eseguita risulta essere:

SELECT 
sum(t1a.a) AS uno, 'sum' uno_CMPFN, COUNT(*) AS ROW_CNT, 'report' AS BRKCOLKEY FROM t1 t1a, t1 t1b;

che restituisce un solo record ma qualcosa su SQLcl sembra vada in loop.

Sono ansioso di vedere se dopo la mia segnalazione al prossimo rilascio il problema sarà corretto

Oracle Program Global Area (PGA)

Premessa

Questo post è un esperimento di importazione da un documento scritto su google docs. Ho cominciato a raccogliere un po’ di appunti per fare un po’ di ordine sull’argomento nella mia testa, poi, visto che sono riuscito a scrivere qualcosa che mi sembra accettabile ho deciso di pubblicarlo, come ho fatto molte volte in passato. Ci sono sicuramente alcuni problemi di formattazione, da tempo ho alcune difficoltà con la piattaforma wordpress ma non ho tempo ne’ voglia di approfondire e risolvere questi dettagli.

In questo articolo farò una panoramica sulla componente “Program Global Area”, riferita normalmente con la sigla PGA. La versione del database a cui faccio riferimento è la 12.1 e quindi anche i riferimenti ai manuali puntano a quella versione.

La Program Global Area in Oracle è un’area di memoria di cui riporto direttamente la definizione dal manuale “Administrators Guide” tradotta alla meglio.

Una Program Global Area (PGA) è una area di memoria che contiene dati e informazioni di controllo per un processo server. Essa è una memoria non condivisa creata dal Database Oracle quando un processo server viene avviato. L’accesso alla PGA è esclusivo per quel processo, c’è una PGA per ogni processo server. Anche in processi di background allocano la loro PGA. La PGA allocata per i processi di background e i processi server attaccati a un Database Oracle vengono chiamati “total instance PGA memory” mentre l’insieme di tutte le PGA individuali è chiamato “total instance PGA” o solo “instance PGA”

Per la verità le ultime definizioni mi sembrano un po’ ambigue ma è un dettaglio irrilevante.

Riporto poi dal manuale “Performance Tuning Guide

Un esempio di informazioni scritte nella PGA per un processo è la “run-time area” di un cursore. Ogni volta che un cursore viene eseguito una nuova “run-time area” viene creata per quel cursore nella area di memoria PGA del processo server che sta eseguendo quel cursore.

Per query complesse una grossa porzione della area “run-time” è dedicata a “work area” allocate da operatori che fanno uso intensivo di memoria, inclusi ad esempio ORDER BY, GROUP BY, ROLLUP, HASH JOIN. Un operatore di ordinamento (sort operator) usa una “work area” (la “sort area”) per eseguire l’ordinamento in memoria di un insieme di righe. Analogamente , un operatore HASH JOIN usa la “work area” (hash area) per costruire una tabella hash dal suo input (left input).

 

Viste le definizioni generiche di PGA andiamo a vedere un po’ nel dettaglio come viene gestita la PGA.

Dimensioni delle “Work Area”

Oracle permette il controllo e la regolazione delle dimensioni delle “work area”. Generalmente “work area” più grandi possono migliorare significativamente le prestazioni di un particolare operatore al costo di un più alto consumo di memoria. Le dimensioni di “work area” possono essere classificate in :

  • Optimal – quando la dimensione è sufficientemente grande da contenere i dati di ingresso e le strutture di memoria ausiliarie allocate dall’operatore SQL associato. Questa è la dimensione ideale
  • One-pass – quando la dimensione della “work area” è al di sotto di quella ottimale e richiede un passaggio in più sui dati di ingresso
  • Multi-pass – quando la “work area” non è sufficiente e occorrono più passaggi sui dati di ingresso

Tunable e Untunable

Oracle classifica, o almeno espone questa classificazione della PGA in sei categorie:

  • SQL
  • PL/SQL
  • Java
  • Olap
  • Other
  • Freeable

La parte dedicata alle work area è quella denominata “SQL”, viene anche definita “tunable” in contrapposizione alle altre che nel complesso sono definite “untunable”.

Dimensionamento della Program Global Area

Abilitando la gestione automatica della PGA (tramite il parametro PGA_AGGREGATE_TARGET con un valore maggiore di zero) Oracle dimensiona automaticamente la PGA correggendo dinamicamente la porzione di PGA dedicata alle “work area”. Se non specificato il valore di default per PGA_AGGREGATE_TARGET è il 20% delle dimensione della SGA. Il valore minimo è 10 MB. Il dimensionamento automatico gestito sulla base del parametro PGA_AGGREGATE_TARGET riguarda solo la parte definita “tunable”, quindi solo quella dedicata alle work area che in un certo senso è quella più dinamica

PGA_AGGREGATE_LIMIT

Dalla versione 12c Oracle ha introdotto il nuovo parametro PGA_AGGREGATE_LIMIT che permette di limitare l’allocazione globale di memoria per la PGA. Per default il valore di questo parametro è il valore più grande fra:

  • 2 GB
  • 2 * PGA_AGGREGATE_TARGET
  • 3MB*PROCESSES

Può essere inferiore al 2* PGA_AGGREGATE_TARGET se tale valore supera il 90% della memoria ram totale disponibile meno la dimensione della SGA. Oracle raccomanda di non impostare PGA_AGGREATE_TARGET a un valore inferiore al suo default e per farlo l’unico modo è attraverso pfile o spfile (PGA_AGGREGATE_LIMIT). Su questa affermazione ho delle perplessità, infatti su una installazione con Oracle 12.1.0.2.0 ho fatto il seguente test:

SYSTEM@svil121 > show parameter pga
NAME TYPE VALUE
-------------------- ----------- -----
pga_aggregate_limit big integer 2856M
pga_aggregate_target big integer 2700M
SYSTEM@svil121 > alter system set pga_aggregate_limit=2900 m;
System SET modificato.
SYSTEM@svil121 > alter system set pga_aggregate_limit=2800 m;Errore con inizio alla riga : 1 nel comando -
alter system set pga_aggregate_limit=2800 m
Report error -
ORA-02097: impossibile modificare il parametro perchÚ il valore specificato non Þ valido
ORA-00093: pga_aggregate_limit deve essere compreso tra 2856M e 100000G
02097. 00000 - "parameter cannot be modified because specified value is invalid"
*Cause: Though the initialization parameter is modifiable, the modified
value is not acceptable to the parameter.
*Action: Check the DBA guide for range of acceptable values for this
parameter.
SYSTEM@svil121 > show parameter processes
NAME TYPE VALUE
------------------------- ------- -----
processes integer 1200

La RAM totale del sistema è 8100 MB e la SGA 4096 MB, quindi il valore di default dovrebbe essere almeno 3600 MB (che rientra nel 90% della memoria del sistema meno la SGA)

Sospetto possa essere un bug (di cui non ho trovato traccia sul supporto) in quanto su una installazione 18.3 con parametro processes a 1000, target a 512 MB il messaggio mi dice che il valore deve essere tra 3000 M e 100000 G, dove 3000 M è coerente con l’indicazione 3MB*PROCESSES.

Nella documentazione relativa al parametro PGA_AGGREGATE_LIMIT si dice che nel caso la dimensione della PGA superi il valore impostato dal parametro le sessioni che stanno usando la maggior quantità di “untunable memory” avranno le chiamate annullate (ORA-04036). Se la PGA sarà ancora sopra al limite tali sessioni verranno terminate.

Sul manuale Performance Tuning vengono riportate tutte le informazioni, c’è però una piccola differenza in quanto si afferma che il valore di PGA_AGGREGATE_LIMIT non supererà il 120% della dimensione della RAM disponibile meno la dimensione totale della SGA.

La nota del supporto Oracle “Limiting process size with database parameter PGA_AGGREGATE_LIMIT” (Doc id 1520324.1) riporta qualche dettaglio in più. Fra l’altro riporta che per Oracle 12.1 c’è il limite del 120% sulla dimensione della memoria fisica meno la SGA mentre per Oracle 12.2 spiega che se il parametro viene impostato a zero il suo valore sarà pari al 90% della dimensione della memoria fisica meno la dimensione della SGA.

A controllare il rispetto del parametro PGA_AGGREGATE_LIMIT ci pensa il processo CKPT ogni 3 secondi. Viene spiegato come nell’intervallo di tempo di tre secondi fra un controllo e l’altro di CKPT può essere superato il limite e se la sessione finisce la chiamata prima non ci sono conseguenze o azioni da parte di CKPT.

Controllo dell’uso e allocazione della PGA

Per controllare quanta PGA è stata allocata e come viene usata si possono usare diverse viste di sistema che possono essere interrogate via SQL, le descriverò sinteticamente una ad una.

V$PGASTAT

Questa vista mostra le statistiche globali sulla PGA, su 12.1 conto 19 righe. Viene riportato il valore di PGA_AGGREGATE_TARGET e poi, una statistica chiamata “aggregate PGA auto target”, questo è il valore derivato da PGA_AGGREGATE_TARGET che indica lo spazio PGA che oracle cerca di dedicare alle work area. Questo valore tendenzialmente è molto vicino alla differenza tra il target e la quantità di PGA “untunable” (PL/SQL ecc).

Abbiamo poi i valori di “total PGA allocated”, “total PGA inuse” e “maximum PGA allocated” che indicano, come è facile comprendere, la dimensione totale di PGA attualmente allocata, quella effettivamente in uso e quella massima allocata dall’avvio dell’istanza.

Può essere interessante il valore di “maximum PGA used for auto workareas” che indica il massimo volume di PGA consumato in una volta da work area. Vi sono poi altre statistiche che permettono di avere una indicazione dell’efficienza della PGA per poter capire se il dimensionamento è adeguato.

V$PROCESS

Se si vuole avere il dettaglio di utilizzo della PGA da parte di un processo (e quindi relativa sessione, supponendo di usare “dedicated servers”) questa vista riporta le quattro colonne:

  • PGA_USED_MEMORY
  • PGA_ALLOC_MEMORY
  • PGA_FREEABLE_MEMORY
  • PGA_MAX_MEM

Per esempio posso usare la query:

select s.sid, s.username,
pga_used_mem/1024/1024 pga_used_mem,
pga_alloc_mem/1024/1024 pga_alloc_mem,
pga_freeable_mem/1024/1024 pga_freeable_mem,
pga_max_mem/1024/1024 pga_max_mem,
row_number() over (order by pga_max_mem) rn
from
v$session s
join v$process p on (p.addr=s.paddr)
order by 6

Per ricavare la lista delle sessioni con relativo uso di PGA. In una applicazione che usa un POOL di connessioni questo dettaglio non è secondo me molto utile, occorre aggregare per avere valori medi, massimi e totali.

V$PROCESS_MEMORY

Per scendere ulteriormente nel dettaglio abbiamo a disposizione questa vista che per ciascun processo riporta il dettaglio di uso di PGA diviso nelle categorie:

  • Java
  • PL/SQL
  • OLAP
  • SQL
  • Freeable
  • Other

Non sempre sono tutte presenti, nel mio caso non ho mai righe relative a Java e OLAP. Se non ho capito male nella categoria SQL ricadono le work area, il resto dovrebbe essere tutto “untunable”. Riporto un esempio:

USER@prod > select category,sum(allocated)/1024/1024 a_mb,sum(used)/1024/1024 u_mb, max(max_allocated)/1024/1024 ma_mb from V$process_memory group by category;
CATEGORY A_MB U_MB MA_MB
--------------- ---------- ---------- ----------
Freeable 606,5 0
PL/SQL 64,0559082 21,6882935 5,46675873
Other 7871,91492 502,54974
SQL 14,4066696 2,82536316 1554,26163

Anche in questo caso ho fatto un’aggregazione

V$SESSTAT

La cito solo per completezza perché non mi sembra che dia informazioni che non possiamo avere già con le viste descritte in precedenza. Questa vista di sistema mostra per ogni sessione i valori per una lunga lista di statistiche, le due che ci possono interessare qui sono:

  • session pga memory
  • session pga memory max

La descrizione sul manuale spiega come queste statistiche abbiano senso solo sulla V$SESSTAT e non sulla V$SYSSTAT dove evidentemente non viene riportato il dato aggregato

DBA_HIST_ACTIVE_SESS_HISTORY

Premesso che questa tabella fa parte di AWR e quindi è soggetta a licenza del “tuning e diagnostics” Pack che si paga come extra sulla Enterprise Edition, riporto questa tabella non perché io la usi di frequente ma perché mi è stata indicata come fonte per individuare query che hanno richiesto più PGA. In effetti, dall’esempio che ho riportato sopra per la V$PROCESS_MEMORY si può vedere che risulta un processo che ha occupato oltre 1500 MB di work area. A meno di vedere in tempo reale le viste V$SESSION e V$PROCESS (o catturare a intervalli regolari in stile ASH il loro contenuto) non ho trovato modi per ricavare per statement/query l’utilizzo di PGA. Una alternativa è V$SQL_WORKAREA ma questa ha solo il campo LAST_MEMORY_USED, non c’è un MAX_MEMORY_USED. Per cui sembrerebbe che facendo un raggruppamento per SQL_ID, SQL_EXEC_ID (sul quale invito alla lettura di questo) si possa avere una indicazione degli statement (sql_id) che hanno richiesto maggiore PGA in un dato momento. Facendo query su snapshot limitati (se si va oltre un certo intervallo temporale il SQL_EXEC_ID potrebbe essere lo stesso ma non necessariamente l’esecuzione, ad esempio fra un giorno e l’altro…) in effetti ASH fotografa la situazione delle sessioni attive, per ciascuna sessione ricava sia la PGA_ALLOCATED che il SQL_ID dello statement in esecuzione, quindi lo statement dovrebbe essere quello che causa l’allocazione di PGA.

select sql_id,sql_exec_id,count(*),max(pga_alloc_mem) from dba_hist_active_sess_history where snap_id>=x and snap_id<=y group by sql_id,sql_exec_id;

Conclusioni

Partendo dall’esigenza di analizzare l’utilizzo di PGA in una ambiente di produzione e di individuare i “colpevoli” di elevato uso di PGA ho fatto un ripasso di tutti gli strumenti/mezzi che Oracle mette a disposizione facendo una piccola panoramica su cosa è la PGA, su come e perché viene utilizzata e infine su dove visualizzarne le informazioni e le statistiche di uso.

Riferimenti:

  1. https://support.oracle.com/epmos/faces/DocumentDisplay?_afrLoop=20851480822242&id=1520324.1&_afrWindowMode=0&_adf.ctrl-state=rze9i5k0x_4
  2. https://weidongzhou.wordpress.com/2016/02/25/pga_aggregate_target-vs-pga_aggregate_limit/
  3. https://docs.oracle.com/database/121/REFRN/GUID-E364D0E5-19F2-4081-B55E-131DF09CFDB3.htm#REFRN10328
  4. https://blog.tanelpoder.com/2011/10/24/what-the-heck-is-the-sql-execution-id-sql_exec_id/
  5. https://oracle-base.com/articles/12c/resource-manager-per-process-pga-limits-12cr2
  6. https://fritshoogland.wordpress.com/2014/12/15/oracle-database-operating-system-memory-allocation-management-for-pga/