Kloning von PDBs mit Reflinks im Filesystem

Nachfolgend möchte ich eine Möglichkeit aufzeigen, wie man das Kloning von PDBs im Filesystem auch ohne Multitenant-Option durchaus praktisch nutzen kann.

Der Beitrag wird zudem aufzeigen, was Reflinks sind und wofür man sie gut einsetzen kann. Somit ist der Beitrag nicht nur für den Einsatz beim Kloning von PDBs geeignet.

Wie entstand die Idee?

Die Idee entstand im Rahmen eines Projektkontextes und basiert im wesentlich auf PDB Snapshot Cloning Additional Platform Support. Die Option steht in der SE2 und EE auch ohne lizenzierter Multitenant-Option zur Verfügung.

Ein großer Nachteil der Funktionalität ist, das die Klonquelle read only sein muß, solange noch eine abhängige PDB existiert. Damit ist die Funktionalität ohne lizenzierte Multitenant-Option praktisch unbrauchbar, weil mindestens 2 PDBs für einen Klon benötigt werden. Dazu kommt, das die Quelle erst gelöscht werden kann, wenn die abhängigen Klone entfernt wurden.

Das sieht auf dem 1. Blick interessant aus, ist in der Praxis jedoch so sehr eingeschränkt, so das ich die Funktionalität als unbrauchbar empfinde.

Aus den Tests mit der Option entstand dann die Lösung, die ich im folgenden Blogpost näher beschreiben möchte.

Pluggable Database find ich gut

Oracle zwingt uns ab 21c zur Nutzung von Pluggable Databases – ob wir es wollen oder nicht.

Ich persönlich bin ein großer Freund der PDBs, weil sich damit viele neue Möglichkeiten eröffnen, die in der non CDB-Architektur nicht zur Verfügung stehen.

Jeder DBA sollte so schnell wie möglich mit PDBs beginnen, damit der spätere Zwang nicht zu heftig wird.

Der Einsatz von Reflinks ist nicht auf PDBs beschränkt. Es wird später noch einen weitere Blogpost geben, wo ich die Lösung in einer anderen Architektur zeigen möchte. Hintergrund ist, das die unten beschriebenen Einschränkungen für einige Szenarien zu hart sind und schnell der Wunsch nach Alternativen bzw. Erweiterungen aukommen dürfte.

Voraussetzungen

Bevor wir in die Lösung einsteigen, möchte ich vorab die Voraussetzungen beschreiben.

  • Linux mit aktivem reflink-Support in XFS
    OL7/OL8 mit aktuellem UEK oder aktuelle Version von RHEL8. In RHEL7 gibt es Reflinks für XFS noch nicht!
  • ext4 bietet keinen reflink-Support!
  • reflink muß während mkfs.xfs aktiviert sein
    Nachträglich läßt sich die Option nicht aktuvieren.
  • Quell- und Ziel-PDB müssen im gleichen Filesystem liegen
    Reflinks funktionieren nur, wenn die Dateien im glecihen Filesystem liegen. Damit ist auch klar, das Quelle und Ziel auf dem gleichen Host sein müssen.
  • Quell-PDB muß temporär gelöscht werden
    Damit funktuioniert das Verfahren leider nicht online. Für ein schnelles Kloning in einer Entwicklung ist es dennoch eine interessante Option.
  • Archivelogmodus nicht erforderlich
    Ist gerade im Entwicklungsumfeld häufig deaktiviert.
  • Backup/Recovery im Auge behalten
    Eine duplizierte PDB wird beim Restore/Recovery ihre Deduplikation verlieren. Wenn möglich, die PDB beim Backup ausschleßen und aus der Quelle neu erstellen.

Wann sollten Reflinks nicht verwendet werden?

  • Storage mit HDDs
    Der verwendete Storge sollte keine Spindeln enthalten. SSDs o.ä. sind Pflicht, weil die Reflinks in den Dateien viele random Reads zur Folge haben. Das kann bei HDDs zu großen Performance-Problemen führen
  • viele Full-Table-Scans
    Wenn viele Full-Table-Scans in der Datenbank zu erwarten sind, dann führen die Reflinks zu random reads im Storage. Das kann durchaus Performance-Probleme mit sich bringen, weshalb ich in entsprechenden Systemen davon abraten würde bzw. man damit rechnen sollte, das die IO-Last spürbar steigen wird.
  • nicht auf produktiven Maschinen
    Ich würde das nur auf Standby-Servern einsetzen. Wie man sowas mit Hilfe einer Standby-Datenbank lösen kann, wird in einem weiteren Blogpost gezeigt werden.

Was sind Reflinks?

Sie lassen sich schnell erzeugen, weil im Filesystem lediglich ein Eintrag im Verzeichnis erzeugt und bei späteren Änderungen einfach neue Blöcke belegt und in der Blockbelegungstabelle der Datei entsprecend verzweigt werden.

Die Möglichkeit von Reflink-Kopien gibt es schon seit vielen Jahren im Linuxkernel. Beim Filesystemsupport sieht es noch etwas mager aus. Von den gängigen Filesystemen in der Oraclewelt sind mir folgende mit Reflink-Support bekannt:

  • BTRFS
    Nicht für Datafiles empfohlen!
  • XFS
  • OCFS
  • ACFS
  • dNFS

Reflinks lassen sich sehr einfach erzeugen, da der cp-Befehl die notwendige Option enthält.

cp --reflink <Quelle> <Ziel>

XFS Filesystem mit Reflink-Support anlegen und mounten

Nachfolgend ein Beispiel, wie das Filesystem angelegt werden muß. Bitte beachten, das neben reflink=1 zusätzlich der crc-Support aktiviert werden muß. Beim späteren mounten ist nichts extra zu beachten, da der Kernel die Optionen automatisch aktiviert.

[root@localhost ~]# mkfs.xfs -m reflink=1 /dev/testvg/test
reflink not supported without CRC support

[root@localhost ~]# mkfs.xfs -m reflink=1 -m crc=1 /dev/testvg/test


meta-data=/dev/testvg/test isize=512 agcount=4, agsize=12800 blks
= sectsz=512 attr=2, projid32bit=1
= crc=1 finobt=1, sparse=1, rmapbt=0
= reflink=1
data = bsize=4096 blocks=51200, imaxpct=25
= sunit=0 swidth=0 blks
naming =version 2 bsize=4096 ascii-ci=0, ftype=1
log =internal log bsize=4096 blocks=1368, version=2
= sectsz=512 sunit=0 blks, lazy-count=1
realtime =none extsz=4096 blocks=0, rtextents=0

Oracle Pluggable Database und Reflinks in XFS – wie geht das?

Ich nutze ansible-oracle, um eine Beispielumgebung zu bauen. Dort habe ich ein Inventory, das alle notwendigen Einstellungen enthält. (Einen entpsrechenden Blog-Beitrag zum Thema ansible-oracle + Setup wird es bald geben.)

Bevor wir nun in die Details einsteigen, noch ein paar Informationen was genau passieren wird:

  • Quell-PDB: close + open read only
    open read only, damit garantiert werden kann, das während des Kloning keine strukturellen Änderungen möglich sind.
  • spool ‚cp -reflink <Quell-Datafile> <temporäres reflink-Verzeichnis>
    Hier wird mittels spool + Select in SQLPlus eine SQL-Datei mit ‚cp –reflik‘ erzeugt, das später ausgeführt wird.
    Wichtig! Die Reflinks dürfen erst erzeugt werden, wenn die Quell-PDB geschlossen und das zugehörige XML-File erzeugt wurde.
  • Quell-PDB: close
  • Quell-PDB: unplug into ‚<xml-File>‘
  • Quell-PDB: drop keep datafiles
    Die PDB muß gelöscht werden, damit der spätere Plugin wieder möglich ist.
  • kopiere Quell-Datendateien mittels cp –eflink in temporäres reflink-Verzeichnis
    Jetzt werden die Reflinks der Datendateien der Quell-DB für die Klon-DB erzeugt.
  • Quell-PDB: create pluggable database Quell-PDB using ‚<xml-File>‘ nocopy
    Nachdem die Datendateien kopiert wurden, kann die Quell-PDB wieder angelegt werden.
  • Quell-PDB: open read write + save state
  • Ziel-PDB: create pluggable database <Ziel-PDB> using ‚<xml-File>‘ move
    Nun können wir die Ziel-PDB erzeugen. Der ‚move‘ stellt sicher, das die temporär erzeugten kopien in die OMF-Struktur von Oracle verschoben werden.
  • Ziel-PDB: open read write + save state

PDB + Reflinks: Vorbereitung

Für Oracle habe ich im SQL-Zauberkasten ein Beispiel eingefügt.

Das Skript erwartet 4 Parameter:

  • Quell-PDB
  • Verzeichnis für XML-Datei
  • Ziel-PDB
  • Verzeichnis für temporäre Reflink-Kopien

Damit die Platzersparnis durch die Reflinks besser sichtbar wird, erzeugen wir zusäzlich einen Tablespace in der Quell-PDB. Das Beispiel zeigt zudem, wie mit Hilfe des SQL-Zauberkasten die Arbeit mit PDBs vereinfacht werden kann:

[oracle@db191 oracle]$cd /usr/local/SQL-Zauberkasten/sql/
[oracle@db191 sql]$./sq.sh 

 SQL*Plus: Release 19.0.0.0.0 - Production on Tue May 25 18:25:51 2021
 Version 19.7.0.0.0
 Copyright (c) 1982, 2020, Oracle.  All rights reserved.
 Connected to:
 Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
 Version 19.7.0.0.0

SYS @ DB191:CDB$ROOT:> create pluggable database orclpdb admin user padmin identified by admin;

SYS @ DB191:CDB$ROOT:>@pdb/open_rw orclpdb                   
 alter pluggable database orclpdb open read write;

 Pluggable database altered.

SYS @ DB191:CDB$ROOT:>@pdb/save_state orclpdb
alter PLUGGABLE DATABASE orclpdb save state;

 Pluggable database altered.

SYS @ DB191:CDB$ROOT:>@pdb/switch orclpdb                    
alter session set container = orclpdb;

 Session altered.

Nun legen wir den Beispieltablespace an und werfen einen Blick auf die Tablespaceübersicht

SYS @ DB191:ORCLPDB:>create tablespace test datafile size 2g;

 Tablespace created.

SYS @ DB191:ORCLPDB:>@tbs/tbs
                                                                                         %
                                                                   %        MaxPoss    Max
Tablespace Name          KBytes           Used           Free   Used         Kbytes   Used
------------------------------------------------------------------------------------------- 
 SYSAUX                  419,840        390,912         28,928   93.1     33,554,416    1.2
 SYSTEM                  307,200        302,720          4,480   98.5     33,554,416     .9
 TEMP                     36,864         36,864              0  100.0     33,554,416     .1
 TEST                  2,097,152          1,024      2,096,128     .0      2,097,152     .0
 UNDOTBS1                163,840        163,840              0  100.0     33,554,416     .5
                  -------------- -------------- --------------        --------------
 sum                   3,024,896        895,360      2,129,536           136,314,816

SYS @ DB191:ORCLPDB:>select name from v$datafile;                                                            
 NAME
--------------------------------------------------------------------------------------------
 /u02/oradata/DB19U1/C32CA941DF697B04E053B538A8C0C58B/datafile/o1_mf_system_jbtjb6v9_.dbf
 /u02/oradata/DB19U1/C32CA941DF697B04E053B538A8C0C58B/datafile/o1_mf_sysaux_jbtjb6vg_.dbf
 /u02/oradata/DB19U1/C32CA941DF697B04E053B538A8C0C58B/datafile/o1_mf_undotbs1_jbtjb6vh_.dbf
 /u02/oradata/DB19U1/C32CA941DF697B04E053B538A8C0C58B/datafile/o1_mf_test_jbtjyml8_.dbf

Aktuell sieht es im Filesystem wie folgt aus.

Hinweis! Die Datendateien der pdb$seed befinden sich im Verzeichnis der CDB! (Siehe ./datafile weiter unten)

[oracle@db191 sql]$df /u02
 Filesystem                      1K-blocks    Used Available Use% Mounted on
 /dev/mapper/vgoradata-lvoradata  31437828 9534772  21903056  31% /u02

[oracle@db191 oracle]$cd /u02/oradata/DB19U1/
[oracle@db191 DB19U1]$du
 3358632 ./datafile
 230412  ./onlinelog
 18576   ./controlfile
 2988128 ./C32CA941DF697B04E053B538A8C0C58B/datafile
 2988128 ./C32CA941DF697B04E053B538A8C0C58B
 6595748 .

Die Differenz von df /u02 zum du entsteht durch die FRA, die ebenfalls in /u02 liegt.

Für die weitere Betrachtung ist nur die Änderung zwischen oben und dem Wert nach dem Erzeugen des reflink-Kons relevant.

Nun sind die Vorbereitungen abgeschlossen und es kann mit dem Kloning begonnen werden.

PDB + Reflinks: Beispielklon mit pdbreflinkcopy.sql

Das Skript pdbreflinkcopy.sql befindet sich im SQL-Zauberkasten.

 [oracle@db191 oracle]$cd /usr/local/SQL-Zauberkasten/sql/
 [oracle@db191 sql]$./sq.sh 

 SQL*Plus: Release 19.0.0.0.0 - Production on Tue May 25 19:30:10 2021
 Version 19.7.0.0.0
 Copyright (c) 1982, 2020, Oracle.  All rights reserved.
 Connected to:
 Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
 Version 19.7.0.0.0
 SYS @ DB191:CDB$ROOT:>
SYS @ DB191:CDB$ROOT:>@pdb/pdbreflinkcopy/pdbreflinkcopy.sql orclpdb /u02/xml/ test01 /u02/reflink/test01/                                         

 SYS @ DB191:CDB$ROOT:>set time off
 SYS @ DB191:CDB$ROOT:>whenever sqlerror exit rollback
 SYS @ DB191:CDB$ROOT:>alter session set container=cdb$root;
 Session altered.
 SYS @ DB191:CDB$ROOT:>whenever sqlerror continue none

 SYS @ DB191:CDB$ROOT:>alter  pluggable database orclpdb close;
 Pluggable database altered.

 SYS @ DB191:CDB$ROOT:>whenever sqlerror exit rollback
 SYS @ DB191:CDB$ROOT:>alter  pluggable database orclpdb open read only;
 Pluggable database altered.

 SYS @ DB191:CDB$ROOT:>set termout off feedback off pages 9000

 SYS @ DB191:CDB$ROOT:>alter  pluggable database orclpdb close;

 SYS @ DB191:CDB$ROOT:>whenever oserror exit rollback
 SYS @ DB191:CDB$ROOT:>host rm -f /u02/xml/cloneplug_orclpdb..xml
 SYS @ DB191:CDB$ROOT:>alter  pluggable database orclpdb unplug into '/u02/xml/cloneplug_orclpdb..xml';

 SYS @ DB191:CDB$ROOT:>drop   pluggable database orclpdb keep datafiles;

 SYS @ DB191:CDB$ROOT:>PROMPT do reflink copy of datafiles
 do reflink copy of datafiles
 SYS @ DB191:CDB$ROOT:>@@/u02/xml/cp_reflink_test01..sql

 SYS @ DB191:CDB$ROOT:>host cp --reflink -p /u02/oradata/DB19U1/C32DCD521B161DA1E053B538A8C099D0/datafile/o1_mf_system_jbto3c0j_.dbf /u02/reflink/test01/
 SYS @ DB191:CDB$ROOT:>host cp --reflink -p /u02/oradata/DB19U1/C32DCD521B161DA1E053B538A8C099D0/datafile/o1_mf_sysaux_jbto3c0q_.dbf /u02/reflink/test01/
 SYS @ DB191:CDB$ROOT:>host cp --reflink -p /u02/oradata/DB19U1/C32DCD521B161DA1E053B538A8C099D0/datafile/o1_mf_undotbs1_jbto3c0r_.dbf /u02/reflink/test01/
 SYS @ DB191:CDB$ROOT:>host cp --reflink -p /u02/oradata/DB19U1/C32DCD521B161DA1E053B538A8C099D0/datafile/o1_mf_test_jbto448c_.dbf /u02/reflink/test01/

 SYS @ DB191:CDB$ROOT:>-- Plugin unplugged PDB
 SYS @ DB191:CDB$ROOT:>create pluggable database orclpdb using '/u02/xml/cloneplug_orclpdb..xml' nocopy;
 SYS @ DB191:CDB$ROOT:>alter  pluggable database orclpdb open read write;
 SYS @ DB191:CDB$ROOT:>alter  pluggable database orclpdb save state;

 SYS @ DB191:CDB$ROOT:>-- Create PDB from reflink copy
 SYS @ DB191:CDB$ROOT:>create pluggable database test01 as clone using '/u02/xml/cloneplug_orclpdb..xml' source_file_directory='/u02/reflink/test01' move;
 SYS @ DB191:CDB$ROOT:>alter  pluggable database test01 open read write;
 SYS @ DB191:CDB$ROOT:>alter  pluggable database test01 save state;

PDB + Reflinks: Analyse des Ergebnis

Wie sehen nun die PDBs aus?

20:08:05 SYS @ DB191:CDB$ROOT:>@pdb/vpdb                                                                              
 NAME                 GUID                                        DBID       OPEN_MODE  RESTRICTED RECOVERY          TS_MB
 ORCLPDB              C32DCD521B161DA1E053B538A8C099D0            3576011439 READ WRITE NO         ENABLED            2934
 PDB$SEED             C316417B89B563B9E053B538A8C06316            1591867463 READ ONLY  NO         ENABLED             876
 TEST01               C32DEE49EFBA2914E053B538A8C06777            3999968580 READ WRITE NO         ENABLED            2934
                                                                                                                      6744

Ein Blick auf das Filesystem:

vorher:
[oracle@db191 sql]$df /u02
 Filesystem                      1K-blocks    Used Available Use% Mounted on
 /dev/mapper/vgoradata-lvoradata  31437828 9534772  21903056  31% /u02

nachher:
Filesystem                      1K-blocks    Used Available Use% Mounted on
 /dev/mapper/vgoradata-lvoradata  31437828 9576876  21860952  31% /u02

Für die neu erzeugte PDB mit 2934 MB wurden durch den ‚cp –reflink‘ nur ~40MB Platz belegt.

Wie sieht es denn nun mit du aus?

[oracle@db191 sql]$cd /u02/oradata/DB19U1/
 [oracle@db191 DB19U1]$du
 3358632 ./datafile
 230412  ./onlinelog
 18576   ./controlfile
 2974736 ./C32DCD521B161DA1E053B538A8C099D0/datafile
 2974736 ./C32DCD521B161DA1E053B538A8C099D0
 2972500 ./C32DEE49EFBA2914E053B538A8C06777/datafile
 2972500 ./C32DEE49EFBA2914E053B538A8C06777
 9554856 .

Neben der enormen Plattersparnis hat der Klon ~ 1 Minute benötigt. Die Laufzeit hängt wesentlich von der Datendateianzahl ab, der Oracle beim unplug und plugin diverse Validierungen durch führt, die von der Dateianzahl abhängig ist.

Kann man prüfen, wie der reflink-Anteil in den Dateien wirklich aussieht?

[oracle@db191 DB19U1]$xfs_bmap -v ./C32DEE49EFBA2914E053B538A8C06777/datafile/o1_mf_test_jbtontj2_.dbf

 ./C32DEE49EFBA2914E053B538A8C06777/datafile/o1_mf_test_jbtontj2_.dbf:
  EXT: FILE-OFFSET        BLOCK-RANGE       AG AG-OFFSET             TOTAL
    0: [0..15]:           7617648..7617663   0 (7617648..7617663)       16 100000
    1: [16..31]:          8746000..8746015   0 (8746000..8746015)       16
    2: [32..57343]:       8746016..8803327   0 (8746016..8803327)    57312 100000
    3: [57344..98303]:    8823936..8864895   0 (8823936..8864895)    40960 100000
    4: [98304..116735]:   8920512..8938943   0 (8920512..8938943)    18432 100000
    5: [116736..118783]:  9050544..9052591   0 (9050544..9052591)     2048 100000
    6: [118784..122879]:  9067360..9071455   0 (9067360..9071455)     4096 100000
    7: [122880..321535]:  9162288..9360943   0 (9162288..9360943)   198656 100000
    8: [321536..462847]:  9364016..9505327   0 (9364016..9505327)   141312 100000
    9: [462848..602111]:  9506752..9646015   0 (9506752..9646015)   139264 100000
   10: [602112..604159]:  9647536..9649583   0 (9647536..9649583)     2048 100000
   11: [604160..610303]:  9652064..9658207   0 (9652064..9658207)     6144 100000
   12: [610304..612351]:  9659456..9661503   0 (9659456..9661503)     2048 100000
   13: [612352..624639]:  9662160..9674447   0 (9662160..9674447)    12288 100000
   14: [624640..647167]:  9676080..9698607   0 (9676080..9698607)    22528 100000
   15: [647168..655359]:  9701552..9709743   0 (9701552..9709743)     8192 100000
   16: [655360..667647]:  9711712..9723999   0 (9711712..9723999)    12288 100000
   17: [667648..686079]:  9732384..9750815   0 (9732384..9750815)    18432 100000
   18: [686080..688127]:  9757216..9759263   0 (9757216..9759263)     2048 100000
   19: [688128..690175]:  9766416..9768463   0 (9766416..9768463)     2048 100000
   20: [690176..692223]:  9771584..9773631   0 (9771584..9773631)     2048 100000
   21: [692224..696319]:  9773640..9777735   0 (9773640..9777735)     4096 100000
   22: [696320..698367]:  9872816..9874863   0 (9872816..9874863)     2048 100000
   23: [698368..4194319]: 9874872..13370823  0 (9874872..13370823) 3495952 100000

Wenn am Ende einer Zeile ‚100000‘ steht, dann ist der Dateiinhalt mit der Quelle geteilt. Nun könnte man den gleichen Befehl auf die Datei der Quell-PDB anwenden und würde dort die gleichen Blockpositionen für geteilte Bereiche sehen.

Am Anfang der Datei ist deutlich erkennbar, das Oracle beim Plugin der PDB die Datendateiheader angepaßt hat, daas sich durch den Plugin u.a. die DBID und Checkpoint-SCN geändert hat.

Total entspricht Anzahl Blöcke mit 512 bytes.

PDB + Reflinks: nachträgliche Deduplikation – geht das?

Es gibt tatsächlich die Möglichkeit, nachträglich Blöcke wieder zu gemeinsamen Bereichen zusammen zu führen.

Auf github gibt es dafür das Projekt duperemove.

Wichtige Hinweise!

  • immer alle Daten das Filesystems vor dem Start sichern
    Normalerweise ist duperemove sicher, weil es offizielle Schnittstellen im Kernel verwendet.
  • niemals Daten während der Ausführung ändern
    Duperemove nutzt eine ioctl vom Kernel. Dort wird beim Ändern der Blockbelegung der Bereich in der Datei vor Änderungen gesperrt, damit keine parallelen Änderungen möglich sind.
    Ich habe es nicht getestet, weshalb ich duperemove m laufenden Betrieb nicht empfehlen würde – auch wenn es technisch möglich sein sollte.
  • duperemove verwendet offizielle Schnittstellen des Kernels zur Deduplikation
  • Das Tool wird auf eigene Gefahr verwendet
  • Je nach Filesystemgröße kann duperemove durchaus Stunden benötigen.
  • duperemove ist für pdb$seed interessant
    Direkt nach dem Erzeugen der CDB über das Filesystem duperemove laufen lassen und sich freuen, das große Teile der pdb$seed dedupliziert werden, da Inhalte zur CDB sehr ähnlich sind.
  • duperemove ist im EPEL-Repository enthalten
    Einfach das RPM duperemove installieren

duperemove -r -d /u02/oradata

Zusammenfassung

Es gab einen Einblick in folgende Themen:

Nun wünsche ich viel Erfolg beim Experimentieren und diskutieren über weitere Möglichkeiten.

Kommentar verfassen

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.

%d Bloggern gefällt das: