Quantcast
Channel: dbi Blog
Viewing all 2878 articles
Browse latest View live

Password rolling change before Oracle 21c

$
0
0

By Franck Pachot

.
You may have read about Gradual Password Rollover usage from Mouhamadou Diaw and about some internals from Rodrigo Jorge. But it works only on 21c which is only in the cloud, for the moment, in Autonomous Database and DBaaS (but here I’ve encountered some problems apparently because of a bug when using SQL*Net native encryption). But your production is not yet in 21c anyway. However, here is how you can achieve a similar goal in 12c,18c or 19c: be able to connect with two passwords for the time window where you are changing the password in a rolling fashion in the application server configuration.

Proxy User

If your application still connects with the application owner, you do it wrong. Even when it needs to be connected in the application schema by default, and even when you can’t to an “alter session set current_schema” you don’t have to use this user for authentication. And this is really easy with proxy users. Consider the application owner as a schema, not as a user to connect with.

My application is in schema DEMO and I’ll not use DEMO credentials. You can set an impossible password or, better, in 18c, set no password at all. I’ll use a proxy user authentication to connect to this DEMO user:


19:28:49 DEMO@atp1_tp> grant create session to APP2020 identified by "2020 was a really Bad Year!";
Grant succeeded.

19:28:50 DEMO@atp1_tp> alter user DEMO grant connect through APP2020;
User DEMO altered.

The APP2020 user is the one I’ll use. I named it 2020 because I want to change the credentials every year and, as I don’t have the gradual rollover password feature, this means changing the user to connect with.


19:28:50 DEMO@atp1_tp> connect APP2020/"2020 was a really Bad Year!"@atp1_tp
Connected.
19:28:52 APP2020@atp1_tp> show user
USER is "APP2020"

This user can connect as usual, as it has the CREATE SESSION privilege. There is a way to prevent this and allow PROXY ONLY CONNECT, but this is unfortunately not documented (Miguel Anjo has written about this) so better not using it.

However, the most important is:


19:28:52 APP2020@atp1_tp> connect APP2020[DEMO]/"2020 was a really Bad Year!"@atp1_tp
Connected.

19:28:53 DEMO@atp1_tp> show user
USER is "DEMO"

With proxy connection, in addition to the proxy user credentials I mention the final user I want to connect to, though this proxy user. Now I’m in the exact same state as if I connected with the DEMO user.

No authentication


19:28:54 ADMIN@atp1_tp> alter user DEMO no authentication;
User DEMO altered.

As we don’t connect through this user anymore (and once I’m sure no application uses it) the best is to set it with NO AUTHENTICATION.

New proxy user

Now that the application uses this APP2020 for months, I want to change the password. I’ll add a new proxy user for that:


19:28:54 ADMIN@atp1_tp> show user
USER is "ADMIN"

19:28:53 ADMIN@atp1_tp> grant create session to APP2021 identified by "Best Hopes for 2021 :)";
Grant succeeded.

19:28:54 ADMIN@atp1_tp> alter user DEMO grant connect through APP2021;
User DEMO altered.

Here I have another proxy user that can be used to connect to DEMO, in addition to the existing one


19:28:54 ADMIN@atp1_tp> connect APP2020[DEMO]/"2020 was a really Bad Year!"@atp1_tp
Connected.

19:28:55 DEMO@atp1_tp> show user
USER is "DEMO"

19:28:55 DEMO@atp1_tp> connect APP2021[DEMO]/"Best Hopes for 2021 :)"@atp1_tp
Connected.

19:28:56 DEMO@atp1_tp> show user
USER is "DEMO"

During this time, I can use both credentials. This gives me enough time to change all application server configuration one by one, without any downtime for the application.

Lock previous account


19:30:00 ADMIN@atp1_tp> 
 select username,account_status,last_login,password_change_date,proxy_only_connect 
 from dba_users where username like 'APP____';

   USERNAME    ACCOUNT_STATUS                                       LAST_LOGIN    PASSWORD_CHANGE_DATE    PROXY_ONLY_CONNECT
___________ _________________ ________________________________________________ _______________________ _____________________
APP2020     OPEN              27-DEC-20 07.28.55.000000000 PM EUROPE/ZURICH    27-DEC-20               N
APP2021     OPEN              27-DEC-20 07.28.56.000000000 PM EUROPE/ZURICH    27-DEC-20               N

After a while, I can validate that the old user is not used anymore. If you have a connection recycling duration in the connection pool (you should) you can rely on last login.


19:30:00 ADMIN@atp1_tp> alter user APP2020 account lock;
User APP2020 altered.

Before dropping it, just lock the account, easier to keep track of it and unlock it quickly if anyone encounters a problem


19:30:00 ADMIN@atp1_tp> connect APP2020[DEMO]/"2020 was a really Bad Year!"@atp1_tp
Error starting at line : 30 File @ /home/opc/demo/tmp/proxy_to_rollover.sql
In command -
  connect ...
Error report -
Connection Failed
  USER          = APP2020[DEMO]
  URL           = jdbc:oracle:thin:@atp1_tp
  Error Message = ORA-28000: The account is locked.
Commit

If someone tries to connect with the old password, he will know that the user is locked.


19:30:01 @> connect APP2021[DEMO]/"Best Hopes for 2021 :)"@atp1_tp
Connected.
19:30:02 DEMO@atp1_tp> show user
USER is "DEMO"

Once the old user locked, only the new one is able to connect, with the new user credentials. As this operation can be done with no application downtime, you can do it frequently. From a security point of view, you must change passwords frequently. For end-user passwords, you can set a lifetime, and grace period. But not for system users as the warning may not be cached. Better change them proactively.

Cet article Password rolling change before Oracle 21c est apparu en premier sur Blog dbi services.


Building a network bonding between 2 cards on Oracle Linux

$
0
0

I recently needed to configure bonding between 2 network cards on a customer side and I wanted trough this blog to share my findings and how I built it showing some traces. I will also do a short comparison of what is possible or not on the ODA.

Why should I use bonding?

Bonding is a technology which will allow you to merge several network interfaces, either ports of the same cards or ports from separated network cards, into a same logical interface. Purposes would be to have some network redundancy in case of network failure, called fault tolerance, or to increase the network throughput (bandwidth), called load balancing.

What bonding mode should I use?

There are 7 bonding modes available to achieve these purposes. All bonding modes will guarantee fault tolerance. Some bonding modes will have load balancing functionnalities. For bonding mode 4 the switch will need to support links aggregation (EtherChannel). Link aggregation can be configured manually on the switch or automatically using LACP protocol (dynamic links aggregation).

Mode Description Fault tolerance Load balancing
0 Round-Robin Packets are sequentially transmitted and received through each interfaces one by one. YES YES
1 Active-backup Only one interface will be the active one. The other interface from the bonding configuration will be configured as backup. If the active interface will be in failure one of the backup interface will become the active one. The MAC address will only be visible on one port at the same time to avoid any confusion for the switch. YES NO
2 Balance-xor Peer connections are matched with MAC addresses of the slave interfaces. Once the connection is established the transmission of the peers is always sent over the same slave interface. YES YES
3 Broadcast All network transmissions are sent on all slaves. YES NO
4 802.3ad – Dynamic Link Aggregation This mode will aggregate all interfaces from the bonding into a logical one. The traffic is sent and received on all slaves from the aggregation. The switch needs to support LACP and LACP needs to be activated. YES YES
5 TLB – Transmit Load Balancing The outgoing traffic is distributed between all interfaces depending of the current load of each slave interface. Incoming traffic is received by the current active slave. In case the active interface fails, another slave will take over the MAC address of the failed interface. YES YES
6 ALB – Adaptive Load Balancing This mode includes TLB (Transmit Load Balancing) and will use RLB (Receive Load Balancing) as well. The load balanced for the received packets will be done through ARP (Address Resolution Protocol) negotiation. YES YES

In my case, our customer wanted to guarantee the service in case of one network card failure only. No load balancing. The switch was not configured to use LACP. I then decided to configure the bonding in active-backup mode, which will guarantee redundancy only.

Bonding configuration

Checking existing connection

The server is composed of 2 network cards having each of the card 4 interfaces (ports).
Card 1 : em1, em2, em3, em4
Card 2 : p4p1, p4p2, p4p3, p4p4

There is no bonding currently existing as shown in below output.

[root@SRV ~]# nmcli connection
NAME  UUID                                  TYPE      DEVICE
p4p1  d3cdc8f5-2d80-433d-9502-3b357c57f307  ethernet  p4p1
em1   f412b74b-2160-4914-b716-88f6b4d58c1f  ethernet  --
em2   0ab78e63-bde7-4c77-b455-7dcb1d5c6813  ethernet  --
em3   d6569615-322f-477b-9693-b42ee3dbe21e  ethernet  --
em4   52949f94-52d1-463e-ba32-06c272c07ce0  ethernet  --
p4p2  12f01c70-4aab-42db-b0e8-b5422e43c1b9  ethernet  --
p4p3  0db2f5b9-d968-44cb-a042-cff20f112ed4  ethernet  --
p4p4  a2a0ebc4-ca74-452e-94ba-6d5fedbfdf28  ethernet  --

Checking existing configuration

The server was configured only with one IP address on the p4p1 network interface.

[root@SRV network-scripts]# pwd
/etc/sysconfig/network-scripts

[root@SRV network-scripts]# ls -l ifcfg*
-rw-r--r--. 1 root root 275 Sep 21 17:09 ifcfg-em1
-rw-r--r--. 1 root root 275 Sep 21 17:09 ifcfg-em2
-rw-r--r--. 1 root root 275 Sep 21 17:09 ifcfg-em3
-rw-r--r--. 1 root root 275 Sep 21 17:09 ifcfg-em4
-rw-r--r--. 1 root root 254 Aug 19  2019 ifcfg-lo
-rw-r--r--. 1 root root 378 Sep 21 17:09 ifcfg-p4p1
-rw-r--r--. 1 root root 277 Sep 21 17:09 ifcfg-p4p2
-rw-r--r--. 1 root root 277 Sep 21 17:09 ifcfg-p4p3
-rw-r--r--. 1 root root 277 Sep 21 17:09 ifcfg-p4p4

[root@SRV network-scripts]# more ifcfg-p4p1
TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=none
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=p4p1
UUID=d3cdc8f5-2d80-433d-9502-3b357c57f307
DEVICE=p4p1
ONBOOT=yes
IPADDR=192.168.1.180
PREFIX=24
GATEWAY=192.168.1.1
DNS1=192.168.1.5
DOMAIN=domain.com
IPV6_PRIVACY=no

Creating the bonding

Purpose is to create a bonding between the 2 network cards for fault tolerance. The bonding will then be composed of the slave interfaces p4p1 and em1.
The bonding mode selected will be the mode 1 (active-backup).

[root@SRV network-scripts]# nmcli con add type bond con-name bond1 ifname bond1 mode active-backup ip4 192.168.1.180/24
Connection 'bond1' (7b736616-f72d-46b7-b4eb-01468639889b) successfully added.

[root@SRV network-scripts]# nmcli conn
NAME   UUID                                  TYPE      DEVICE
p4p1   d3cdc8f5-2d80-433d-9502-3b357c57f307  ethernet  p4p1
bond1  7b736616-f72d-46b7-b4eb-01468639889b  bond      bond1
em1    f412b74b-2160-4914-b716-88f6b4d58c1f  ethernet  --
em2    0ab78e63-bde7-4c77-b455-7dcb1d5c6813  ethernet  --
em3    d6569615-322f-477b-9693-b42ee3dbe21e  ethernet  --
em4    52949f94-52d1-463e-ba32-06c272c07ce0  ethernet  --
p4p2   12f01c70-4aab-42db-b0e8-b5422e43c1b9  ethernet  --
p4p3   0db2f5b9-d968-44cb-a042-cff20f112ed4  ethernet  --
p4p4   a2a0ebc4-ca74-452e-94ba-6d5fedbfdf28  ethernet  --

Updating the bonding with appropriate gateway, dns and domain information

[root@SRV network-scripts]# cat ifcfg-bond1
BONDING_OPTS=mode=active-backup
TYPE=Bond
BONDING_MASTER=yes
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=none
IPADDR=192.168.1.180
PREFIX=24
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=bond1
UUID=7b736616-f72d-46b7-b4eb-01468639889b
DEVICE=bond1
ONBOOT=yes

[root@SRV network-scripts]# vi ifcfg-bond1

[root@SRV network-scripts]# cat ifcfg-bond1
BONDING_OPTS=mode=active-backup
TYPE=Bond
BONDING_MASTER=yes
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=none
IPADDR=192.168.1.180
PREFIX=24
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=bond1
UUID=7b736616-f72d-46b7-b4eb-01468639889b
DEVICE=bond1
ONBOOT=yes
GATEWAY=192.168.1.1
DNS1=192.168.1.5
DOMAIN=domain.com

Adding slave interface em1 in the bonding bond1

Each slaves needs to be added to the master bonding.

We will first delete existing em1 slave :

[root@SRV network-scripts]# nmcli con delete em1
Connection 'em1' (f412b74b-2160-4914-b716-88f6b4d58c1f) successfully deleted.

We will then create new em1 interface part of the bond1 bonding configuration :

[root@SRV network-scripts]# nmcli con add type bond-slave ifname em1 con-name em1 master bond1
Connection 'em1' (8c72c383-e1e9-4e4b-ac2f-3d3d81d5159b) successfully added.

And we can check the interfaces :

[root@SRV network-scripts]# nmcli con
NAME   UUID                                  TYPE      DEVICE
p4p1   d3cdc8f5-2d80-433d-9502-3b357c57f307  ethernet  p4p1
bond1  7b736616-f72d-46b7-b4eb-01468639889b  bond      bond1
em1    8c72c383-e1e9-4e4b-ac2f-3d3d81d5159b  ethernet  em1
em2    0ab78e63-bde7-4c77-b455-7dcb1d5c6813  ethernet  --
em3    d6569615-322f-477b-9693-b42ee3dbe21e  ethernet  --
em4    52949f94-52d1-463e-ba32-06c272c07ce0  ethernet  --
p4p2   12f01c70-4aab-42db-b0e8-b5422e43c1b9  ethernet  --
p4p3   0db2f5b9-d968-44cb-a042-cff20f112ed4  ethernet  --
p4p4   a2a0ebc4-ca74-452e-94ba-6d5fedbfdf28  ethernet  --

Activating the bonding

We need to first activate the first configured slaves :

[root@SRV network-scripts]# nmcli con up em1
Connection successfully activated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/4)

We can now activate the bonding :

[root@SRV network-scripts]# nmcli con up bond1
Connection successfully activated (master waiting for slaves) (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/5)

We can check the connections :

[root@SRV network-scripts]# nmcli con
NAME   UUID                                  TYPE      DEVICE
p4p1   d3cdc8f5-2d80-433d-9502-3b357c57f307  ethernet  p4p1
bond1  7b736616-f72d-46b7-b4eb-01468639889b  bond      bond1
em1    8c72c383-e1e9-4e4b-ac2f-3d3d81d5159b  ethernet  em1
em2    0ab78e63-bde7-4c77-b455-7dcb1d5c6813  ethernet  --
em3    d6569615-322f-477b-9693-b42ee3dbe21e  ethernet  --
em4    52949f94-52d1-463e-ba32-06c272c07ce0  ethernet  --
p4p2   12f01c70-4aab-42db-b0e8-b5422e43c1b9  ethernet  --
p4p3   0db2f5b9-d968-44cb-a042-cff20f112ed4  ethernet  --
p4p4   a2a0ebc4-ca74-452e-94ba-6d5fedbfdf28  ethernet  --

Adding slave interface p4p1 in the bonding bond1

We will first delete existing p4p1 slave :

[root@SRV network-scripts]# nmcli con delete p4p1
Connection 'p4p1' (d3cdc8f5-2d80-433d-9502-3b357c57f307) successfully deleted.

[root@SRV network-scripts]# nmcli con
NAME   UUID                                  TYPE      DEVICE
bond1  7b736616-f72d-46b7-b4eb-01468639889b  bond      bond1
em1    8c72c383-e1e9-4e4b-ac2f-3d3d81d5159b  ethernet  em1
em2    0ab78e63-bde7-4c77-b455-7dcb1d5c6813  ethernet  --
em3    d6569615-322f-477b-9693-b42ee3dbe21e  ethernet  --
em4    52949f94-52d1-463e-ba32-06c272c07ce0  ethernet  --
p4p2   12f01c70-4aab-42db-b0e8-b5422e43c1b9  ethernet  --
p4p3   0db2f5b9-d968-44cb-a042-cff20f112ed4  ethernet  --
p4p4   a2a0ebc4-ca74-452e-94ba-6d5fedbfdf28  ethernet  --

We will then create new p4p1 interface part of the bond1 bonding configuration :

[root@SRV network-scripts]# nmcli con add type bond-slave ifname p4p1 con-name p4p1 master bond1
Connection 'p4p1' (efef0972-4b3f-46a2-b054-ebd1aa201056) successfully added.

And we can check the interfaces :

[root@SRV network-scripts]# nmcli con
NAME   UUID                                  TYPE      DEVICE
bond1  7b736616-f72d-46b7-b4eb-01468639889b  bond      bond1
em1    8c72c383-e1e9-4e4b-ac2f-3d3d81d5159b  ethernet  em1
p4p1   efef0972-4b3f-46a2-b054-ebd1aa201056  ethernet  p4p1
em2    0ab78e63-bde7-4c77-b455-7dcb1d5c6813  ethernet  --
em3    d6569615-322f-477b-9693-b42ee3dbe21e  ethernet  --
em4    52949f94-52d1-463e-ba32-06c272c07ce0  ethernet  --
p4p2   12f01c70-4aab-42db-b0e8-b5422e43c1b9  ethernet  --
p4p3   0db2f5b9-d968-44cb-a042-cff20f112ed4  ethernet  --
p4p4   a2a0ebc4-ca74-452e-94ba-6d5fedbfdf28  ethernet  --

Activating the new p4p1 slave interface

We can now activate the next recently added slaves :

[root@SRV network-scripts]# nmcli con up p4p1
Connection successfully activated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/11)

Restart the network service

We will restart the network service to have the new bonding configuration taking into account :

[root@SRV network-scripts]# service network restart
Restarting network (via systemctl):                        [  OK  ]

We can check the IP configuration :

[root@SRV network-scripts]# ip addr sh
1: lo:  mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: em1:  mtu 1500 qdisc mq master bond1 state UP group default qlen 1000
    link/ether bc:97:e1:5b:e4:50 brd ff:ff:ff:ff:ff:ff
3: em3:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether bc:97:e1:5b:e4:4e brd ff:ff:ff:ff:ff:ff
4: em2:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether bc:97:e1:5b:e4:51 brd ff:ff:ff:ff:ff:ff
5: em4:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether bc:97:e1:5b:e4:4f brd ff:ff:ff:ff:ff:ff
6: p4p1:  mtu 1500 qdisc mq master bond1 state UP group default qlen 1000
    link/ether bc:97:e1:5b:e4:50 brd ff:ff:ff:ff:ff:ff
7: p4p2:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether 3c:fd:fe:85:0d:31 brd ff:ff:ff:ff:ff:ff
8: p4p3:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether 3c:fd:fe:85:0d:32 brd ff:ff:ff:ff:ff:ff
9: p4p4:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether 3c:fd:fe:85:0d:33 brd ff:ff:ff:ff:ff:ff
11: bond1:  mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether bc:97:e1:5b:e4:50 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.180/24 brd 192.168.1.255 scope global noprefixroute bond1
       valid_lft forever preferred_lft forever
    inet6 fe80::b4f9:e44d:25fc:3a6/64 scope link noprefixroute
       valid_lft forever preferred_lft forever

Check IP configuration files

We are now having our bond ifcfg configuration file :

[root@SRV ~]# cd /etc/sysconfig/network-scripts

[root@SRV network-scripts]# pwd
/etc/sysconfig/network-scripts

[root@SRV network-scripts]# ls -ltrh ifcfg*
-rw-r--r--. 1 root root 254 Aug 19  2019 ifcfg-lo
-rw-r--r--. 1 root root 277 Sep 21 17:09 ifcfg-p4p4
-rw-r--r--. 1 root root 277 Sep 21 17:09 ifcfg-p4p2
-rw-r--r--. 1 root root 275 Sep 21 17:09 ifcfg-em4
-rw-r--r--. 1 root root 275 Sep 21 17:09 ifcfg-em3
-rw-r--r--. 1 root root 277 Sep 21 17:09 ifcfg-p4p3
-rw-r--r--. 1 root root 275 Sep 21 17:09 ifcfg-em2
-rw-r--r--. 1 root root 411 Oct  7 16:45 ifcfg-bond1
-rw-r--r--. 1 root root 110 Oct  7 16:46 ifcfg-em1
-rw-r--r--. 1 root root 112 Oct  7 16:50 ifcfg-p4p1

The bonding file will have the IP configuration :

[root@SRV network-scripts]# cat ifcfg-bond1
BONDING_OPTS=mode=active-backup
TYPE=Bond
BONDING_MASTER=yes
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=none
IPADDR=192.168.1.180
PREFIX=24
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=bond1
UUID=7b736616-f72d-46b7-b4eb-01468639889b
DEVICE=bond1
ONBOOT=yes
GATEWAY=192.168.1.1
DNS1=192.168.1.5
DOMAIN=domain.com

p4p1 interface will be one of the bond1 slave :

[root@SRV network-scripts]# cat ifcfg-p4p1
TYPE=Ethernet
NAME=p4p1
UUID=efef0972-4b3f-46a2-b054-ebd1aa201056
DEVICE=p4p1
ONBOOT=yes
MASTER=bond1
SLAVE=yes

em1 interface from the other physical network card will be the next bond1 slave :

[root@SRV network-scripts]# cat ifcfg-em1
TYPE=Ethernet
NAME=em1
UUID=8c72c383-e1e9-4e4b-ac2f-3d3d81d5159b
DEVICE=em1
ONBOOT=yes
MASTER=bond1
SLAVE=yes

Check bonding interfaces and mode

[root@SRV network-scripts]# cat /proc/net/bonding/bond1
Ethernet Channel Bonding Driver: v3.7.1 (April 27, 2011)

Bonding Mode: fault-tolerance (active-backup)
Primary Slave: None
Currently Active Slave: em1
MII Status: up
MII Polling Interval (ms): 100
Up Delay (ms): 0
Down Delay (ms): 0

Slave Interface: em1
MII Status: up
Speed: 1000 Mbps
Duplex: full
Link Failure Count: 1
Permanent HW addr: bc:97:e1:5b:e4:50
Slave queue ID: 0

Slave Interface: p4p1
MII Status: up
Speed: 1000 Mbps
Duplex: full
Link Failure Count: 1
Permanent HW addr: 3c:fd:fe:85:0d:30
Slave queue ID: 0
[root@SRV network-scripts]#

Test the bonding

Both network cables are plugged into em1 and p4p1. Both interfaces are UP. :

[root@SRV network-scripts]# ip addr sh
1: lo:  mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: em1:  mtu 1500 qdisc mq master bond1 state UP group default qlen 1000
    link/ether bc:97:e1:5b:e4:50 brd ff:ff:ff:ff:ff:ff
3: em3:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether bc:97:e1:5b:e4:4e brd ff:ff:ff:ff:ff:ff
4: em2:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether bc:97:e1:5b:e4:51 brd ff:ff:ff:ff:ff:ff
5: em4:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether bc:97:e1:5b:e4:4f brd ff:ff:ff:ff:ff:ff
6: p4p1:  mtu 1500 qdisc mq master bond1 state UP group default qlen 1000
    link/ether bc:97:e1:5b:e4:50 brd ff:ff:ff:ff:ff:ff
7: p4p2:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether 3c:fd:fe:85:0d:31 brd ff:ff:ff:ff:ff:ff
8: p4p3:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether 3c:fd:fe:85:0d:32 brd ff:ff:ff:ff:ff:ff
9: p4p4:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether 3c:fd:fe:85:0d:33 brd ff:ff:ff:ff:ff:ff
15: bond1:  mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether bc:97:e1:5b:e4:50 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.180/24 brd 192.168.1.255 scope global noprefixroute bond1
       valid_lft forever preferred_lft forever
    inet6 fe80::b4f9:e44d:25fc:3a6/64 scope link noprefixroute
       valid_lft forever preferred_lft forever

Pinging the server is OK :

[ansible@linux-ansible / ]$ ping 192.168.1.180
PING 192.168.1.180 (192.168.1.180) 56(84) bytes of data.
64 bytes from 192.168.1.180: icmp_seq=1 ttl=64 time=0.206 ms
64 bytes from 192.168.1.180: icmp_seq=2 ttl=64 time=0.290 ms
64 bytes from 192.168.1.180: icmp_seq=3 ttl=64 time=0.152 ms
64 bytes from 192.168.1.180: icmp_seq=4 ttl=64 time=0.243 ms

I have plug out the cable from the em1 interface. We can see em1 interface DOWN and p4p1 interface UP :

[root@SRV network-scripts]# ip addr sh
1: lo:  mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: em1:  mtu 1500 qdisc mq master bond1 state DOWN group default qlen 1000
    link/ether bc:97:e1:5b:e4:50 brd ff:ff:ff:ff:ff:ff
3: em3:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether bc:97:e1:5b:e4:4e brd ff:ff:ff:ff:ff:ff
4: em2:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether bc:97:e1:5b:e4:51 brd ff:ff:ff:ff:ff:ff
5: em4:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether bc:97:e1:5b:e4:4f brd ff:ff:ff:ff:ff:ff
6: p4p1:  mtu 1500 qdisc mq master bond1 state UP group default qlen 1000
    link/ether bc:97:e1:5b:e4:50 brd ff:ff:ff:ff:ff:ff
7: p4p2:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether 3c:fd:fe:85:0d:31 brd ff:ff:ff:ff:ff:ff
8: p4p3:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether 3c:fd:fe:85:0d:32 brd ff:ff:ff:ff:ff:ff
9: p4p4:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether 3c:fd:fe:85:0d:33 brd ff:ff:ff:ff:ff:ff
15: bond1:  mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether bc:97:e1:5b:e4:50 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.180/24 brd 192.168.1.255 scope global noprefixroute bond1
       valid_lft forever preferred_lft forever
    inet6 fe80::b4f9:e44d:25fc:3a6/64 scope link noprefixroute
       valid_lft forever preferred_lft forever

pinging the server is still OK :

[ansible@linux-ansible / ]$ ping 192.168.1.180
PING 192.168.1.180 (192.168.1.180) 56(84) bytes of data.
64 bytes from 192.168.1.180: icmp_seq=1 ttl=64 time=0.234 ms
64 bytes from 192.168.1.180: icmp_seq=2 ttl=64 time=0.256 ms
64 bytes from 192.168.1.180: icmp_seq=3 ttl=64 time=0.257 ms
64 bytes from 192.168.1.180: icmp_seq=4 ttl=64 time=0.245 ms

I have then plug in the cable in em1 interface again and plug out the cable from the p4p1 interface. We can see em1 interface now UP again and p4p1 interface DOWN :

[root@SRV network-scripts]# ip addr sh
1: lo:  mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: em1:  mtu 1500 qdisc mq master bond1 state UP group default qlen 1000
    link/ether bc:97:e1:5b:e4:50 brd ff:ff:ff:ff:ff:ff
3: em3:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether bc:97:e1:5b:e4:4e brd ff:ff:ff:ff:ff:ff
4: em2:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether bc:97:e1:5b:e4:51 brd ff:ff:ff:ff:ff:ff
5: em4:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether bc:97:e1:5b:e4:4f brd ff:ff:ff:ff:ff:ff
6: p4p1:  mtu 1500 qdisc mq master bond1 state DOWN group default qlen 1000
    link/ether bc:97:e1:5b:e4:50 brd ff:ff:ff:ff:ff:ff
7: p4p2:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether 3c:fd:fe:85:0d:31 brd ff:ff:ff:ff:ff:ff
8: p4p3:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether 3c:fd:fe:85:0d:32 brd ff:ff:ff:ff:ff:ff
9: p4p4:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether 3c:fd:fe:85:0d:33 brd ff:ff:ff:ff:ff:ff
15: bond1:  mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether bc:97:e1:5b:e4:50 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.180/24 brd 192.168.1.255 scope global noprefixroute bond1
       valid_lft forever preferred_lft forever
    inet6 fe80::b4f9:e44d:25fc:3a6/64 scope link noprefixroute
       valid_lft forever preferred_lft forever

pinging the server is still OK :

[ansible@linux-ansible / ]$ ping 192.168.1.180
PING 192.168.1.180 (192.168.1.180) 56(84) bytes of data.
64 bytes from 192.168.1.180: icmp_seq=1 ttl=64 time=0.159 ms
64 bytes from 192.168.1.180: icmp_seq=2 ttl=64 time=0.219 ms
64 bytes from 192.168.1.180: icmp_seq=3 ttl=64 time=0.362 ms
64 bytes from 192.168.1.180: icmp_seq=4 ttl=64 time=0.236 ms

And what about the ODA?

This configuration has been setup at one customer system running DELL servers. I have been deploying several ODAs by other customers and the questionning of having fault tolerance between several network cards is often coming. Unfortunately, and albeit the ODA are running Oracle Linux operation system, such configuration is not supported on the appliance. The Appliance will only support active-backup between ports of the same network cards. Additionnal network cards will be used on the ODA to have additionnal network connections. Last but not least, LACP is not supported on the appliance.

Cet article Building a network bonding between 2 cards on Oracle Linux est apparu en premier sur Blog dbi services.

Optimizer Statistics Gathering – pending and history

$
0
0

By Franck Pachot

.
This was initially posted to CERN Database blog on Wednesday, 12 September 2018 where it seems to be lost. Here is a copy thanks to web.archive.org

Demo table

I create a table for the demo. The CTAS gathers statistics (12c online statistics gathering) with one row and then I insert more rows:



10:33:56 SQL> create table DEMO as select rownum n from dual;
Table DEMO created.
10:33:56 SQL> insert into DEMO select rownum n from xmltable('1 to 41');
41 rows inserted.
10:33:56 SQL> commit;
Commit complete.

The estimations are stale: estimates 1 row (E-Rows) but 42 actual rows (A-Rows)



10:33:56 SQL> select /*+ gather_plan_statistics */ count(*) from DEMO;

  COUNT(*) 
  -------- 
        42 

10:33:57 SQL> select * from table(dbms_xplan.display_cursor(format=>'basic +rows +rowstats last'));

PLAN_TABLE_OUTPUT                                                
-----------------                                                
EXPLAINED SQL STATEMENT:                                         
------------------------                                         
select /*+ gather_plan_statistics */ count(*) from DEMO          
                                                                 
Plan hash value: 2180342005                                      
                                                                 
--------------------------------------------------------------   
| Id  | Operation          | Name | Starts | E-Rows | A-Rows |   
--------------------------------------------------------------   
|   0 | SELECT STATEMENT   |      |      1 |        |      1 |   
|   1 |  SORT AGGREGATE    |      |      1 |      1 |      1 |   
|   2 |   TABLE ACCESS FULL| DEMO |      1 |      1 |     42 |   
--------------------------------------------------------------   

Pending Statistics

Here we are: I want to gather statistics on this table. But I will lower all risks by not publishing them immediately. Current statistics preferences are set to PUBLISH=TRUE:



10:33:58 SQL> select num_rows,cast(last_analyzed as timestamp),dbms_stats.get_prefs('PUBLISH',owner,table_name) from dba_tab_statistics where owner='DEMO' and table_name in ('DEMO');

  NUM_ROWS CAST(LAST_ANALYZEDASTIMESTAMP)    DBMS_STATS.GET_PREFS('PUBLISH',OWNER,TABLE_NAME)   
  -------- ------------------------------    ------------------------------------------------   
         1 12-SEP-18 10.33.56.000000000 AM   TRUE     
                                          

I set it to FALSE:



10:33:59 SQL> exec dbms_stats.set_table_prefs('DEMO','DEMO','publish','false');

PL/SQL procedure successfully completed.

10:34:00 SQL> select num_rows,cast(last_analyzed as timestamp),dbms_stats.get_prefs('PUBLISH',owner,table_name) from dba_tab_statistics where owner='DEMO' and table_name in ('DEMO');

  NUM_ROWS CAST(LAST_ANALYZEDASTIMESTAMP)    DBMS_STATS.GET_PREFS('PUBLISH',OWNER,TABLE_NAME)   
  -------- ------------------------------    ------------------------------------------------   
         1 12-SEP-18 10.33.56.000000000 AM   FALSE  
                                            

I’m now gathering stats as I want to:



10:34:01 SQL> exec dbms_stats.gather_table_stats('DEMO','DEMO');
PL/SQL procedure successfully completed.

Test Pending Statistics

They are not published. But to test my queries with those new stats, I can set my session to use pending statistics:



10:34:02 SQL> alter session set optimizer_use_pending_statistics=true;
Session altered.

Running my query again, I can see the good estimations (E-Rows=A-Rows)



10:34:03 SQL> select /*+ gather_plan_statistics */ count(*) from DEMO;

  COUNT(*) 
  -------- 
        42 

10:34:04 SQL> select * from table(dbms_xplan.display_cursor(format=>'basic +rows +rowstats last'));

PLAN_TABLE_OUTPUT                                                
-----------------                                                
EXPLAINED SQL STATEMENT:                                         
------------------------                                         
select /*+ gather_plan_statistics */ count(*) from DEMO          
                                                                 
Plan hash value: 2180342005                                      
                                                                 
--------------------------------------------------------------   
| Id  | Operation          | Name | Starts | E-Rows | A-Rows |   
--------------------------------------------------------------   
|   0 | SELECT STATEMENT   |      |      1 |        |      1 |   
|   1 |  SORT AGGREGATE    |      |      1 |      1 |      1 |   
|   2 |   TABLE ACCESS FULL| DEMO |      1 |     42 |     42 |   
--------------------------------------------------------------   

The published statistics still show 1 row:



10:34:05 SQL> select num_rows,cast(last_analyzed as timestamp),dbms_stats.get_prefs('PUBLISH',owner,table_name) from dba_tab_statistics where owner='DEMO' and table_name in ('DEMO');

  NUM_ROWS CAST(LAST_ANALYZEDASTIMESTAMP)    DBMS_STATS.GET_PREFS('PUBLISH',OWNER,TABLE_NAME)   
  -------- ------------------------------    ------------------------------------------------   
         1 12-SEP-18 10.33.56.000000000 AM   FALSE            
                                  

But I can query the pending ones before publishing them:



10:34:05 SQL> c/dba_tab_statistics/dba_tab_pending_stats
  1* select num_rows,cast(last_analyzed as timestamp),dbms_stats.get_prefs('PUBLISH',owner,table_name) from dba_tab_pending_stats where owner='DEMO' and table_name in ('DEMO');
10:34:05 SQL> /

  NUM_ROWS CAST(LAST_ANALYZEDASTIMESTAMP)    DBMS_STATS.GET_PREFS('PUBLISH',OWNER,TABLE_NAME)   
  -------- ------------------------------    ------------------------------------------------   
        42 12-SEP-18 10.34.01.000000000 AM   FALSE          
                                    

I’ve finished my test with pending statistics:



10:34:05 SQL> alter session set optimizer_use_pending_statistics=false;
Session altered.

Note that if you have Real Application Testing, you can use SQL Performance Analyzer to test the pending statistics on a whole SQL Tuning Set representing the critical queries of your application. Of course, the more you test there, the better it is.

Delete Pending Statistics

Now let’s say that my test shows that the new statistics are not good, I can simply delete the pending statistics:



10:34:06 SQL> exec dbms_stats.delete_pending_stats('DEMO','DEMO');
PL/SQL procedure successfully completed.

Then all queries are still using the previous statistics:



10:34:07 SQL> show parameter pending
NAME                             TYPE    VALUE
-------------------------------- ------- -----
optimizer_use_pending_statistics boolean FALSE

10:34:07 SQL> select /*+ gather_plan_statistics */ count(*) from DEMO;

  COUNT(*) 
  -------- 
        42 

10:34:08 SQL> select * from table(dbms_xplan.display_cursor(format=>'basic +rows +rowstats last'));

PLAN_TABLE_OUTPUT                                                
-----------------                                                
EXPLAINED SQL STATEMENT:                                         
------------------------                                         
select /*+ gather_plan_statistics */ count(*) from DEMO          
                                                                 
Plan hash value: 2180342005                                      
                                                                 
--------------------------------------------------------------   
| Id  | Operation          | Name | Starts | E-Rows | A-Rows |   
--------------------------------------------------------------   
|   0 | SELECT STATEMENT   |      |      1 |        |      1 |   
|   1 |  SORT AGGREGATE    |      |      1 |      1 |      1 |   
|   2 |   TABLE ACCESS FULL| DEMO |      1 |      1 |     42 |   
--------------------------------------------------------------   

Accept Pending Statistics

Now I’ll show the second case where my tests show that the new statistics gathering is ok. I gather statistics again:



10:34:09 SQL> exec dbms_stats.gather_table_stats('DEMO','DEMO');
PL/SQL procedure successfully completed.

10:34:09 SQL> alter session set optimizer_use_pending_statistics=true;
Session altered.

10:34:11 SQL> select /*+ gather_plan_statistics */ count(*) from DEMO;

  COUNT(*) 
  -------- 
        42 


10:34:12 SQL> select * from table(dbms_xplan.display_cursor(format=>'basic +rows +rowstats last'));

PLAN_TABLE_OUTPUT                                                
-----------------                                                
EXPLAINED SQL STATEMENT:                                         
------------------------                                         
select /*+ gather_plan_statistics */ count(*) from DEMO          
                                                                 
Plan hash value: 2180342005                                      
                                                                 
--------------------------------------------------------------   
| Id  | Operation          | Name | Starts | E-Rows | A-Rows |   
--------------------------------------------------------------   
|   0 | SELECT STATEMENT   |      |      1 |        |      1 |   
|   1 |  SORT AGGREGATE    |      |      1 |      1 |      1 |   
|   2 |   TABLE ACCESS FULL| DEMO |      1 |     42 |     42 |   
--------------------------------------------------------------   
                                                                 
10:34:12 SQL> alter session set optimizer_use_pending_statistics=false;
Session altered.

When I’m ok with the new statistics I can publish them so that other sessions can see them. As doing this in production is probably a fix for a critical problem, I want the effects to take immediately, invalidating all cursors:



10:34:13 SQL> exec dbms_stats.publish_pending_stats('DEMO','DEMO',no_invalidate=>false);
PL/SQL procedure successfully completed.

The default NO_INVALIDATE value is probably to avoid in those cases because you want to see the side effects, if any, as soon as possible. Not within a random window of 5 hours later where you have left the office. I set back the table preference to PUBLISH=TRUE and check that the new statistics are visible in DBA_TAB_STATISTICS (and no more in DBA_TAB_PENDING_STATS):



10:34:14 SQL> exec dbms_stats.set_table_prefs('DEMO','DEMO','publish','true');
PL/SQL procedure successfully completed.

10:34:15 SQL> select num_rows,cast(last_analyzed as timestamp),dbms_stats.get_prefs('PUBLISH',owner,table_name) from dba_tab_statistics where owner='DEMO' and table_name in ('DEMO');

  NUM_ROWS CAST(LAST_ANALYZEDASTIMESTAMP)    DBMS_STATS.GET_PREFS('PUBLISH',OWNER,TABLE_NAME)   
  -------- ------------------------------    ------------------------------------------------   
        42 12-SEP-18 10.34.09.000000000 AM   TRUE                                               


10:34:15 SQL> c/dba_tab_statistics/dba_tab_pending_stats
  1* select num_rows,cast(last_analyzed as timestamp),dbms_stats.get_prefs('PUBLISH',owner,table_name) from dba_tab_pending_stats where owner='DEMO' and table_name in ('DEMO');
10:34:15 SQL> /

no rows selected

Report Differences

Then what if a citical regression is observed later? I still have the possibility to revert to the old statistics. First I can check in detail what has changed:



10:34:16 SQL> select report from table(dbms_stats.diff_table_stats_in_history('DEMO','DEMO',sysdate-1,sysdate,0));

REPORT
------

###############################################################################

STATISTICS DIFFERENCE REPORT FOR:
.................................

TABLE         : DEMO
OWNER         : DEMO
SOURCE A      : Statistics as of 11-SEP-18 10.34.16.000000 AM EUROPE/ZURICH
SOURCE B      : Statistics as of 12-SEP-18 10.34.16.000000 AM EUROPE/ZURICH
PCTTHRESHOLD  : 0
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


TABLE / (SUB)PARTITION STATISTICS DIFFERENCE:
.............................................

OBJECTNAME                  TYP SRC ROWS       BLOCKS     ROWLEN     SAMPSIZE
...............................................................................

DEMO                        T   A   1          4          3          1
                                B   42         8          3          42
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

COLUMN STATISTICS DIFFERENCE:
.............................

COLUMN_NAME     SRC NDV     DENSITY    HIST NULLS   LEN  MIN   MAX   SAMPSIZ
...............................................................................

N               A   1       1          NO   0       3    C102  C102  1
                B   41      .024390243 NO   0       3    C102  C12A  42
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


NO DIFFERENCE IN INDEX / (SUB)PARTITION STATISTICS
###############################################################################

Restore Previous Statistics

If nothing is obvious and the regression is more critical than the original problem, I still have the possibility to revert back to the old statistics:



10:34:17 SQL> exec dbms_stats.restore_table_stats('DEMO','DEMO',sysdate-1,no_invalidate=>false);
PL/SQL procedure successfully completed.

Again, invalidating all cursors immediately is probably required as I solve a critical problem here. Immediately, the same query uses the old statistics:



10:34:17 SQL> select /*+ gather_plan_statistics */ count(*) from DEMO;

  COUNT(*) 
  -------- 
        42 


10:34:17 SQL> select * from table(dbms_xplan.display_cursor(format=>'basic +rows +rowstats last'));

PLAN_TABLE_OUTPUT                                                
-----------------                                                
EXPLAINED SQL STATEMENT:                                         
------------------------                                         
select /*+ gather_plan_statistics */ count(*) from DEMO          
                                                                 
Plan hash value: 2180342005                                      
                                                                 
--------------------------------------------------------------   
| Id  | Operation          | Name | Starts | E-Rows | A-Rows |   
--------------------------------------------------------------   
|   0 | SELECT STATEMENT   |      |      1 |        |      1 |   
|   1 |  SORT AGGREGATE    |      |      1 |      1 |      1 |   
|   2 |   TABLE ACCESS FULL| DEMO |      1 |      1 |     42 |
--------------------------------------------------------------   

If I want to see what happened recently on this table, I can query the history of operations (you can replace my ugly regexp_replace with XQuery):



10:34:18 SQL> select end_time,end_time-start_time,operation,target,regexp_replace(regexp_replace(notes,'" val="','=>'),'(||)',' '),status from DBA_OPTSTAT_OPERATIONS where regexp_like(target,'"?'||'DEMO'||'"?."?'||'DEMO'||'"?') order by end_time desc fetch first 10 rows only;

END_TIME                                 END_TIME-START_TIME   OPERATION             TARGET          REGEXP_REPLACE(REGEXP_REPLACE(NOTES,'"VAL="','=>'),'(||)','')                                                                                                                                                                                                                                         STATUS      
--------                                 -------------------   ---------             ------          ----------------------------------------------------------------------------------------------                                                                                                                                                                                                                                         ------      
12-SEP-18 10.34.17.718800000 AM +02:00   +00 00:00:00.017215   restore_table_stats   "DEMO"."DEMO"     as_of_timestamp=>09-11-2018 10:34:17  force=>FALSE  no_invalidate=>FALSE  ownname=>DEMO  restore_cluster_index=>FALSE  tabname=>DEMO                                                                                                                                                                                                 COMPLETED   
12-SEP-18 10.34.13.262234000 AM +02:00   +00 00:00:00.010021   restore_table_stats   "DEMO"."DEMO"     as_of_timestamp=>11-30-3000 01:00:00  force=>FALSE  no_invalidate=>FALSE  ownname=>DEMO  restore_cluster_index=>FALSE  tabname=>DEMO                                                                                                                                                                                                 COMPLETED   
12-SEP-18 10.34.09.974873000 AM +02:00   +00 00:00:00.032513   gather_table_stats    "DEMO"."DEMO"     block_sample=>FALSE  cascade=>NULL  concurrent=>FALSE  degree=>NULL  estimate_percent=>DBMS_STATS.AUTO_SAMPLE_SIZE  force=>FALSE  granularity=>AUTO  method_opt=>FOR ALL COLUMNS SIZE AUTO  no_invalidate=>NULL  ownname=>DEMO  partname=>  reporting_mode=>FALSE  statid=>  statown=>  stattab=>  stattype=>DATA  tabname=>DEMO     COMPLETED   
12-SEP-18 10.34.01.194735000 AM +02:00   +00 00:00:00.052087   gather_table_stats    "DEMO"."DEMO"     block_sample=>FALSE  cascade=>NULL  concurrent=>FALSE  degree=>NULL  estimate_percent=>DBMS_STATS.AUTO_SAMPLE_SIZE  force=>FALSE  granularity=>AUTO  method_opt=>FOR ALL COLUMNS SIZE AUTO  no_invalidate=>NULL  ownname=>DEMO  partname=>  reporting_mode=>FALSE  statid=>  statown=>  stattab=>  stattype=>DATA  tabname=>DEMO     COMPLETED   

We can see here that the publishing of pending stats was actually a restore of stats as of Nov 30th of Year 3000. This is probably because the pending status is hardcoded as a date in the future. Does that mean that all pending stats will become autonomously published at that time? I don’t think we have to worry about Y3K bugs for the moment…

Here is the full receipe I’ve given to an application owner who needs to gather statistics on his tables on a highly critical database. Then he has all the info to limit the risks. My recommendation is to prepare this fallback scenario before doing any change, and test it as I did, on a test environment, in order to be ready to react on any unexpected side effect. Be careful, the pending statsitics do not work correctly with system statistics and can have very nasty side effects (Bug 21326597), but restoring from history is possible.

Cet article Optimizer Statistics Gathering – pending and history est apparu en premier sur Blog dbi services.

Migrate Oracle Database 9.2.0.6 to Oracle 19c using GoldenGate

$
0
0

When a customer wanted to take the challenge to migrate an oracle database 9.2.0.6 (the prehistory in the Oracle world) to Oracle 19c using Oracle GodenGate, I saw more problems than add value for different reasons:

  •  Oracle 9.2.0.6 database is out of support (final 9.2 patch was Oracle 9.2.0.8).
  • The customer Operating Systems was AIX 7.4 and only Oracle GoldenGate 11.1.1.1.2 for Oracle 9.2 for AIX 5.3 is available on https://edelivery.oracle.com.
  • The Patch 13606038: ORACLE GOLDENGATE V11.1.1.0.31 FOR ORACLE 9I is not available for download since we need special support to got it.

Oracle GoldenGate database Schema Profile check script

The first step is to download from Oracle Support, the Oracle GoldenGate database Schema Profile check script to query the database by schema to identify current configuration and any unsupported data types or types that may need special considerations for Oracle GoldenGate in an oracle environment:

  • Oracle GoldenGate database Schema Profile check script for Oracle DB (Doc ID 1296168.1) : full-schemaCheckOracle_07072020.sql

Even Oracle Support mentions that this script is written for Oracle database version 9i thru 11g, some adaptation must be done for an Oracle 9.2.0.6 database:

First of all, add a parameter to specify schema name as entry :

oracle@aixSourceServer-Ora9i: /opt/oracle/software/goldengate> ls -ltr

vi full-schemaCheckOracle_07072020.sql

--Lazhar Felahi – 10.12.2020 - comment this line
--spool schemaCheckOracle.&&schema_name.out
--Lazhar Felahi – 10.12.2020 - comment this line
--ACCEPT schema_name char prompt 'Enter the Schema Name > '
variable b0 varchar2(50)
--Lazhar Felahi – 10.12.2020 - comment this line
--exec :b0 := upper('&schema_name');
--Lazhar Felahi – 10.12.2020 - add this line
exec :b0 := '&1';
--Lazhar Felahi – 10.12.2020 - comment this line
--spool schemaCheckOracle.&&schema_name..out
--Lazhar Felahi – 10.12.2020 - add this line
spool schemaCheckOracle.&&1..out

Execute the script for schemas needed:

oracle@aixSourceServer-Ora9i: /opt/oracle/software/goldengate>
sqlplus /nolog
SQL*Plus: Release 9.2.0.6.0 - Production on Thu Dec 10 21:19:37 2020
Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved.
SQL> start full-schemaCheckOracle_07072020.sql HR
error :
ERROR at line 4:
ORA-00904: "SUPPLEMENTAL_LOG_DATA_ALL": invalid identifier
platform_name
*
ERROR at line 2:
ORA-00904: "PLATFORM_NAME": invalid identifier
------ Integrated Extract unsupported objects in HR

select object_name, support_mode from DBA_GOLDENGATE_SUPPORT_MODE WHERE OWNER = :b0 and support_mode = 'NONE'
ERROR at line 1:
ORA-00942: table or view does not exist

 

The above errors can be ignored :

  • The errors ORA-00904: “SUPPLEMENTAL_LOG_DATA_ALL”: invalid identifier and ORA-00904: “PLATFORM_NAME”: invalid identifier can be ignored since this column does not exist into the data dictionary view v$database for the version Oracle 9.2.0.6 database.
  • The error ORA-00942: table or view does not exist can be ignored since the view DBA_GOLDENGATE_SUPPORT_MODE  is available starting with Oracle Database 11g Release 2 (11.2.0.4).

Adapt the script and re-execute it, an output file is generated listing different checks and any types unsupported.

For instance, the script lists all tables with no primary key or Unique Index or Tables with NOLOGGING setting.

GoldenGate needs tables with primary key. If no PK exist for one table, GG will take all column to define the unicity.

GOLDENGATE INSTALLATION – ON SOURCE SERVER

Download the zip file corresponding to Oracle GoldenGate 11.1.1.1.2 software from https://edelivery.oracle.com :

  • V28955-01.zip Oracle GoldenGate 11.1.1.1.2 for Oracle 9.2 for AIX 5.3 on IBM AIX on POWER Systems (64-bit), 45.5 MB

Unzip and untar the file:

oracle@aixSourceServer-Ora9i: /opt/oracle/software/goldengate> ls -ltr
total 365456
-rw-rw-r--    1 oracle   dba       139079680 Oct  5 2011  ggs_AIX_ppc_ora9.2_64bit.tar
-rw-r--r--    1 oracle   dba          245329 Oct 28 2011  OGG_WinUnix_Rel_Notes_11.1.1.1.2.pdf
-rw-r--r--    1 oracle   dba           25065 Oct 28 2011  Oracle GoldenGate 11.1.1.1 README.txt
-rwx------    1 oracle   dba        47749729 Dec 10 13:55 V28955-01.zip
drwxr-xr-x    2 oracle   dba            4096 Dec 14 09:35 check_script
oracle@aixSourceServer-Ora9i:/opt/oracle/software/goldengate>

oracle@aixSourceServer-Ora9i:/opt/oracle/product/gg_11.1.1.1.2> tar -xvf /opt/oracle/software/goldengate/ggs_AIX_ppc_ora9.2_64bit.tar
…
x marker_setup.sql, 3702 bytes, 8 tape blocks
x marker_status.sql, 1715 bytes, 4 tape blocks
oracle@aixSourceServer-Ora9i: /opt/oracle/product/gg_11.1.1.1.2> ls -ltr
total 235344
-r--r--r-- 1 oracle dba 1476 Oct 15 2010 zlib.txt
. . .
-rwxr-xr-x 1 oracle dba 13911955 Oct 5 2011 replicat

Let’s set the LIBPATH environment variable and call “ggsci” utility:

oracle@aixSourceServer-Ora9i: /opt/oracle/product/gg_11.1.1.1.2> export LIBPATH=/opt/oracle/product/gg_11.1.1.1.2/:$ORACLE_HOME/lib:/opt/oracle/product/9.2.0.6/lib32/:/opt/oracle/product/9.2.0.6/lib/:$LIBPATH
oracle@aixSourceServer-Ora9i: /opt/oracle/product/gg_11.1.1.1.2> ./ggsci

Oracle GoldenGate Command Interpreter for Oracle
Version 11.1.1.1.2 OGGCORE_11.1.1.1.2_PLATFORMS_111004.2100
AIX 5L, ppc, 64bit (optimized), Oracle 9.2 on Oct 5 2011 00:37:06

Copyright (C) 1995, 2011, Oracle and/or its affiliates. All rights reserved.

GGSCI (aixSourceServer-Ora9i) 1> info all

Program Status Group Lag Time Since Chkpt

MANAGER STOPPED

GOLDENGATE SETUP – ON SOURCE SERVER

Create the goldengate admin user on source and target database:

oracle@aixSourceServer-Ora9i:/home/oracle/ [DB2] sqlplus / as sysdba

SQL> create tablespace GOLDENGATE datafile '/u02/oradata/DB2/goldengate.dbf' size 2G ;

SQL> create profile GGADMIN limit password_life_time unlimited ;

SQL> create user GGADMIN identified by "******" default tablespace
     goldengate temporary tablespace temp profile GGADMIN ;

SQL> grant create session, dba to GGADMIN ;

SQL> exec dbms_goldengate_auth.grant_admin_privilege ('GGADMIN') ;

SQL> grant flashback any table to GGADMIN ;
--create subdirs
GGSCI (aixSourceServer-Ora9i) 1> create subdirs

Creating subdirectories under current directory /opt/oracle/product/gg_11.1.1.1.2

Parameter files                /opt/oracle/product/gg_11.1.1.1.2/dirprm: created
Report files                   /opt/oracle/product/gg_11.1.1.1.2/dirrpt: created
Checkpoint files               /opt/oracle/product/gg_11.1.1.1.2/dirchk: created
Process status files           /opt/oracle/product/gg_11.1.1.1.2/dirpcs: created
SQL script files               /opt/oracle/product/gg_11.1.1.1.2/dirsql: created
Database definitions files     /opt/oracle/product/gg_11.1.1.1.2/dirdef: created
Extract data files             /opt/oracle/product/gg_11.1.1.1.2/dirdat: created
Temporary files                /opt/oracle/product/gg_11.1.1.1.2/dirtmp: created
Veridata files                 /opt/oracle/product/gg_11.1.1.1.2/dirver: created
Veridata Lock files            /opt/oracle/product/gg_11.1.1.1.2/dirver/lock: created
Veridata Out-Of-Sync files     /opt/oracle/product/gg_11.1.1.1.2/dirver/oos: created
Veridata Out-Of-Sync XML files /opt/oracle/product/gg_11.1.1.1.2/dirver/oosxml: created
Veridata Parameter files       /opt/oracle/product/gg_11.1.1.1.2/dirver/params: created
Veridata Report files          /opt/oracle/product/gg_11.1.1.1.2/dirver/report: created
Veridata Status files          /opt/oracle/product/gg_11.1.1.1.2/dirver/status: created
Veridata Trace files           /opt/oracle/product/gg_11.1.1.1.2/dirver/trace: created
Stdout files                   /opt/oracle/product/gg_11.1.1.1.2/dirout: created

--add GGSCHEMA into ./GLOBALS file in source and target
oracle@ aixSourceServer-Ora9i: /opt/oracle/product/gg_11.1.1.1.2> ./ggsci
GGSCI (aixSourceServer-Ora9i) 3> view param ./GLOBALS
GGSCHEMA goldengate

--add PORT into mgr parameter file and start the manager
GGSCI (aixSourceServer-Ora9i) 1> edit params mgr

PORT 7809
GGSCI (aixSourceServer-Ora9i) 6> start mgr
Manager started.
GGSCI (aixSourceServer-Ora9i) 7> info all
Program     Status      Group       Lag           Time Since Chkpt
MANAGER     RUNNING

--Installing the DDL support on the source database : You will be prompted for the name of a schema for the GoldenGate database objects.
SQL> @marker_setup.sql
. . .
Script complete
SQL> @ddl_setup.sql
. . .
SUCCESSFUL installation of DDL Replication software components
SQL> @role_setup.sql
Role setup script complete
SQL> grant ggs_ggsuser_role to goldengate;
SQL> @ddl_enable.sql
Trigger altered
--On both database (source and target), Installing Support for Sequences
SQL> @sequence.
. . .
SUCCESSFUL installation of Oracle Sequence Replication support

Add the trandata on schemas concerned by the GoldenGate replication:

oracle@aixSourceServer-Ora9i: /opt/oracle/product/gg_11.1.1.1.2> ./ggsci

Oracle GoldenGate Command Interpreter for Oracle
Version 11.1.1.1.2 OGGCORE_11.1.1.1.2_PLATFORMS_111004.2100
AIX 5L, ppc, 64bit (optimized), Oracle 9.2 on Oct  5 2011 00:37:06

Copyright (C) 1995, 2011, Oracle and/or its affiliates. All rights reserved.
	


GGSCI (aixSourceServer-Ora9i) 1> dblogin userid goldengate
Password:
Successfully logged into database.


GGSCI (aixSourceServer-Ora9i) 2> add trandata bgh.*
GGSCI (aixSourceServer-Ora9i) 2> add trandata all_opi.*

2020-12-18 10:46:45  WARNING OGG-00706  Failed to add supplemental log group on table bgh.KLI_J_TEST_HIST due to ORA-02257: maximum number of columns exceeded, SQL ALTER TABLE "bgh"."KLI_J_TEST_HIST" ADD SUPPLEMENTAL LOG GROUP "GGS_KLI_J_TEST_HIST_901157" ("ENH_N_ID","ENH_N_NOINSCRIPTION","ENH_N_NOCOURS","ENH_C_P1NOTE","ENH_C_P2NOTE","ENH_C_P3NOTE","ENH_C_.


2020-12-18 10:46:52  WARNING OGG-00706  Failed to add supplemental log group on table bgh.TABLE_ELEVES_SVG due to ORA-02257: maximum number of columns exceeded, SQL ALTER TABLE "bgh"."TABLE_ELEVES" ADD SUPPLEMENTAL LOG GROUP "GGS_TABLE_ELEVES_901320" ("NOINSCRIPTION","NOCOURS","P1NOTE","P2NOTE","P3NOTE","P4NOTE","P5NOTE","P6NOTE","P7NOTE","P8NOTE","P1COMPTE".

2020-12-18 10:46:52  WARNING OGG-00869  No unique key is defined for table TABLENOTE_TMP. All viable columns will be used to represent the key, but may not guarantee uniqueness.  KEYCOLS may be used to define the key.

Logging of supplemental redo data enabled for table all_opi.ZUI_VM_RETUIO_SCOLARITE.
ERROR: OCI Error retrieving bind info for query (status = 100), SQL <SELECT * FROM "all_opi"."EXT_POI_V_RTEWR">.

The warning OGG-00706 and OGG–00869 are solved by adding a primary key to the tables concerned.

The OCI error must be investigated by opening an Oracle  Service Request.

Add the extract, exttrail and start it :

GGSCI (aixSourceServer-Ora9i) 2> add extract EXTRSO, tranlog, begin now
EXTRACT added.

add extract EXTRNA, tranlog, begin now
GGSCI (aixSourceServer-Ora9i) 7> add EXTTRAIL /opt/oracle/goldengate/data/DDIP9/so, EXTRACT EXTRSO
EXTTRAIL added.

add EXTTRAIL /opt/oracle/goldengate/data/DDIP9/na, EXTRACT EXTRNA

edit param EXTRSO
Extract EXTRSO
userid goldengate password ******
Exttrail /opt/oracle/goldengate/data/DDIP9/so
ENCRYPTTRAIL AES192
DDL INCLUDE MAPPED OBJNAME SO.*
Table bgh.*;

edit param EXTRNA
Extract EXTRNA
userid goldengate password ******
Exttrail /opt/oracle/goldengate/data/DDIP9/na
ENCRYPTTRAIL AES192
DDL INCLUDE MAPPED OBJNAME NBDS_ADM.*
Table all_api.*;

start EXTRSO

Sending START request to MANAGER ...
EXTRACT EXTRSO starting

start EXTRNA

GGSCI (aixSourceServer-Ora9i) 11> info all

Program     Status      Group       Lag           Time Since Chkpt

MANAGER     RUNNING
EXTRACT     RUNNING     EXTRHR      00:00:00      00:00:04
EXTRACT     RUNNING     EXTRSO      00:08:18      00:00:00
EXTRACT     RUNNING     PUMPHR      00:00:00      00:00:07

Check if trail files are created:

oracle@aixSourceServer-Ora9i: /opt/oracle/product/gg_11.1.1.1.2> ls -ltr /opt/oracle/goldengate/data/DDIP9/
total 72
-rw-rw-rw-    1 oracle   dba             960 Dec 16 09:34 hr000000
-rw-rw-rw-    1 oracle   dba            1021 Dec 16 10:25 hr000001
-rw-rw-rw-    1 oracle   dba            1021 Dec 16 10:34 hr000002
-rw-rw-rw-    1 oracle   dba            2679 Dec 16 14:26 hr000003
-rw-rw-rw-    1 oracle   dba             960 Dec 16 19:54 so000000
-rw-rw-rw-    1 oracle   dba            1021 Dec 16 19:59 na000000

Add the PUMP:

GGSCI (aixSourceServer-Ora9i) 1> add extract PUMPSO,EXTTRAILSOURCE /opt/oracle/goldengate/data/DDIP9/so
EXTRACT added.

add extract PUMPNA,EXTTRAILSOURCE /opt/oracle/goldengate/data/DDIP9/na

GGSCI (aixSourceServer-Ora9i) 2> add rmttrail /data/oradata/goldengate/data/LGGATE/so, extract PUMPSO
RMTTRAIL added.

add rmttrail /data/oradata/goldengate/data/LGGATE/na, extract PUMPNA

extract PUMPSO
userid goldengate password ******
RMTHOST aixTargetServer-Ora19c, MGRPORT 7810
RMTTRAIL /data/oradata/goldengate/data/LGGATE/so
TABLE bgh.*;

extract PUMPNA
userid goldengate password ******
RMTHOST aixTargetServer-Ora19c, MGRPORT 7810
RMTTRAIL /data/oradata/goldengate/data/LGGATE/na
TABLE all_api.*;


GGSCI (aixSourceServer-Ora9i) 6> start pumpso

Sending START request to MANAGER ...
EXTRACT PUMPSO starting

start pumpna

GGSCI (aixSourceServer-Ora9i) 26> info all

Program     Status      Group       Lag           Time Since Chkpt

MANAGER     RUNNING
EXTRACT     RUNNING     EXTRHR      00:00:00      00:00:07
EXTRACT     RUNNING     EXTRNA      00:00:00      00:00:08
EXTRACT     RUNNING     EXTRSO      00:00:00      00:00:05
EXTRACT     RUNNING     PUMPHR      00:00:00      00:00:03
EXTRACT     RUNNING     PUMPNA      00:00:00      00:03:42
EXTRACT     RUNNING     PUMPSO      00:00:00      00:00:00

 

GOLDENGATE INITIAL LOAD

On the source schemas, got the last active transaction and do the export:

SELECT dbms_flashback.get_system_change_number as current_scn FROM DUAL;
10228186709471 --Backup this SCN, it will be used later to start the goldengate replicat process on the target server
nohup  exp / file=rg081DDIP9.s0.202012172303.dmp log=rg081DDIP9.s0.202012172303.dmp.log tables=bgh.% flashback_scn=10228186709471 &
nohup  exp / file=rg081DDIP9.nbds_adm.202012172303.dmp log=rg081DDIP9.all_opi.202012172303.dmp.log tables=nbds_adm.% flashback_scn=10228186709471 &

Copy the dump file on the target and do the import :

drop user bgh cascade;
create user bgh identified by "******" default tablespace SO temporary tablespace TEMP;
alter user bgh quota unlimited on S0_D;
alter user bgh quota unlimited on S0_I;
alter user bgh quota unlimited on S0_LOB;
nohup imp / file=/data/export/LGGATE/rg081DDIP9.s0.202012172303.dmp log=so.imp171220202303.log buffer=1000000 fromuser=bgh touser=bgh grants=n statistics=none constraints=n ignore=y &

drop user all_opi cascade;
create user all_opi identified by "******" default tablespace NA temporary tablespace TEMP;
alter user all_opi quota unlimited on NBDS_D;
alter user all_opi quota unlimited on NBDS_I;
alter user all_opi quota unlimited on NBDS_LOB;
alter user all_opi quota unlimited on system;
alter user all_opi quota unlimited on na;
nohup imp / file=/data/export/LGGATE/rg081DDIP9.nbds_adm.202012172303.dmp log=na.imp171220202303.log buffer=1000000 fromuser=all_opi touser=all_opi grants=n statistics=none constraints=n ignore=y &

Since the import is done without the constraints, get all primary key from the source database and create it into target.

Disable all triggers on the target:

select 'alter trigger '||owner||'.'||trigger_name||' disable;' from dba_triggers where owner = 'NBDS_ADM';

Check no ref. constraints exist, job_queue_processes parameter equal to 0 and recompile all:

--checK ref constraints
SQL> select * from dba_constraints where owner = 'NBDS_ADM' and constraint_type = 'R';

no rows selected

SQL>
--check job_queue_processes

SQL> sho parameter job

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
job_queue_processes                  integer     384
max_datapump_jobs_per_pdb            string      100
max_datapump_parallel_per_job        string      50
SQL> alter system set job_queue_Processes = 0;

System altered.

SQL>

--recompile all
SQL> start ?/rdbms/admin/utlrp.sql

Session altered.
. . .

GOLDENGATE SETUP – ON TARGET SERVER

Add the replicat:

--add replicat
GGSCI (aixTargetServer-Ora19c) 5> dblogin useridalias goldengate
Successfully logged into database.

GGSCI (aixTargetServer-Ora19c as goldengate@LGGATE) 8> add replicat REPLSO, exttrail /data/oradata/goldengate/data/LGGATE/so,checkpointtable GOLDENGATE.CHECKPOINT;
REPLICAT added.


GGSCI (aixTargetServer-Ora19c as goldengate@LGGATE) 9> add replicat REPLNA, exttrail /data/oradata/goldengate/data/LGGATE/na,checkpointtable GOLDENGATE.CHECKPOINT;
REPLICAT added.


GGSCI (aixTargetServer-Ora19c as goldengate@LGGATE) 10> info all

Program     Status      Group       Lag at Chkpt  Time Since Chkpt

MANAGER     RUNNING
REPLICAT    RUNNING     REPLHR      00:00:00      00:00:09
REPLICAT    STOPPED     REPLNA      00:00:00      00:00:02
REPLICAT    STOPPED     REPLSO      00:00:00      00:00:17


GGSCI (aixTargetServer-Ora19c as goldengate@LGGATE) 11>

--configure replicat
Replicat REPLSO
--DBOPTIONS INTEGRATEDPARAMS (parallelism 6)
SOURCECHARSET PASSTHRU
DISCARDFILE /opt/oracle/product/gg_191004/dirrpt/REPLSO_discard.txt, append, megabytes 10
USERIDALIAS goldengate
ASSUMETARGETDEFS
MAP bhg.*,TARGET bgh.*;


Replicat REPLNA
--DBOPTIONS INTEGRATEDPARAMS (parallelism 6)
SOURCECHARSET PASSTHRU
DISCARDFILE /opt/oracle/product/gg_191004/dirrpt/REPLNA_discard.txt, append, megabytes 10
USERIDALIAS goldengate
ASSUMETARGETDEFS
MAP all_opi.*,TARGET all_opi.*;

--Start replicat REPLNA
GGSCI (aixTargetServer-Ora19c as goldengate@LGGATE) 18> start replicat REPLNA, atcsn 10228186775913

Sending START request to MANAGER ...
REPLICAT REPLNA starting

--Start replicat REPLS0
GGSCI (aixTargetServer-Ora19c as goldengate@LGGATE) 18> start replicat REPLSO, atcsn 10228186775913

Sending START request to MANAGER ...
REPLICAT REPLSO starting

GGSCI (aixTargetServer-Ora19c as goldengate@LGGATE) 19> info all

Program     Status      Group       Lag at Chkpt  Time Since Chkpt

MANAGER     RUNNING
REPLICAT    RUNNING     REPLHR      00:00:00      00:00:02
REPLICAT    RUNNING     REPLNA      00:00:00      00:00:01
REPLICAT    RUNNING     REPLSO      00:00:00      00:21:10

Wait unti the lag decrease…

GOLDENGATE TEST SYNCHRONIZATION

Add some activity DML + DDL on the source database and check the synchronization with goldengate “stats” commands on both servers:

GGSCI (aixSourceServer-Ora9i) 5> stats extract EXTRNA, totalsonly *.*

Sending STATS request to EXTRACT EXTRNA ...

Start of Statistics at 2020-12-18 16:35:00.

DDL replication statistics (for all trails):

*** Total statistics since extract started     ***
        Operations                                   0.00
        Mapped operations                            0.00
        Unmapped operations                          0.00
        Other operations                             0.00
        Excluded operations                          0.00

Output to /opt/oracle/goldengate/data/DDIP9/na:

Cumulative totals for specified table(s):

*** Total statistics since 2020-12-18 10:42:15 ***
        Total inserts                                8.00
        Total updates                                1.00
        Total deletes                               25.00
        Total discards                               0.00
        Total operations                            34.00

*** Daily statistics since 2020-12-18 10:42:15 ***
        Total inserts                                8.00
        Total updates                                1.00
        Total deletes                               25.00
        Total discards                               0.00
        Total operations                            34.00

*** Hourly statistics since 2020-12-18 16:00:00 ***

        No database operations have been performed.

*** Latest statistics since 2020-12-18 10:42:15 ***
        Total inserts                                8.00
        Total updates                                1.00
        Total deletes                               25.00
        Total discards                               0.00
        Total operations                            34.00

End of Statistics.

GGSCI (aixSourceServer-Ora9i) 10> stats extract EXTRSO, totalsonly *.*

Sending STATS request to EXTRACT EXTRSO ...

Start of Statistics at 2020-12-18 16:36:06.

DDL replication statistics (for all trails):

*** Total statistics since extract started     ***
        Operations                                   0.00
        Mapped operations                            0.00
        Unmapped operations                          0.00
        Other operations                             0.00
        Excluded operations                          0.00

Output to /opt/oracle/goldengate/data/DDIP9/so:

Cumulative totals for specified table(s):

*** Total statistics since 2020-12-18 10:42:15 ***
        Total inserts                              156.00
        Total updates                                0.00
        Total deletes                                0.00
        Total discards                               0.00
        Total operations                           156.00

*** Daily statistics since 2020-12-18 10:42:15 ***
        Total inserts                              156.00
        Total updates                                0.00
        Total deletes                                0.00
        Total discards                               0.00
        Total operations                           156.00

*** Hourly statistics since 2020-12-18 16:00:00 ***

        No database operations have been performed.

*** Latest statistics since 2020-12-18 10:42:15 ***
        Total inserts                              156.00
        Total updates                                0.00
        Total deletes                                0.00
        Total discards                               0.00
        Total operations                           156.00

End of Statistics.

--On the target server
GGSCI (aixTargetServer-Ora19c) 5> stats replicat REPLNA, totalsonly *.*

Sending STATS request to REPLICAT REPLNA ...

Start of Statistics at 2020-12-18 16:36:45.

DDL replication statistics:

*** Total statistics since replicat started     ***
        Operations                                         1.00
        Mapped operations                                  1.00
        Unmapped operations                                0.00
        Other operations                                   0.00
        Excluded operations                                0.00
        Errors                                             0.00
        Retried errors                                     0.00
        Discarded errors                                   0.00
        Ignored errors                                     0.00

Cumulative totals for specified table(s):

*** Total statistics since 2020-12-18 11:42:12 ***
        Total inserts                                    526.00
        Total updates                                      1.00
        Total deletes                                    543.00
        Total upserts                                      0.00
        Total discards                                     0.00
        Total operations                                1070.00

*** Daily statistics since 2020-12-18 11:42:12 ***
        Total inserts                                    526.00
        Total updates                                      1.00
        Total deletes                                    543.00
        Total upserts                                      0.00
        Total discards                                     0.00
        Total operations                                1070.00

*** Hourly statistics since 2020-12-18 16:00:00 ***

        No database operations have been performed.

*** Latest statistics since 2020-12-18 11:42:12 ***
        Total inserts                                    526.00
        Total updates                                      1.00
        Total deletes                                    543.00
        Total upserts                                      0.00
        Total discards                                     0.00
        Total operations                                1070.00

End of Statistics.

 

If you want to remove your GoldenGate configuration

on source :
delete trandata hr.*
delete trandata bgh.*
delete trandata all_opi.*
drop user goldengate cascade;
SQL> @ddl_disable

on target :
SQL> drop user goldengate cascade;

User dropped.


NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
enable_goldengate_replication        boolean     TRUE
resource_manage_goldengate           boolean     FALSE
SQL> alter system set enable_goldengate_replication=FALSE scope = both;

System altered.

SQL> sho parameter goldengate

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
enable_goldengate_replication        boolean     FALSE
resource_manage_goldengate           boolean     FALSE
SQL>

Conclusion

  • Synchronize an oracle database 9.2.0.6 to Oracle 19c (Oracle 19.7 in our case) with GoldenGate works !!! Of course some test with more activity as we have in the real life (production database) must be done to evaluate all possible problems.
  • Oracle does some enhancements to the Oracle GoldenGate software, we don’t need any parameter to convert the trail file format between different Oracle GoldenGate versions (as we had in the past between GG prior 10g and GG post 10g), the converison is done automatically.
  • Using GoldenGate to migrate your Oracle 9i database to Oracle 19c must be compared with alternative migration solution :
    • Transportable tablespace
    • Export/Import or Datapump
  • The focus must be done on the downtime available for the migration:
    • Less Downtime you have, Oracle Export Import, DataPump or Transportable Tablespaces will be better solution.
    • Near Zero Downtime you have, GoldenGate could be a solution only if the applicative team (architect, project manager, developer) participates since, for instance, tables without primary key will prevent GoldenGate to work, thus, developer must choose column/s to be candidate to be the PK into source.

Cet article Migrate Oracle Database 9.2.0.6 to Oracle 19c using GoldenGate est apparu en premier sur Blog dbi services.

DctmAPI.py revisited

$
0
0

2 years ago, I proposed a ctypes-based Documentum extension for python, DctmAPI.py. While it did the job, it was quite basic. For example, its select2dict() function, as inferred from its name, returned the documents from a dql query into a list of dictionaries, one per document, all in memory. While this is OK for testing and demonstration purpose, it can potentially put some stress on the available memory; besides, do we really need to hold at once in memory a complete result set with several hundreds thousands rows ? It makes more sense to iterate and process the result row by row. For instance, databases have cursors for that purpose.
Another rudimentary demonstration function was select(). Like select2dict(), it executed a dql statement but output the result row by row to stdout without any special attempt at pretty printing it. The result was quite crude, yet OK for testing purposes.
So, after 2 years, I thought it was about time to revamp this interface and make it more practical. A new generator-based function, co_select(), has been introduced for a more efficient processing of the result set. select2dict() is still available for those cases where it is still handy to have a full result set in memory and the volume is manageable; actually, select2dict() is now down to 2 lines, the second one being a list comprehension around co_select() (see the listing below). select() has become select_to_stdout() and its output much enhanced; it can be json or tabular, with optional column-wrapping à la sql*plus and colorization as well, all stuff I mentioned several times in the past, e.g. here. Moreover, a pagination functionality has been added through the functions paginate() and paginate_to_stdout(). Finally, exceptions and message logging have been used liberally. As it can be seen, those are quite some improvements from the original version. Of course, there are so many way to implement them depending on the level of usability and performance that is looked for. Also, new functionalities, maybe unexpected ones as of this writing, can be felt necessary, so the current functions are only to be taken as examples.
Let’s see how the upgraded module looks like now.

Listing of DctmAPI.py

"""
This module is a python - Documentum binding based on ctypes;
requires libdmcl40.so/libdmcl.so to be reachable through LD_LIBRARY_PATH;
initial version, C. Cervini - dbi-services.com - May 2018
revised, C. Cervini - dbi-services.com - December 2020

The binding works as-is for both python2 amd python3; no recompilation required; that's the good thing with ctypes compared to e.g. distutils/SWIG;
Under a 32-bit O/S, it must use the libdmcl40.so, whereas under a 64-bit Linux it must use the java backed one, libdmcl.so;

For compatibility with python3 (where strings are now unicode ones and no longer arrays of bytes, ctypes strings parameters are always converted to unicode, either by prefixing them
with a b if litteral or by invoking their encode('ascii', 'ignore') method; to get back to text from bytes, b.decode() is used;these works in python2 as well as in python3 so the source is compatible with these two versions of the language;

Because of the use of f-strings formatting, python 3.5 minimum is required;
"""

import os
import ctypes
import sys, traceback
import json

# use foreign C library;
# use this library in eContent server < v6.x, 32-bit Linux;
dmlib = '/home/dmadmin/documentum53/libdmcl40.so'
dmlib = 'libdmcl40.so'

# use this library in eContent server >= v6.x, 64-bit Linux;
dmlib = 'libdmcl.so'

# used by ctypes;
dm = 0

# maximum cache size in rows;
# used while calling the paginate() function;
# set this according to the row size and the available memory;
# set it to 0 for unlimited memory;
MAX_CACHE_SIZE = 10000

# incremental log verbosity levels, i.e. include previous levels;
class LOG_LEVEL:
   # no logging;
   nolog = 0

   # informative messages;
   info = 1

   # errors, i.e. exceptions messages and less;
   error = 2

   # debug, i.e. functions calls and less;
   debug = 3

   # current active level;
   log_level = error
   
class dmException(Exception):
   """
   generic, catch-all documentum exception;
   """
   def __init__(self, origin = "", message = None):
      super().__init__(message)
      self.origin = origin
      self.message = message

   def __repr__(self):
      return f"exception in {self.origin}: {self.message if self.message else ''}"

def show(level = LOG_LEVEL.error, mesg = "", beg_sep = "", end_sep = ""):
   """
   displays the message msg if allowed
   """
   if LOG_LEVEL.log_level > LOG_LEVEL.nolog and level <= LOG_LEVEL.log_level:
      print(f"{beg_sep} {repr(mesg)} {end_sep}")

def dmInit():
   """
   initializes the Documentum part;
   returns True if successfull, False otherwise;
   since they already have an implicit namespace through their dm prefix, dm.dmAPI* would be redundant so we define later dmAPI*() as wrappers around their respective dm.dmAPI*() functions;
   returns True if no error, False otherwise;
   """
   show(LOG_LEVEL.debug, "in dmInit()")
   global dm
   try:
      dm = ctypes.cdll.LoadLibrary(dmlib);  dm.restype = ctypes.c_char_p
      show(LOG_LEVEL.debug, f"in dmInit(), dm= {str(dm)} after loading library {dmlib}")
      dm.dmAPIInit.restype    = ctypes.c_int;
      dm.dmAPIDeInit.restype  = ctypes.c_int;
      dm.dmAPIGet.restype     = ctypes.c_char_p;      dm.dmAPIGet.argtypes  = [ctypes.c_char_p]
      dm.dmAPISet.restype     = ctypes.c_int;         dm.dmAPISet.argtypes  = [ctypes.c_char_p, ctypes.c_char_p]
      dm.dmAPIExec.restype    = ctypes.c_int;         dm.dmAPIExec.argtypes = [ctypes.c_char_p]
      status  = dm.dmAPIInit()
   except Exception as e:
      show(LOG_LEVEL.error, "exception in dmInit():")
      show(LOG_LEVEL.error, e)
      if LOG_LEVEL.log_level > LOG_LEVEL.error: traceback.print_stack()
      status = False
   else:
      status = True
   finally:
      show(LOG_LEVEL.debug, "exiting dmInit()")
      return status
   
def dmAPIDeInit():
   """
   releases the memory structures in documentum's library;
   returns True if no error, False otherwise;
   """
   show(LOG_LEVEL.debug, "in dmAPIDeInit()")
   try:
      dm.dmAPIDeInit()
   except Exception as e:
      show(LOG_LEVEL.error, "exception in dmAPIDeInit():")
      show(LOG_LEVEL.error, e)
      if LOG_LEVEL.log_level > LOG_LEVEL.error: traceback.print_stack()
      status = False
   else:
      status = True
   finally:
      show(LOG_LEVEL.debug, "exiting dmAPIDeInit()")
      return status
   
def dmAPIGet(s):
   """
   passes the string s to dmAPIGet() method;
   returns a non-empty string if OK, None otherwise;
   """
   show(LOG_LEVEL.debug, "in dmAPIGet()")
   try:
      value = dm.dmAPIGet(s.encode('ascii', 'ignore'))
   except Exception as e:
      show(LOG_LEVEL.error, "exception in dmAPIGet():")
      show(LOG_LEVEL.error, e)
      if LOG_LEVEL.log_level > LOG_LEVEL.error: traceback.print_stack()
      status = False
   else:
      status = True
   finally:
      show(LOG_LEVEL.debug, "exiting dmAPIGet()")
      return value.decode() if status and value is not None else None

def dmAPISet(s, value):
   """
   passes the string s to dmAPISet() method;
   returns TRUE if OK, False otherwise;
   """
   show(LOG_LEVEL.debug, "in dmAPISet()")
   try:
      status = dm.dmAPISet(s.encode('ascii', 'ignore'), value.encode('ascii', 'ignore'))
   except Exception as e:
      show(LOG_LEVEL.error, "exception in dmAPISet():")
      show(LOG_LEVEL.error, e)
      if LOG_LEVEL.log_level > LOG_LEVEL.error: traceback.print_stack()
      status = False
   else:
      status = True
   finally:
      show(LOG_LEVEL.debug, "exiting dmAPISet()")
      return status

def dmAPIExec(stmt):
   """
   passes the string s to dmAPIExec() method;
   returns TRUE if OK, False otherwise;
   """
   show(LOG_LEVEL.debug, "in dmAPIExec()")
   try:
      status = dm.dmAPIExec(stmt.encode('ascii', 'ignore'))
   except Exception as e:
      show(LOG_LEVEL.error, "exception in dmAPIExec():")
      show(LOG_LEVEL.error, e)
      if LOG_LEVEL.log_level > LOG_LEVEL.error: traceback.print_stack()
      status = False
   else:
      # no error, status is passed through, to be converted to boolean below;
      pass
   finally:
      show(LOG_LEVEL.debug, "exiting dmAPIExec()")
      return True == status 

def connect(docbase, user_name, password):
   """
   connects to given docbase as user_name/password;
   returns a session id if OK, None otherwise
   """
   show(LOG_LEVEL.debug, "in connect(), docbase = " + docbase + ", user_name = " + user_name + ", password = " + password) 
   try:
      session = dmAPIGet(f"connect,{docbase},{user_name},{password}")
      if session is None:
         raise dmException(origin = "connect()", message = f"unsuccessful connection to docbase {docbase} as user {user_name}")
   except dmException as dme:
      show(LOG_LEVEL.error, dme)
      show(LOG_LEVEL.error, dmAPIGet(f"getmessage,{session}").rstrip())
      if LOG_LEVEL.log_level > LOG_LEVEL.error: traceback.print_stack()
      session = None
   else:
      show(LOG_LEVEL.debug, f"successful session {session}")
      # emptying the message stack in case some are left form previous calls;
      while True:
         msg = dmAPIGet(f"getmessage,{session}").rstrip()
         if msg is None or not msg:
            break
         show(LOG_LEVEL.debug, msg)
   finally:
      show(LOG_LEVEL.debug, "exiting connect()")
      return session

def execute(session, dql_stmt):
   """
   execute non-SELECT DQL statements;
   returns TRUE if OK, False otherwise;
   """
   show(LOG_LEVEL.debug, f"in execute(), dql_stmt={dql_stmt}")
   try:
      query_id = dmAPIGet(f"query,{session},{dql_stmt}")
      if query_id is None:
         raise dmException(origin = "execute()", message = f"query {dql_stmt}")
      err_flag = dmAPIExec(f"close,{session},{query_id}")
      if not err_flag:
         raise dmException(origin = "execute()", message = "close")
   except dmException as dme:
      show(LOG_LEVEL.error, dme)
      show(LOG_LEVEL.error, dmAPIGet(f"getmessage,{session}").rstrip())
      status = False
   except Exception as e:
      show(LOG_LEVEL.error, "exception in execute():")
      show(LOG_LEVEL.error, e)
      if LOG_LEVEL.log_level > LOG_LEVEL.error: traceback.print_stack()
      status = False
   else:
      status = True
   finally:
      show(LOG_LEVEL.debug, "exiting execute()")
      return status

def co_select(session, dql_stmt):
   """
   a coroutine version of former select2dict;
   the result set is returned of row at a time as a dictionary by a yield statement, e.g.:
   {"attr-1": "value-1", "attr-2": "value-2", ... "attr-n": "value-n"}
   in case of repeating attributes, value is an array of values, e.g.:
   { .... "attr-i": ["value-1", "value-2".... "value-n"], ....}
   """
   show(LOG_LEVEL.debug, "in co_select(), dql_stmt=" + dql_stmt)
   try:
      query_id = dmAPIGet(f"query,{session},{dql_stmt}")
      if query_id is None:
         show(f'in co_select(), error in dmAPIGet("query,{session},{dql_stmt}")')
         raise dmException(origin = "co_select", message = f"query {dql_stmt}")

      # counts the number of returned rows in the result set;
      row_counter = 0

      # list of attributes returned by query;
      # internal use only; the caller can compute it at will through the following expression: results[0].keys();
      attr_names = []

      # default number of rows to return at once;
      # can be dynamically changed by the caller through send();
      size = 1

      # multiple rows are returned as an array of dictionaries;
      results = []

      # iterate through the result set;
      while dmAPIExec(f"next,{session},{query_id}"):
         result = {}
         nb_attrs = dmAPIGet(f"count,{session},{query_id}")
         if nb_attrs is None:
            raise dmException(origin = "co_select", message = "count")
         nb_attrs = int(nb_attrs) 
         for i in range(nb_attrs):
            if 0 == row_counter:
               # get the attributes' names only once for the whole query;
               value = dmAPIGet(f"get,{session},{query_id},_names[{str(i)}]")
               if value is None:
                  raise dmException(origin = "co_select", message = f"get ,_names[{str(i)}]")
               attr_names.append(value)

            is_repeating = dmAPIGet(f"repeating,{session},{query_id},{attr_names[i]}")
            if is_repeating is None:
               raise dmException(origin = "co_select", message = f"repeating {attr_names[i]}")
            is_repeating = 1 == int(is_repeating)

            if is_repeating:
               # multi-valued attributes;
               result[attr_names[i]] = []
               count = dmAPIGet(f"values,{session},{query_id},{attr_names[i]}")
               if count is None:
                  raise dmException(origin = "co_select", message = f"values {attr_names[i]}")
               count = int(count)

               for j in range(count):
                  value = dmAPIGet(f"get,{session},{query_id},{attr_names[i]}[{j}]")
                  if value is None:
                     value = "null"
                  result[attr_names[i]].append(value)
            else:
               # mono-valued attributes;
               value = dmAPIGet(f"get,{session},{query_id},{attr_names[i]}")
               if value is None:
                  value = "null"
               result[attr_names[i]] = value

         row_counter += 1
         results.append(result)

         size -= 1
         if size > 0:
            # a grouping has been requested;
            continue 
         
         while True:
            # keeps returning the same results until the group size is non-negative;
            # default size value if omitted is 1, so next(r) keeps working;
            # if the size is 0, abort the result set;
            size = yield results
            if size is None:
               # default value is 1;
               size = 1
               break
            if size >= 0:
               # OK if size is positive or 0;
               break
         results = []
         if 0 == size: break

      err_flag = dmAPIExec(f"close,{session},{query_id}")
      if not err_flag:
         raise dmException(origin = "co_select", message = "close")

      # if here, it means that the full result set has been read;
      # the finally clause will return the residual (i.e. out of the yield statement above) rows;

   except dmException as dme:
      show(LOG_LEVEL.error, dme)
      show(LOG_LEVEL.error, dmAPIGet(f"getmessage,{session}").rstrip())
   except Exception as e:
      show(LOG_LEVEL.error, "exception in co_select():")
      show(LOG_LEVEL.error, e)
      if LOG_LEVEL.log_level > LOG_LEVEL.error: traceback.print_stack()
   finally:
      # close the collection;
      try:
         show(LOG_LEVEL.debug, "exiting co_select()")
         dmAPIExec(f"close,{session},{query_id}")
      except Exception as e:
         pass
      return results
      # for some unknown reason, an exception is raised on returning ...;
      # let the caller handle it;

def select_to_dict(session, dql_stmt):
   """
   new version of the former select2dict();
   execute in session session the DQL SELECT statement passed in dql_stmt and return the result set into an array of dictionaries;
   as the whole result set will be held in memory, be sure it is really necessary and rather use the more efficient co_select();
   """
   result = co_select(session, dql_stmt)
   return [row for row in result]

def result_to_stdout(result, format = "table", column_width = 20, mode = "wrap", frame = True, fg_color = "BLACK", bg_color = "white", alt_period = 5, col_mode = 2):
   """
      print the list of dictionaries result into a table with column_width-wide columns and optional wrap-around and frame;
      result can be a generator from co_select() or an array of dictionaries;
      the output is like from idql only more readable with column wrap-around if values are too wide;
      if frame is True, a frame identical to the one from mysql/postgresql is drawn around the table;
      in order to increase readability, rows can be colorized by specifying a foreground and a background colors;
      alt_period is the number of rows to print in fg_color/bg_color before changing to bg_color/fg_color;
      if col_mode is:
         0: no colorization is applied;
         1: text color alternates between fg/bg and bg/fg every alt_period row blocks;
         2: alt_period row blocks are colorized 1st line fg/bg and the rest bg/fg
      color naming is different of termcolor's; we use the following convention which is later converted to termcolor's:
      bright text colors (does not apply to background color) are identified by the uppercase strings: "BLACK", "RED", "GREEN", "YELLOW", "BLUE", "MAGENTA", "CYAN", "WHITE";
      normal intensity colors are identified by the capitalized lowercase strings: "Black", "Red", "Green", "Yellow", "Blue", "Magenta", "Cyan", "White";
      dim intensity colors are identified by the lowercase strings: "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white";
   """

   # let's use the termcolor package wrapper around the ANSI color escape sequences;
   from copy import deepcopy
   from termcolor import colored, cprint

   if fg_color[0].isupper() and fg_color[1:].islower():
      # capitalized name: normal intensity;
      fg_color = fg_color.lower()
      attr = []
   elif fg_color.islower():
      # all lowercase name: dim intensity;
      attr = ["dark"]
   elif fg_color.isupper():
      # all uppercase name: bright intensity;
      attr = ["bold"]
      fg_color = fg_color.lower()
   else:
      show(LOG_LEVEL.error, f"unsupported color {fg_color}; it must either be all uppercase or all lowercase or capitalized lowercase")
   if bg_color.isupper():
      bg_color = bg_color.lower()
   elif not bg_color.islower():
      show(LOG_LEVEL.error, f"unsupported color {bg_color}; it must either be all uppercase or all lowercase")

   # remap black to termcolor's grey;
   if "black" == fg_color:
      fg_color = "grey"
   if "black" == bg_color:
      bg_color = "grey"

   bg_color = "on_" + bg_color
   color_current_block = 0

   def colorization(index):
      nonlocal color_current_block, ind
      if 0 == col_mode:
         return "", "", []
      elif 1 == col_mode:
         #1: fg/bg every alt_period rows then switch to bg/fg for alt_period rows, then back again;
         if 0 == index % alt_period: 
            color_current_block = (color_current_block + 1) % 2 
         return fg_color, bg_color, attr + ["reverse"] if 0 == color_current_block % 2 else attr
      else:
         #2: fg/bg as first line of every alt_period rows, then bg/fg;
         return fg_color, bg_color, attr if 0 == index % alt_period else attr + ["reverse"]

   def rows_to_stdout(rows, no_color = False):
      """
         print the list of dictionaries in rows in tabular format using the parent function's parameters;
      """
      btruncate = "truncate" == mode
      ellipsis = "..."
      for i, row in enumerate(rows):
         # preserve the original data as they may be referenced elsewhere;
         row = deepcopy(row)
         # hack to keep history of printed rows...;
         col_fg, col_bg, col_attr = colorization(max(ind,i)) if 0 != col_mode and not no_color else ("white", "on_grey", [])
         while True:
            left_over = ""
            line = ""
            nb_fields = len(row)
            for k,v in row.items():
               nb_fields -= 1
               Min = max(column_width, len(ellipsis)) if btruncate else column_width

               # extract the next piece of the column and pad it with blanks to fill the width if needed;
               if isinstance(v, list):
                  # process repeating attributes;
                  columnS = "{: <{width}}".format(v[0][:Min] if v else "", width = column_width)
                  restColumn = btruncate and v and len(v[0]) > Min
               else:
                  columnS = "{: <{width}}".format(v[:Min], width = column_width)
                  restColumn = btruncate and v and len(v) > Min
               if restColumn:
                  columnS = columnS[ : len(columnS) - len(ellipsis)] + ellipsis

               # cell content colored only vs. the whole line;
               #line += ("|  " if frame else "") + colored(columnS, col_fg, col_bg, col_attr) + ("  " if frame else ("  " if nb_fields > 0 else ""))
               line += colored(("|  " if frame else "") + columnS + ("  " if frame or nb_fields > 0 else ""), col_fg, col_bg, col_attr)

               if isinstance(v, list):
                  # process repeating attributes;
                  restS = v[0][Min : ] if v else ""
                  if restS:
                     v[0] = restS
                  elif v:
                     # next repeating value;
                     v.pop(0)
                     restS = v[0] if v else ""
               else:
                  restS = v[Min : ]
                  row[k] = v[Min : ]
               left_over += "{: <{width}}".format(restS, width = column_width)
            # cell content colored only vs. the whole line;
            #print(line + ("|" if frame else ""))
            print(line + colored("|" if frame else "", col_fg, col_bg, col_attr))
            left_over = left_over.rstrip(" ")
            if not left_over or btruncate:
               break

   def print_frame_line(nb_columns, column_width = 20):
      line = ""
      while nb_columns > 0:
         line += "+" + "{:-<{width}}".format('', width = column_width + 2 + 2)
         nb_columns -= 1
      line += "+"
      print(line)
      return line

   # result_to_stdout;
   try:
      if "json" != format and "table" != format:
         raise dmException(origin = "result_to_stdout", message = "format must be either json or table")
      if "wrap" != mode and "truncate" != mode:
         raise dmException(origin = "result_to_stdout", message = "invalid mode; mode must be either wrap or truncate")
      if "json" == format:
         for r in result:
            print(json.dumps(r, indent = 3))
      else:
         for ind, r in enumerate(result):
            # print the rows in result set or list one at a time;
            if 0 == ind:
               # print the column headers once;
               # print the frame's top line;
               frame_line = print_frame_line(len(r[0]), column_width)
               rows_to_stdout([{k:k for k,v in r[0].items()}], no_color = True)
               print(frame_line)
            rows_to_stdout(r)
         # print the frame's bottom line;
         print(frame_line)
   except dmException as dme:
      show(LOG_LEVEL.error, dme)

def select_to_stdout(session, dql_stmt, format = "table", column_width = 20, mode = "wrap", frame = True, fg_color = "BLACK", bg_color = "white", alt_period = 5, col_mode = 2):
   """
   execute in session session the DQL SELECT statement passed in dql_stmt and sends the properly formatted result to stdout;
   if format == "json", json.dumps() is invoked for each document;
   if format == "table", document is output in tabular format;
   """
   result = co_select(session, dql_stmt)
   result_to_stdout(result, format, column_width, mode, frame, fg_color, bg_color, alt_period, col_mode)

def paginate(cursor, initial_page_size, max_cache_size = MAX_CACHE_SIZE):
   """
   Takes the generator cursor and returns a closure handle that allows to move forwards and backwards in the result set it is bound to;
   a closure is used here so a context is preserved between calls (an alternate implementation could use a co-routine or a class);
   returns None if the result set is empty;
   rows are returned as an array of dictionaries;
   i.e. if the page size (in rows) is negative, the cursor goes back that many rows, otherwise it moves forwards;
   pages can be resized by passing a new page_size to the handle;
   use a page size of 0 to close the cursor;
   Usage:
          cursor = co_select(session, dql_stmt)
          handle = paginate(cursor, max_cache_size = 1000)
          # paginate forwards 50 rows:
          handle(50)
          # paginate backwards 50 rows:
          handle(-50)
          # change page_size to 50 rows while moving forward 20 rows;
          handle(20, 50)
          # close cursor;
          handle(0)
          cursor.send(0)
   the rows from the result set that have been fetched so far are kept in cache so that they can be returned when paginating back;
   the cache is automatically extended when paginating forwards; it is never emptied so it can be heavy on memory if the result set is very large and the forwards pagination goes very far into it;
   the cache has a settable max_cache_size limit with default MAX_CACHE_SIZE;
   """
   cache = []
   # current cache'size in rows;
   cache_size = 0

   # initialize current_page_size, it can change later;
   current_page_size = initial_page_size

   # index in cached result_set of first and last rows in page;
   current_top = current_bottom = -1 

   # start the generator;
   # one row will be in the cache before even starting paginating and this is taken into account later;
   cache = next(cursor)
   if cache:
      current_top = current_bottom = 0
      cache_size = 1
   else:
      return None

   def move_window(increment, page_size = None):
      nonlocal cache, cache_size, current_top, current_bottom, current_page_size
      if page_size is None:
         # work-around the default parameter value being fixed at definition time...
         page_size = current_page_size
      # save the new page size in case it has changed;
      current_page_size = page_size
      if increment > 0:
         # forwards pagination;
         if current_bottom + increment + 1 > cache_size:
            # "page fault": must fetch the missing rows to complete the requested page size;
            if current_bottom + increment > max_cache_size:
               # the cache size limit has been reached;
               # note that the above formula does not always reflect reality, i.e. if less rows are returned that asked for because the result set's end has been reached;
               # in such cases, page_size will be adjusted to fit max_cache_size;
               show(LOG_LEVEL.info, f"in cache_logic, maximum allowed cache size of {max_cache_size} reached")
               increment = max_cache_size - current_bottom
            delta = increment if cache_size > 1 else increment - 1 # because of the starting one row in cache;
            cache += cursor.send(delta)
            cache_size += delta # len(cache)
            current_bottom += delta
         else:
            current_bottom += increment
         current_top = max(0, current_bottom - page_size + 1)
         return cache[current_top : current_bottom + 1]
      elif increment < 0:
         # backwards pagination;
         increment = abs(increment)
         current_top = max(0, current_top - increment)
         current_bottom = min(cache_size, current_top + page_size) - 1
         return cache[current_top : current_bottom + 1]
      else:
         # increment is 0: close the generator;
         # must trap the strange exception after the send();
         try:
            cursor.send(0)
         except:
            pass
         return None
   return move_window

def paginate_to_stdout(session, dql_stmt, page_size = 20, format = "table", column_width = 20, mode = "wrap", frame = True, fg_color = "BLACK", bg_color = "white", alt_period = 5, col_mode = 2):
   """
      execute the dql statement dql_stmt in session session and output the result set in json or table format; if a tabular format is chosen, page_size is the maximum number of rows displayed at once;
      returns a handle to request the next pages or navigate backwards;
      example of usage:
              h = paginate_to_stdout(s, "select r_object_id, object_name, r_version_label from dm_document")
              if h:
                 # start the generator;
                 next(h)
                 # navigate the result set;
                 # paginate forwards 10 rows;
                 h.send(10)
                 # paginate forwards 20 rows;
                 h.send(20)
                 # paginate backwards 15 rows;
                 h.send(-15)
                 # close the handle; 
                 h.send(0)

   """
   try:
      q = co_select(session, dql_stmt)
      if not q:
         return None
      handle = paginate(q, page_size)
      while True:
         nb_rows = yield handle
         if nb_rows is None:
            # default value is 1;
            nb_rows = 1
         if 0 == nb_rows:
            # exit request;
            break
         result_to_stdout([handle(nb_rows)], format, column_width, mode, frame, fg_color, bg_color, alt_period, col_mode)
   except Exception as e:
      #show(LOG_LEVEL.error, e)
      pass

def describe(session, dm_type, is_type = True, format = "table", column_width = 20, mode = "wrap", frame = True, fg_color = "WHITE", bg_color = "BLACK", alt_period = 5, col_mode = 2):
   """
   describe dm_type as a type if is_type is True, as a registered table otherwise;
   optionally displays the output into a table or json if format is not None;
   returns the output of api's describe verb or None if an error occured;
   """
   show(LOG_LEVEL.debug, f"in describe(), dm_type={dm_type}")
   try:
      dump_str = dmAPIGet(f"describe,{session},{'type' if is_type else 'table'},{dm_type}")
      if dump_str is None:
         raise dmException(origin = "describe()", message = f"bad parameter {dm_type}")
      s = [{"attribute": l[0], "type": l[1]} for l in [i.split() for i in dump_str.split("\n")[5:-1]]]
      if format:
         result_to_stdout([s], format, column_width, mode, frame, fg_color, bg_color, alt_period, col_mode)
   except dmException as dme:
      show(LOG_LEVEL.error, dme)
      show(LOG_LEVEL.error, dmAPIGet(f"getmessage,{session}").rstrip())
   finally:
      show(LOG_LEVEL.debug, "exiting describe()")
      return dump_str

def disconnect(session):
   """
   closes the given session;
   returns True if no error, False otherwise;
   """
   show(LOG_LEVEL.debug, "in disconnect()")
   try:
      status = dmAPIExec("disconnect," + session)
   except Exception as e:
      show(LOG_LEVEL.error, "exception in disconnect():")
      show(LOG_LEVEL.error, e)
      if LOG_LEVEL.log_level > LOG_LEVEL.error: traceback.print_stack()
      status = False
   finally:
      show(LOG_LEVEL.debug, "exiting disconnect()")
      return status

# call module initialization;
dmInit()

Some comments

A few comments are in order. I´ll skip the ctypes part because it was already presented in the original blog.
On line 39, class LOG_LEVEL is being defined to encapsulate the verbosity levels, and the current one, of the error messages. Levels are inclusive of lesser ones; set LOG_LEVEL.log_level to LOG_LEVEL.no_log to turn off error messages. Default verbosity level is error, which means that only error messages are output, not debugging messages such as on function entry and exit.
On line 55, class dmException defines the custom exception used to raise Documentum errors. The linked-in C library libdmcl.so does not raise exceptions, their calls just return a TRUE or FALSE status (non-zero or zero value). The interface remaps those values to True or False, or sometimes None. Default exception Exception is still handled, more so for uniformity reason rather than out of real necessity, although it cannot be totally excluded that ctypes can raise some exception of it own under some circumstances. else and finally clauses are frequently used to remap the status or result value, return it, and cleaning up.
One line 235, function co_select() is defined. This is really the main function of the whole interface. Its purpose is to execute a SELECT DQL statement and return the rows on-demand, rather than into one potentially large in-memory list of dictionaries (reminder: pythons lists are respectively equivalent to arrays, and dictionaries to records or hashes, or associative arrays in other languages). On line 316, the yield statement makes this possible; it is this statement that turns a usual, unsuspecting function into a generator. Here, yield works both ways: it returns a row but can also optionally accept a number of rows to return at once, and 0 to stop the generator. The exception handler´s finally clause closes the collection and returns the residual rows that were fetched but not returned yet because the end of the collection was reached and the yield statement was not executed.
One of the biggest pros of generators, in addition to saving memory, is to separate the navigation into the result set from the processing of the received data. Low-level, dirty technical details are therefore segregated into their own function out of the way of high-level data processing, resulting in a clearer and less distracting code.
One line 352, the function select_to_dict() is defined for those cases where it still makes sense to hold a whole result set in memory at once. It does almost nothing, as the bulk of the work is done by co_select(). Line 359 executes a list comprehension that takes the generator returned by co_select() and forces it to be iterated until it meets its stop condition.
Skipping to line 510, function select_to_stdout() is another application of co_select(). This time, the received generator is passed to function result_to_stdout() defined on line 361; this function exemplifies outputting the data in a useful manner: it displays them to stdout either in json through the imported json library, or in tabular format. It can be used elsewhere each time such a presentation is sensible, e.g. from function describe() below, just make sure that the data are passed as a singleton list of a list of dictionaries (i.e. a list whose sole element is a list of dictionaries).
There isn’t much to add about the well-known json format (see an example below) but the tabular presentation is quite rich in functionalities. It implements in python what was presented here and here with the addition of color; the driving goal was to get a readable and comfortable table output containing documents as rows and their attributes as columns. Interactivity can be achieved by piping the output of the function into the less utility, as illustrated below:

$ pip install termcolor
$ export PYTHONPATH=/home/dmadmin/dctm-DctmAPI:/home/dmadmin/.local/lib/python3.5/site-packages
$ cat - < test-table.py 
#!/usr/bin/python3.6
import DctmAPI

s = DctmAPI.connect("dmtest73", "dmadmin", "dmadmin")
DctmAPI.select_to_stdout(s, "select r_object_id, object_name, title, owner_name, subject, r_version_label from dm_document", format = "table", column_width = 30, mode = "wrap", frame = True, fg_color = "YELLOW", bg_color = "BLACK", alt_period = 5, col_mode = 2)
eot
$ chmod +x test-table.py
$ ./test-table.py | less -R

Result:

Here, a tabular (format = “table”, use format = “json” for json output) representation of the data returned by the DQL statement has been requested with 30 character-wide columns (column_width = 30); attributes too large to fit in their column are wrapped around; they could have been truncated by setting mode = “truncate”. A frame à la mysql or postgresql has been requested with frame = True. Rows colorization has been requested with the first line every 5 rows (alt_period = 5) in reverse color yellow on black and the others in black on yellow (col_mode = 2; use col_mode = 1 for alt_period lines large alternating colored fg/bg bg/fg blocks, and col_mode = 0 for no colorization).
The simple but very effective termcolor ANSI library is used here, which is a real relief compared to having to reimplement one myself for the 2nd or 3rd time in my life…
Note the use of the less command with the -R option so ANSI color escape sequences are passed through to the terminal and correctly rendered.
As a by-product, let’s generalize the snippet above into an independent, reusable utility:

$ cat test-table.py
#!/usr/bin/python3.6 
import argparse
import DctmAPI
                
if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('-d', '--docbase', action='store',
                        default='dmtest73', type=str,
                        nargs='?',
                        help='repository name [default: dmtest73]')
    parser.add_argument('-u', '--user_name', action='store',
                        default='dmadmin',
                        nargs='?',
                        help='user name [default: dmadmin]')
    parser.add_argument('-p', '--password', action='store',
                        default='dmadmin',
                        nargs='?',
                        help='user password [default: "dmadmin"]')
    parser.add_argument('-q', '--dql_stmt', action='store',
                        nargs='?',
                        help='DQL SELECT statement')
    args = parser.parse_args()
            
    session = DctmAPI.connect(args.docbase, args.user_name, args.password)
    if session is None:
       print(f"no session opened in docbase {args.docbase} as user {args.user_name}, exiting ...")
       exit(1)

    DctmAPI.select_to_stdout(session, args.dql_stmt, format = "table", column_width = 30, mode = "wrap", frame = True, fg_color = "YELLOW", bg_color = "BLACK", alt_period = 5, col_mode = 2)

# make it self-executable;
$ chmod +x test-table.py

# test it !
$ ./test-table.py -q "select r_object_id, object_name, title, owner_name, subject, r_version_label from dm_document" | less -R

# ship it !
# nah, kidding.

For completeness, here is an example of a json output:

s = DctmAPI.connect("dmtest73", "dmadmin", "dmadmin")
DctmAPI.select_to_stdout(s, "select r_object_id, object_name, r_version_label from dm_document", format = "json")
[
   {
      "r_object_id": "0900c350800001d0",
      "object_name": "Default Signature Page Template",
      "r_version_label": [
         "CURRENT",
         "1.0"
      ]
   }
]
...
[
   {
      "r_object_id": "0900c350800001da",
      "object_name": "Blank PowerPoint Pre-3.0 Presentation",
      "r_version_label": [
         "CURRENT",
         "1.0"
      ]
   }
]

Note the embedded list for the repeating attribute r_version_label; unlike relational tables, the json format suits perfectly well documents from repositories. It is not ready to support Documentum’s object-relational model but it is close enough. Maybe one day, once hell has frozen over -;), we’ll see a NOSQL implementation of Documentum, but I digress.
Back to the code, on line 604 paginate_to_stdout() is defined. This function allows to navigate a result set forwards and backwards into a table; the latter is possible by caching (more exactly, saving, as the data are cumulative and never replaced), the rows received so far. As parameters, it takes a cursor for the opened collection, a page size and the maximum cache size. In order to preserve its context, e.g. the cache and the pointers to the first and last rows displayed from the result set, the function’s chosen implementation is that of a closure, with the inner function move_window() returned to the caller as a handle. Alternative implementations could be a class or a co-routine again. move_windows() requests the rows from the cursor via send(nb_rows) as previously explained and returns them as a list. A negative nb_rows means to navigate backwards, i.e. the requested rows are returned from the cache instead of the cursor. Obviously, as the cache is dynamically extended up to the specified size and its content never released to make room for the new rows, if one paginates to the bottom of a very large result set, a lot of memory can still be consumed as the whole result set finishes up in memory. A more conservative implementation could get rid of older rows to accomodate the new ones but at the cost of a reduced history depth, so it’s a trade-off; anyway, this subject is out of scope.
As its usage protocol may not by that simple at first, an example function paginate_to_stdout() is defined as a co-routine starting on line 604, with the same parameters as in select_to_stdout(). It can be used as follows:

# connect to the repository;
s = DctmAPI.connect("dmtest73", "dmadmin", "dmadmin")

# demonstration of DctmAPI.paginate_to_stdout();
# request a pagination handle to the result set returned for the SELECT dql query below;
h = DctmAPI.paginate_to_stdout(s, "select r_object_id, object_name, title, owner_name, subject, r_version_label from dm_document", page_size = 10, format = "table", column_width = 30, mode = "wrap", frame = True, fg_color = "RED", bg_color = "black", alt_period = 5, col_mode = 1)  

print("starting the generator")
next(h)

nb_rows = 3
print(f"next {nb_rows} rows")
h.send(nb_rows)

nb_rows = 10
print(f"next {nb_rows} rows")
h.send(nb_rows)

nb_rows = 1
print(f"next {nb_rows} rows")
h.send(nb_rows)

nb_rows = -4
print(f"previous {nb_rows} rows")
h.send(nb_rows)

nb_rows = 2
print(f"next {nb_rows} rows")
h.send(nb_rows)

nb_rows = -7
print(f"previous {nb_rows} rows")
h.send(nb_rows)

nb_rows = -10
print(f"previous {nb_rows} rows")
h.send(nb_rows)

print(f"exiting ...")
try:
   h.send(0)
except:
   # trap the StopIteration exception;
   pass
sys.exit()

Here, each call to send() results in a table being displayed with the requested rows, as illustrated below:



The snippet above could be generalized to a stand-alone interactive program that reads from the keyboard a number of rows as an offset to move backwards or forwards, if saving the whole result set into a disk file is too expensive and only a few pages are requested, but DQL has the limiting clause enable(return_top N) for this purpose. so such an utility is not really useful.
On line 642, the describe() function returns as-is the result of the eponymous api verb, i.e. as a raw string with each item delimited by an end of line character (‘\n’character under Linux) for further processing by the caller; optionally, it can also output it as a table or as a json literal by taking profit of the function result_to_stdout() and passing it the data that were appropriately formatted on line 653 as a list of one list of dictionaries.
Finally, on line 681, the module is automatically initialized at load time.

Conclusion

The python language has quite evolved from v2 to v3, the latest as of this writing being 3.9. Each version brings a few small, visible enhancements; an example of which are the formatting f’strings (no pun intended), which were used here. Unfortunately, they need python 3.6 minimum, which breaks compatibility with previous releases; fortunately, they can be easily replaced with older syntax alternatives if need be.
As usual, the DctmAPI does not pretend to be the best python interface to Documentum ever. It has been summarily tested and bugs could still be lurking around. I know, there are lots of improvements and functionalities possible, e.g. displaying acls and users and groups, maybe wrapping the module into classes, using more pythonic constructs, to name but a few. So, feel free to add your comments, corrections and suggestions below. They will all be taken into consideration and maybe implemented too if interesting enough. In the meantime, take care of yourself and your family. Happy New Year to everyone !

Cet article DctmAPI.py revisited est apparu en premier sur Blog dbi services.

Exoscale scaling up and down

$
0
0

In my previous Blog I had wrote about Swiss Cloud Provider: Exoscale. Read the Blog
Today we will look little bit deeper in three subjects:

  • Template & Template sharing
  • Scaling up and down instances
  • dbi CentOS 8 & Debian 10 Templates

Templating

On Exoscale you can create custom Template from you current Instance. Custom Templates can make your daily work easier. We will look also how we can share Templates.  Starting with the GUI:
1. Go to your instance ( Computer -> Instances -> [instance_name] -> scroll to Snapshot)
2. Click on “Create Snapshot”
3. Your Snapshot has been created 🙂

From the CLI:
1. Get VM Id

exo vm list

2. Create the snapshot

exo vm snapshot create  

3. Your Snapshot has been created 🙂

Let’s create now our custom Template using CLI:

exo vm template register centos8-pg13-dmk --from-snapshot e39344f6-d1c6-4ea6-857c-98dc1fce069a --description "CentOS 8 server with PostgreSQL 13"

IMPORTANT:
If your Instance has UEFI Bootmode enabled, then you need to give following parameter:

--boot-mode uefi

Otherwise your template will not boot.

Here is the full command with UEFI boot:

exo vm template register centos8-pg13-dmk --from-snapshot e39344f6-d1c6-4ea6-857c-98dc1fce069a --description "CentOS 8 server with PostgreSQL 13" --boot-mode uefi

Creating the template can take few minutes. After it has been created successfully, you will see the Template in the GUI:

Sharing Templates

You can share your templates with by generating a link. Exoscale will generate the URL automatically for you:
First of all you need to export the VM

exo vm list
exo vm snapshot export 14cd6418-5fae-4003-85ee-87d409a0e4bb

This command will generate a URL. You can send the generated URL to your Team Members / customers.
FYI: The exported snapshot’s URL is only valid for 6 hours. You can always re-issue export commands to get a fresh URL but if you wish for it to be valid for longer, you should indeed download the snapshot and re-upload it in a public location such as a bucket of yours with a public ACL.

To import the VM from url, the command is following:

exo vm template register --url 

Scaling up and down

Scaling up and down of your instance is very easy. Open your instance, click on “Scale” and change your Instance size.

IMPORTANT:
Decreasing the DISK is not possible. After you added more space to your disk, you need to restart your instance.

Our Templates

We prepared for our Customers two new Templates:
– CentOS 8 with PostgreSQL 13 installed and DMK configured.
– Debian 10 with PostgreSQL 13 installed and DMK configured.

DMK (Database Management Kit) allows dba’s to manage their database environment easier. With one command you can create new instance, restart your instance etc. For more Information you can visit following website: Database Management Kit

Cet article Exoscale scaling up and down est apparu en premier sur Blog dbi services.

Google Spanner – SQL compatibility

$
0
0

By Franck Pachot

.
I have posted, a long time ago, about Google Spanner (inserting data and no decimal numeric data types) but many things have changed in this area. There is now a NUMERIC data type and many things have improved in this distributed SQL database, improving a bit the SQL compatibility.

gcloud

I can use the Cloud Shell, which is very easy – one click fron the console – but here I’m showing how to install the gcloud CLI temporarily in a OEL environement (I’ve explained in a previous post how the OCI free tier is my preferred home)


curl https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-321.0.0-linux-x86_64.tar.gz | tar -C /var/tmp -zxf -
/var/tmp/google-cloud-sdk/install.sh --usage-reporting true --screen-reader false --rc-path /tmp/gcloud.tmp --command-completion true --path-update true --override-components

This downloads and unzips the Google Cloud SDK for Linux (there are other options like a YUM repo). I put it in a temporary directory here under /var/tmp. install.sh is interactive or you can supply all information on command line. I don’t want it to update my .bash_profile or .bashrc but want to see what it puts there, so just providing a temporary /tmp/gcloud.sh to have a lookt at it


[opc@a ~]$ /var/tmp/google-cloud-sdk/install.sh --usage-reporting true --screen-reader false --rc-path /tmp/gcloud.sh --command-completion true --path-update true --override-components

Welcome to the Google Cloud SDK!

Your current Cloud SDK version is: 321.0.0
The latest available version is: 321.0.0

┌────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│                                                 Components                                                 │
├───────────────┬──────────────────────────────────────────────────────┬──────────────────────────┬──────────┤
│     Status    │                         Name                         │            ID            │   Size   │
├───────────────┼──────────────────────────────────────────────────────┼──────────────────────────┼──────────┤
...
│ Installed     │ BigQuery Command Line Tool                           │ bq                       │  < 1 MiB │
│ Installed     │ Cloud SDK Core Libraries                             │ core                     │ 15.9 MiB │
│ Installed     │ Cloud Storage Command Line Tool                      │ gsutil                   │  3.5 MiB │
└───────────────┴──────────────────────────────────────────────────────┴──────────────────────────┴──────────┘

[opc@a ~]$ cat /tmp/gcloud.tmp

# The next line updates PATH for the Google Cloud SDK.
if [ -f '/var/tmp/google-cloud-sdk/path.bash.inc' ]; then . '/var/tmp/google-cloud-sdk/path.bash.inc'; fi

# The next line enables shell command completion for gcloud.
if [ -f '/var/tmp/google-cloud-sdk/completion.bash.inc' ]; then . '/var/tmp/google-cloud-sdk/completion.bash.inc'; fi

As you can see, I mentioned nothing for –override-components and the default is gsutil, core and bq


This is the most simple cloud CLI I ever seen. Just type: gcloud init
(/var/tmp/google-cloud-sdk/bin/gcloud in my temporary installation) and it gives you an URL where you can get the verification code, using the web console authentication. Then you pick the cloud project and, optionally, a default region and zone. Those default informations are stored in: ~/.config/gcloud/configurations/config_default
and the credentials are in a sqllite database in ~/.config/gcloud/credentials.db

Sample SQL data

Gerald Venzl has recently published some free to use data about the world’s countries, capitals, and currencies. What I like with this data set is that, in addition to providing the CSV, Gerald have managed to provide a unique DDL + DML to create this data in SQL databases. And this works in the most common databases despite the fact that, beyond the SQL standard, data types and syntax is different in each engine.

Google Spanner has a SQL-like API but I cannot run this without a few changes. But just a few, thanks to many recent improvements. And loading this data set will be the occasion to show those new features.

Primary key

Here is the DDL to create the first table, REGIONS:


/*********************************************/
/***************** REGIONS *******************/
/*********************************************/
CREATE TABLE regions
(
  region_id     VARCHAR(2)   NOT NULL,
  name          VARCHAR(13)  NOT NULL,
  CONSTRAINT regions_pk
    PRIMARY KEY (region_id)
);

In order to run this in Google Spanner, I change VARCHAR to STRING and I move the PRIMARY KEY declaration out of the relational properties:


CREATE TABLE regions
(
  region_id     STRING(2)   NOT NULL,
  name          STRING(13)  NOT NULL
)
    PRIMARY KEY (region_id)
;

It may be surprising to have the PRIMARY KEY declaration at this place but, because Google Spanner is a distributed database, the primary key is also a storage attribute. And it is mandatory as sharding is done by range partitioning on the primary key. Well, I would prefer to stay compatible with SQL and have, if needed, an additional organization clause. But Spanner is one of the first database trying to bring SQL to NoSQL and was originally designed to be used internally (like Google object storage matadata), providing SQL database benefits (SQL, ACID, joins,…) with the same scalability as distributed NoSQL databases. So compatibility with other SQL databases was probably not a priority.

Note that the NOT NULL constraint is allowed and we will see more about columns constraints later.

I have removed the comments because this is not allowed in Google Spanner. I don’t understand that, but remember that it takes its roots in NoSQL where the API calls are embedded in the code, and not in scripts, and the code has its own comments.

Foreign key

The second table is COUNTRIES and a country belongs to a region from the REGION table.


/*********************************************/
/**************** COUNTRIES ******************/
/*********************************************/
CREATE TABLE countries
(
  country_id    VARCHAR(3)     NOT NULL,
  country_code  VARCHAR(2)     NOT NULL,
  name          VARCHAR(100)   NOT NULL,
  official_name VARCHAR(200),
  population    NUMERIC(10),
  area_sq_km    NUMERIC(10,2),
  latitude      NUMERIC(8,5),
  longitude     NUMERIC(8,5),
  timezone      VARCHAR(40),
  region_id     VARCHAR(2)     NOT NULL,
  CONSTRAINT countries_pk
    PRIMARY KEY (country_id),
  CONSTRAINT countries_regions_fk001
    FOREIGN KEY (region_id) REFERENCES regions (region_id)
);

CREATE INDEX countries_regions_fk001 ON countries (region_id);

On the datatypes, I’ll change VARCHAR to STRING and now we have a NUMERIC datatype in Spanner (was only IEEE 754 float) but NUMERIC has a fixed scale and precision (38,9) I’ll probably come back to it in another post. But there are still many limitations with NUMERIC (cannot create index on it, not easy to map when importing,…). It is definitely not a good choice here for areas, latitude and longitude. But I keep it just for DDL compatibility.

I move the PRIMARY KEY definition. But the most important here is actually about not changing the referential constraint at all:


CREATE TABLE countries
(
  country_id    STRING(3)     NOT NULL,
  country_code  STRING(2)     NOT NULL,
  name          STRING(100)   NOT NULL,
  official_name STRING(200),
  population    NUMERIC,
  area_sq_km    NUMERIC,
  latitude      NUMERIC,
  longitude     NUMERIC,
  timezone      STRING(40),
  region_id     STRING(2)     NOT NULL,
  CONSTRAINT countries_regions_fk001
    FOREIGN KEY (region_id) REFERENCES regions (region_id)
)
PRIMARY KEY (country_id)
;

Before March 2020 The only possible referential integrity way actually a storage clause (at the same place as the PRIMARY KEY declaration) because referential integrity is not something easy in a distributed database. Because you distribute to scale and that works well only when your transaction is single-shard. This is why, initially, referential integrity was not a FOREIGN KEY but a compound PRIMARY KEY interleaved with the parent PRIMARY KEY. But we will see INTERLEAVE IN PARENT later. Here, COUTRIES has its own primary key, and then its own sharding scheme. That’ also mean that inserting a new country may have to check the parent key (region) in another shard. We will look at performance in another post.

When we declare a foreign key in Google Spanner, an index on it is created, then I didn’t copy the CREATE INDEX statement.

Check constraints


/*********************************************/
/***************** CITIES ********************/
/*********************************************/

CREATE TABLE cities
(
  city_id       VARCHAR(7)    NOT NULL,
  name          VARCHAR(100)  NOT NULL,
  official_name VARCHAR(200),
  population    NUMERIC(8),
  is_capital    CHAR(1)       DEFAULT 'N' NOT NULL,
  latitude      NUMERIC(8,5),
  longitude     NUMERIC(8,5),
  timezone      VARCHAR(40),
  country_id    VARCHAR(3)    NOT NULL,
  CONSTRAINT cities_pk
    PRIMARY KEY (city_id),
  CONSTRAINT cities_countries_fk001
    FOREIGN KEY (country_id) REFERENCES countries (country_id),
  CONSTRAINT cities_is_capital_Y_N_check001
    CHECK (is_capital IN ('Y','N'))
);

CREATE INDEX cities_countries_fk001 ON cities (country_id);

In addition to the datatypes we have seen earlier I’ll transform CHAR to STRING, but I have to remove the DEFAULT declaration. Spanner recently introduced generated columns but those are always calculated, they cannot substitute to DEFAULT.
We declare check constraints since Oct. 2020 and I keep the same declaration. As far as I know they are not used by the optimizer but only to validate the data ingested.

I have a foreign key to COUNTRIES which I keep as non-interleaved. Because the COUNTRY_ID is not part of the CITIES primary key, and maybe because my data model may have to cope with cities changing to another country (geopolitical immutability).


CREATE TABLE cities
(
  city_id       STRING(7)    NOT NULL,
  name          STRING(100)  NOT NULL,
  official_name STRING(200),
  population    NUMERIC,
  is_capital    STRING(1)       NOT NULL,
  latitude      NUMERIC,
  longitude     NUMERIC,
  timezone      STRING(40),
  country_id    STRING(3)    NOT NULL,
  CONSTRAINT cities_countries_fk001
    FOREIGN KEY (country_id) REFERENCES countries (country_id),
  CONSTRAINT cities_is_capital_Y_N_check001
    CHECK (is_capital IN ('Y','N'))
)
    PRIMARY KEY (city_id)
;

Again, the index on COUNTRY_ID is created implicitely.

Interleaved


/*********************************************/
/***************** CURRENCIES ****************/
/*********************************************/
CREATE TABLE currencies
(
  currency_id       VARCHAR(3)    NOT NULL,
  name              VARCHAR(50)   NOT NULL,
  official_name     VARCHAR(200),
  symbol            VARCHAR(18)   NOT NULL,
  CONSTRAINT currencies_pk
    PRIMARY KEY (currency_id)
);
/*********************************************/
/*********** CURRENCIES_COUNTRIES ************/
/*********************************************/
CREATE TABLE currencies_countries
(
  currency_id    VARCHAR(3)   NOT NULL,
  country_id     VARCHAR(3)   NOT NULL,
  CONSTRAINT currencies_countries_pk
    PRIMARY KEY (currency_id, country_id),
  CONSTRAINT currencies_countries_currencies_fk001
    FOREIGN KEY (currency_id) REFERENCES currencies (currency_id),
  CONSTRAINT currencies_countries_countries_fk002
    FOREIGN KEY (country_id)  REFERENCES countries(country_id)
);

The CURRENCIES_COUNTRIES is the implementation of many-to-many relationship as a country may have multiple currencies and a currency can be used in multiple country. The primary key is a concatenation of the foreign keys. Here I’ll decide that referential integrity is a bit stronger and the list of currencies will be stored in each country, like storing it pre-joined for performance reason. If you come from Oracle, you may see this INTERLEAVE IN PARENT as a table CLUSTER but on a partitioned table. If you come from DynamoDB you may see the of it as the Adjacency lists modeling in the single-table design.


CREATE TABLE currencies
(
  currency_id       STRING(3)    NOT NULL,
  name              STRING(50)   NOT NULL,
  official_name     STRING(200),
  symbol            STRING(18)   NOT NULL,
)
    PRIMARY KEY (currency_id)
;

CREATE TABLE currencies_countries
(
  currency_id    STRING(3)   NOT NULL,
  country_id     STRING(3)   NOT NULL,
  CONSTRAINT currencies_countries_countries_fk002
    FOREIGN KEY (country_id)  REFERENCES countries(country_id)
)
PRIMARY KEY (currency_id, country_id)
,  INTERLEAVE IN PARENT currencies ON DELETE CASCADE;

Here, the many-to-many association between CURRENCIES and COUNTRIES is materialized as a composition with CURRENCIES, stored with it (INTERLEAVE), and removed with it (ON DELETE CASCADE). Note that I did that for the demo because CURRENCY_ID was first in the primary key declaration, but you may think more about data distribution and lifecycle when deciding on interleaving. Google Spanner partitions by range, and this means that all CURRENCIES_COUNTRIES associations will be stored together, in the same shard (called “split” in Spanner) for the same CURRENCY_ID.

Multi-region instance

There are many new multi-region configurations, within the same continent or distributed over multiple ones:


gcloud spanner instance-configs list

NAME                              DISPLAY_NAME
asia1                             Asia (Tokyo/Osaka/Seoul)
eur3                              Europe (Belgium/Netherlands)
eur5                              Europe (London/Belgium/Netherlands)
eur6                              Europe (Netherlands, Frankfurt)
nam-eur-asia1                     United States, Europe, and Asia (Iowa/Oklahoma/Belgium/Taiwan)
nam10                             United States (Iowa/Salt Lake/Oklahoma)
nam11                             United States (Iowa, South Carolina, Oklahoma)
nam3                              United States (Northern Virginia/South Carolina)
nam6                              United States (Iowa/South Carolina/Oregon/Los Angeles)
nam7                              United States (Iowa, Northern Virginia, Oklahoma)
nam8                              United States (Los Angeles, Oregon, Salt Lake City)
nam9                              United States (Northern Virginia, Iowa, South Carolina, Oregon)
regional-asia-east1               asia-east1
regional-asia-east2               asia-east2
regional-asia-northeast1          asia-northeast1
regional-asia-northeast2          asia-northeast2
regional-asia-northeast3          asia-northeast3
regional-asia-south1              asia-south1
regional-asia-southeast1          asia-southeast1
regional-asia-southeast2          asia-southeast2
regional-australia-southeast1     australia-southeast1
regional-europe-north1            europe-north1
regional-europe-west1             europe-west1
regional-europe-west2             europe-west2
regional-europe-west3             europe-west3
regional-europe-west4             europe-west4
regional-europe-west6             europe-west6
regional-northamerica-northeast1  northamerica-northeast1
regional-southamerica-east1       southamerica-east1
regional-us-central1              us-central1
regional-us-east1                 us-east1
regional-us-east4                 us-east4
regional-us-west1                 us-west1
regional-us-west2                 us-west2
regional-us-west3                 us-west3
regional-us-west4                 us-west4

I’ll use the latest dual-region in my continent, eur6, added on Dec. 2020 which has two read-write regions (Netherlands, Frankfurt) and the witness region in Zurich. Yes, this neutral position of Switzerland is a perfect fit in the distributed quorum, isn’t it? 😉


time gcloud spanner instances create franck --config eur6 --nodes=3 --description Franck

Creating instance...done.

real    0m5.128s
user    0m0.646s
sys     0m0.079s

The instance is created, and we can create multiple database in it (there is no schemas, so database is the right logical isolation between database objects)


time gcloud spanner databases create test --instance=franck --ddl "
CREATE TABLE regions
(
  region_id     STRING(2)   NOT NULL,
  name          STRING(13)  NOT NULL
)
    PRIMARY KEY (region_id)
;
CREATE TABLE countries
(
  country_id    STRING(3)     NOT NULL,
  country_code  STRING(2)     NOT NULL,
  name          STRING(100)   NOT NULL,
  official_name STRING(200),
  population    NUMERIC,
  area_sq_km    NUMERIC,
  latitude      NUMERIC,
  longitude     NUMERIC,
  timezone      STRING(40),
  region_id     STRING(2)     NOT NULL,
  CONSTRAINT countries_regions_fk001
    FOREIGN KEY (region_id) REFERENCES regions (region_id)
)
PRIMARY KEY (country_id)
;
CREATE TABLE cities
(
  city_id       STRING(7)    NOT NULL,
  name          STRING(100)  NOT NULL,
  official_name STRING(200),
  population    NUMERIC,
  is_capital    STRING(1)       NOT NULL,
  latitude      NUMERIC,
  longitude     NUMERIC,
  timezone      STRING(40),
  country_id    STRING(3)    NOT NULL,
  CONSTRAINT cities_countries_fk001
    FOREIGN KEY (country_id) REFERENCES countries (country_id),
  CONSTRAINT cities_is_capital_Y_N_check001
    CHECK (is_capital IN ('Y','N'))
)
    PRIMARY KEY (city_id)
;
CREATE TABLE currencies
(
  currency_id       STRING(3)    NOT NULL,
  name              STRING(50)   NOT NULL,
  official_name     STRING(200),
  symbol            STRING(18)   NOT NULL,
)
    PRIMARY KEY (currency_id)
;
CREATE TABLE currencies_countries
(
  currency_id    STRING(3)   NOT NULL,
  country_id     STRING(3)   NOT NULL,
  CONSTRAINT currencies_countries_countries_fk002
    FOREIGN KEY (country_id)  REFERENCES countries(country_id)
)
PRIMARY KEY (currency_id, country_id)
,  INTERLEAVE IN PARENT currencies ON DELETE CASCADE;
"

ERROR: (gcloud.spanner.databases.create) INVALID_ARGUMENT: Error parsing Spanner DDL statement: \n : Syntax error on line 1, column 1: Encountered \'EOF\' while parsing: ddl_statement
- '@type': type.googleapis.com/google.rpc.LocalizedMessage
  locale: en-US
  message: |-
    Error parsing Spanner DDL statement:
     : Syntax error on line 1, column 1: Encountered 'EOF' while parsing: ddl_statement

No idea why it didn’t parse my DDL. The same in the console works, so this is how I’ve created those tables. Now re-reading this and the problem may be only that I start with an newline here before the first CREATE.

My database is ready for DDL and DML, provisioned in a few seconds.

Foreign Key indexes

You mave seen that I removed the CREATE INDEX that was in the original DDL. This is because an index is created automatically on the foreign key (which makes sense, we want to avoid full table scans at all prize in a distributed database).


gcloud spanner databases ddl update test --instance=franck --ddl="CREATE INDEX countries_regions_fk001 ON countries (region_id)"

Schema updating...failed.
ERROR: (gcloud.spanner.databases.ddl.update) Duplicate name in schema: countries_regions_fk001.

Trying to create an index with the same name as the foreign key is rejected as “duplicate name”. However, when looking at the ddl:


gcloud spanner databases ddl describe test --instance=franck | grep -i "CREATE INDEX"

Shows no index. And trying to query explicitly through this index with this name is rejected:


gcloud spanner databases execute-sql test --instance=franck --query-mode=PLAN \
 --sql "select count(region_id) from countries@{FORCE_INDEX=countries_regions_fk001}"

ERROR: (gcloud.spanner.databases.execute-sql) INVALID_ARGUMENT: The table countries does not have a secondary index called countries_regions_f
k001

However this index exists, with another name. Let’s do an explain plan when querying this column only:


gcloud spanner databases execute-sql test --instance=franck --query-mode=PLAN \
 --sql "select count(region_id) from countries"

 RELATIONAL Serialize Result
    |
    +- RELATIONAL Aggregate
    |  call_type: Global, iterator_type: Stream, scalar_aggregate: true
    |   |
    |   +- RELATIONAL Distributed Union
    |   |  subquery_cluster_node: 3
    |   |   |
    |   |   +- RELATIONAL Aggregate
    |   |   |  call_type: Local, iterator_type: Stream, scalar_aggregate: true
    |   |   |   |
    |   |   |   +- RELATIONAL Distributed Union
    |   |   |   |  call_type: Local, subquery_cluster_node: 5
    |   |   |   |   |
    |   |   |   |   \- RELATIONAL Scan
    |   |   |   |      Full scan: true, scan_target: IDX_countries_region_id_1D2B1A686087F93F, scan_type: IndexScan
    |   |   |   |
    |   |   |   \- SCALAR Function
    |   |   |      COUNT(1)
    |   |   |       |
    |   |   |       \- SCALAR Constant
    |   |   |          1
    |   |   |
    |   |   \- SCALAR Constant
    |   |      true
    |   |
    |   \- SCALAR Function
    |      COUNT_FINAL($v1)
    |       |
    |       \- SCALAR Reference
    |          $v1
    |
    \- SCALAR Reference
       $agg1

The foreign key index name is: IDX_countries_region_id_1D2B1A686087F93F


gcloud spanner databases execute-sql test --instance=franck --query-mode=PLAN \
 --sql "select count(region_id) from countries@{FORCE_INDEX=IDX_countries_region_id_1D2B1A686087F93F}" \
 | grep scan

    |   |   |   |      Full scan: true, scan_target: IDX_countries_region_id_1D2B1A686087F93F, scan_type: IndexScan

I can use this index name here. Apparently, the index that is implicitly created with the foreign key has an internal name, but also reserves the constraint name in the index namespace. Let’s validate that the initial error was about the name of the index and not the same columns:


gcloud spanner databases ddl update test --instance=franck \
 --ddl="CREATE INDEX a_silly_redundant_index ON countries (region_id)"

Schema updating...done.

This works, I can create a secondary index on the foreign key columns


gcloud spanner databases execute-sql test --instance=franck --query-mode=PLAN \
 --sql "select count(region_id) from countries"\
 | grep scan

    |   |   |   |      Full scan: true, scan_target: a_silly_redundant_index, scan_type: IndexScan

Querying without mentioning the index shows that it can be used instead of the implicit one. Yes, Google Spanner has now an optimizer that can decide to use a secondary index. But I’ll come back to that in a future blog post. Why using this new index rather than the one created implicitly, as they have the same cost? I don’t know but I’ve started it with an “a” in case alphabetical order wins…


gcloud spanner databases ddl describe test --instance=franck | grep -i "CREATE INDEX"

CREATE INDEX a_silly_redundant_index ON countries(region_id);

gcloud spanner databases ddl update test --instance=franck \
 --ddl="DROP INDEX a_silly_redundant_index"

Schema updating...done.

I remove this index which has no reason to be there.

insert

Executing multiple DML statements at once is not possible. It is possible to insert multiple rows into one table like this:


INSERT INTO regions (region_id, name) VALUES
   ('AF', 'Africa')
 , ('AN', 'Antarctica')
 , ('AS', 'Asia')
 , ('EU', 'Europe')
 , ('NA', 'North America')
 , ('OC', 'Oceania')
 , ('SA', 'South America')
;

However, the initial SQL file doesn’t insert into the same number of columns, like:


INSERT INTO cities (city_id, name, population, is_capital, latitude, longitude, country_id) VALUES ('AFG0001', 'Kabul', 4012000, 'Y', 34.52813, 69.17233, 'AFG');
INSERT INTO cities (city_id, name, official_name, population, is_capital, latitude, longitude, country_id) VALUES ('ATG0001', 'Saint John''s', 'Saint John’s', 21000, 'Y', 17.12096, -61.84329, 'ATG');
INSERT INTO cities (city_id, name, population, is_capital, latitude, longitude, country_id) VALUES ('ALB0001', 'Tirana', 476000, 'Y', 41.3275, 19.81889, 'ALB');

and then a quick replace is not easy. On this example you see something else. There are double quotes, the SQL way to escape quotes, but Spanner doesn’t like it.

Then, without writing a program to insert in bulk, here is what I did to keep the SQL statements from the source:


curl -s https://raw.githubusercontent.com/gvenzl/sample-data/master/countries-cities-currencies/install.sql \
 | awk "/^INSERT INTO /{gsub(/''/,quote);print}" quote="\\\\\\\'" \
 | while read sql ; do echo "$sql" ; gcloud spanner databases execute-sql test --instance=franck --sql="$sql" ; done \
 | ts | tee insert.log

I just replaced the quote escaping ” by //. It is slow row-by-row but all rows are inserted with the original SQL.

I have 3 nodes running, two in read-write in two regions (europe-west3 is “Frankfurt”, europe-west4 is “Netherlands”) and one acting as witness for the quorum (europe-west6 in Zürich).

select

With the sample data provided by Gerald, there’s a query to check the number of rows:


gcloud spanner databases execute-sql test --instance=franck --query-mode=NORMAL --sql "SELECT 'regions' AS Table, 7 AS provided,
COUNT(1) AS actual FROM regions                                                                                                               
UNION ALL
SELECT 'countries' AS Table, 196 AS provided, COUNT(1) AS actual FROM countries
UNION ALL
SELECT 'cities' AS Table, 204 AS provided, COUNT(1) AS actual FROM cities
UNION ALL
SELECT 'currencies' AS Table, 146 AS provided, COUNT(1) AS actual FROM currencies
UNION ALL
SELECT 'currencies_countries' AS Table, 203 AS provided, COUNT(1) AS actual FROM currencies_countries;
"

Table                 provided  actual
regions               7         7
countries             196       196
cities                204       204
currencies            146       146
currencies_countries  203       203

The result is correct. By curiosity, I’m running it with execution plan and statistics (query mode “PROFILE”):



gcloud spanner databases execute-sql test --instance=franck --query-mode=PROFILE --sql "SELECT 'regions' AS Table, 7 AS provided,
 COUNT(1) AS actual FROM regions
 UNION ALL
 SELECT 'countries' AS Table, 196 AS provided, COUNT(1) AS actual FROM countries
 UNION ALL
 SELECT 'cities' AS Table, 204 AS provided, COUNT(1) AS actual FROM cities
 UNION ALL
 SELECT 'currencies' AS Table, 146 AS provided, COUNT(1) AS actual FROM currencies
 UNION ALL
 SELECT 'currencies_countries' AS Table, 203 AS provided, COUNT(1) AS actual FROM currencies_countries;
 "

┌────────────────────┬────────────┬───────────────┬──────────────┬───────────────────┐
│ TOTAL_ELAPSED_TIME │  CPU_TIME  │ ROWS_RETURNED │ ROWS_SCANNED │ OPTIMIZER_VERSION │
├────────────────────┼────────────┼───────────────┼──────────────┼───────────────────┤
│ 14.7 msecs         │ 4.92 msecs │ 5             │ 756          │ 2                 │
└────────────────────┴────────────┴───────────────┴──────────────┴───────────────────┘
 RELATIONAL Serialize Result
 (1 execution, 0.45 msecs total latency)
    |
    +- RELATIONAL Union All
    |  (1 execution, 0.44 msecs total latency)
    |   |
    |   +- RELATIONAL Union Input
    |   |   |
    |   |   +- RELATIONAL Compute
    |   |   |   |
    |   |   |   +- RELATIONAL Aggregate
    |   |   |   |  (1 execution, 0.06 msecs total latency)
    |   |   |   |  call_type: Global, iterator_type: Stream, scalar_aggregate: true
    |   |   |   |   |
    |   |   |   |   +- RELATIONAL Distributed Union
    |   |   |   |   |  (1 execution, 0.06 msecs total latency)
    |   |   |   |   |  subquery_cluster_node: 6
    |   |   |   |   |   |
    |   |   |   |   |   +- RELATIONAL Aggregate
    |   |   |   |   |   |  (1 execution, 0.04 msecs total latency)
    |   |   |   |   |   |  call_type: Local, iterator_type: Stream, scalar_aggregate: true
    |   |   |   |   |   |   |
    |   |   |   |   |   |   +- RELATIONAL Distributed Union
    |   |   |   |   |   |   |  (1 execution, 0.04 msecs total latency)
    |   |   |   |   |   |   |  call_type: Local, subquery_cluster_node: 8
    |   |   |   |   |   |   |   |
    |   |   |   |   |   |   |   \- RELATIONAL Scan
    |   |   |   |   |   |   |      (1 execution, 0.03 msecs total latency)
    |   |   |   |   |   |   |      Full scan: true, scan_target: regions, scan_type: TableScan
    |   |   |   |   |   |   |
    |   |   |   |   |   |   \- SCALAR Function
    |   |   |   |   |   |      COUNT(1)
    |   |   |   |   |   |       |
    |   |   |   |   |   |       \- SCALAR Constant
    |   |   |   |   |   |          1
    |   |   |   |   |   |
    |   |   |   |   |   \- SCALAR Constant
    |   |   |   |   |      true
    |   |   |   |   |
    |   |   |   |   \- SCALAR Function
    |   |   |   |      COUNT_FINAL($v1)
    |   |   |   |       |
    |   |   |   |       \- SCALAR Reference
    |   |   |   |          $v1
    |   |   |   |
    |   |   |   +- SCALAR Constant
    |   |   |   |  'regions'
    |   |   |   |
    |   |   |   \- SCALAR Constant
    |   |   |      7
    |   |   |
    |   |   +- SCALAR Reference
    |   |   |  $Table
    |   |   |
    |   |   +- SCALAR Reference
    |   |   |  $provided
    |   |   |
    |   |   \- SCALAR Reference
    |   |      $actual
    |   |
    |   +- RELATIONAL Union Input
    |   |   |
    |   |   +- RELATIONAL Compute
    |   |   |   |
    |   |   |   +- RELATIONAL Aggregate
    |   |   |   |  (1 execution, 0.1 msecs total latency)
    |   |   |   |  call_type: Global, iterator_type: Stream, scalar_aggregate: true
    |   |   |   |   |
    |   |   |   |   +- RELATIONAL Distributed Union
    |   |   |   |   |  (1 execution, 0.1 msecs total latency)
    |   |   |   |   |  subquery_cluster_node: 23
    |   |   |   |   |   |
    |   |   |   |   |   +- RELATIONAL Aggregate
    |   |   |   |   |   |  (1 execution, 0.09 msecs total latency)
    |   |   |   |   |   |  call_type: Local, iterator_type: Stream, scalar_aggregate: true
    |   |   |   |   |   |   |
    |   |   |   |   |   |   +- RELATIONAL Distributed Union
    |   |   |   |   |   |   |  (1 execution, 0.09 msecs total latency)
    |   |   |   |   |   |   |  call_type: Local, subquery_cluster_node: 25
    |   |   |   |   |   |   |   |
    |   |   |   |   |   |   |   \- RELATIONAL Scan
    |   |   |   |   |   |   |      (1 execution, 0.08 msecs total latency)
    |   |   |   |   |   |   |      Full scan: true, scan_target: IDX_countries_region_id_1D2B1A686087F93F, scan_type: IndexScan
    |   |   |   |   |   |   |
    |   |   |   |   |   |   \- SCALAR Function
    |   |   |   |   |   |      COUNT(1)
    |   |   |   |   |   |       |
    |   |   |   |   |   |       \- SCALAR Constant
    |   |   |   |   |   |          1
    |   |   |   |   |   |
    |   |   |   |   |   \- SCALAR Constant
    |   |   |   |   |      true
    |   |   |   |   |
    |   |   |   |   \- SCALAR Function
    |   |   |   |      COUNT_FINAL($v2)
    |   |   |   |       |
    |   |   |   |       \- SCALAR Reference
    |   |   |   |          $v2
    |   |   |   |
    |   |   |   +- SCALAR Constant
    |   |   |   |  'countries'
    |   |   |   |
    |   |   |   \- SCALAR Constant
    |   |   |      196
    |   |   |
    |   |   +- SCALAR Reference
    |   |   |  $Table_1
    |   |   |
    |   |   +- SCALAR Reference
    |   |   |  $provided_1
    |   |   |
    |   |   \- SCALAR Reference
    |   |      $actual_1
    |   |
    |   +- RELATIONAL Union Input
    |   |   |
    |   |   +- RELATIONAL Compute
    |   |   |   |
    |   |   |   +- RELATIONAL Aggregate
    |   |   |   |  (1 execution, 0.12 msecs total latency)
    |   |   |   |  call_type: Global, iterator_type: Stream, scalar_aggregate: true
    |   |   |   |   |
    |   |   |   |   +- RELATIONAL Distributed Union
    |   |   |   |   |  (1 execution, 0.11 msecs total latency)
    |   |   |   |   |  subquery_cluster_node: 40
    |   |   |   |   |   |
    |   |   |   |   |   +- RELATIONAL Aggregate
    |   |   |   |   |   |  (1 execution, 0.11 msecs total latency)
    |   |   |   |   |   |  call_type: Local, iterator_type: Stream, scalar_aggregate: true
    |   |   |   |   |   |   |
    |   |   |   |   |   |   +- RELATIONAL Distributed Union
    |   |   |   |   |   |   |  (1 execution, 0.1 msecs total latency)
    |   |   |   |   |   |   |  call_type: Local, subquery_cluster_node: 42
    |   |   |   |   |   |   |   |
    |   |   |   |   |   |   |   \- RELATIONAL Scan
    |   |   |   |   |   |   |      (1 execution, 0.09 msecs total latency)
    |   |   |   |   |   |   |      Full scan: true, scan_target: IDX_cities_country_id_35A7C9365B4BF943, scan_type: IndexScan
    |   |   |   |   |   |   |
    |   |   |   |   |   |   \- SCALAR Function
    |   |   |   |   |   |      COUNT(1)
    |   |   |   |   |   |       |
    |   |   |   |   |   |       \- SCALAR Constant
    |   |   |   |   |   |          1
    |   |   |   |   |   |
    |   |   |   |   |   \- SCALAR Constant
    |   |   |   |   |      true
    |   |   |   |   |
    |   |   |   |   \- SCALAR Function
    |   |   |   |      COUNT_FINAL($v3)
    |   |   |   |       |
    |   |   |   |       \- SCALAR Reference
    |   |   |   |          $v3
    |   |   |   |
    |   |   |   +- SCALAR Constant
    |   |   |   |  'cities'
    |   |   |   |
    |   |   |   \- SCALAR Constant
    |   |   |      204
    |   |   |
    |   |   +- SCALAR Reference
    |   |   |  $Table_2
    |   |   |
    |   |   +- SCALAR Reference
    |   |   |  $provided_2
    |   |   |
    |   |   \- SCALAR Reference
    |   |   |
    |   |   \- SCALAR Reference
    |   |      $actual_2
    |   |
    |   +- RELATIONAL Union Input
    |   |   |
    |   |   +- RELATIONAL Compute
    |   |   |   |
    |   |   |   +- RELATIONAL Aggregate
    |   |   |   |  (1 execution, 0.08 msecs total latency)
    |   |   |   |  call_type: Global, iterator_type: Stream, scalar_aggregate: true
    |   |   |   |   |
    |   |   |   |   +- RELATIONAL Distributed Union
    |   |   |   |   |  (1 execution, 0.08 msecs total latency)
    |   |   |   |   |  subquery_cluster_node: 57
    |   |   |   |   |   |
    |   |   |   |   |   +- RELATIONAL Aggregate
    |   |   |   |   |   |  (1 execution, 0.07 msecs total latency)
    |   |   |   |   |   |  call_type: Local, iterator_type: Stream, scalar_aggregate: true
    |   |   |   |   |   |   |
    |   |   |   |   |   |   +- RELATIONAL Distributed Union
    |   |   |   |   |   |   |  (1 execution, 0.07 msecs total latency)
    |   |   |   |   |   |   |  call_type: Local, subquery_cluster_node: 59
    |   |   |   |   |   |   |   |
    |   |   |   |   |   |   |   \- RELATIONAL Scan
    |   |   |   |   |   |   |      (1 execution, 0.06 msecs total latency)
    |   |   |   |   |   |   |      Full scan: true, scan_target: currencies, scan_type: TableScan
    |   |   |   |   |   |   |
    |   |   |   |   |   |   \- SCALAR Function
    |   |   |   |   |   |      COUNT(1)
    |   |   |   |   |   |       |
    |   |   |   |   |   |       \- SCALAR Constant
    |   |   |   |   |   |          1
    |   |   |   |   |   |
    |   |   |   |   |   \- SCALAR Constant
    |   |   |   |   |      true
    |   |   |   |   |
    |   |   |   |   \- SCALAR Function
    |   |   |   |      COUNT_FINAL($v4)
    |   |   |   |       |
    |   |   |   |       \- SCALAR Reference
    |   |   |   |          $v4
    |   |   |   |          $v4                                                                                                       [49/9050]
    |   |   |   |
    |   |   |   +- SCALAR Constant
    |   |   |   |  'currencies'
    |   |   |   |
    |   |   |   \- SCALAR Constant
    |   |   |      146
    |   |   |
    |   |   +- SCALAR Reference
    |   |   |  $Table_3
    |   |   |
    |   |   +- SCALAR Reference
    |   |   |  $provided_3
    |   |   |
    |   |   \- SCALAR Reference
    |   |      $actual_3
    |   |
    |   +- RELATIONAL Union Input
    |   |   |
    |   |   +- RELATIONAL Compute
    |   |   |   |
    |   |   |   +- RELATIONAL Aggregate
    |   |   |   |  (1 execution, 0.08 msecs total latency)
    |   |   |   |  call_type: Global, iterator_type: Stream, scalar_aggregate: true
    |   |   |   |   |
    |   |   |   |   +- RELATIONAL Distributed Union
    |   |   |   |   |  (1 execution, 0.08 msecs total latency)
    |   |   |   |   |  subquery_cluster_node: 74
    |   |   |   |   |   |
    |   |   |   |   |   +- RELATIONAL Aggregate
    |   |   |   |   |   |  (1 execution, 0.08 msecs total latency)
    |   |   |   |   |   |  call_type: Local, iterator_type: Stream, scalar_aggregate: true
    |   |   |   |   |   |   |
    |   |   |   |   |   |   +- RELATIONAL Distributed Union
    |   |   |   |   |   |   |  (1 execution, 0.07 msecs total latency)
    |   |   |   |   |   |   |  call_type: Local, subquery_cluster_node: 76
    |   |   |   |   |   |   |   |
    |   |   |   |   |   |   |   \- RELATIONAL Scan
    |   |   |   |   |   |   |      (1 execution, 0.07 msecs total latency)
    |   |   |   |   |   |   |      Full scan: true, scan_target: IDX_currencies_countries_country_id_89DC8690B5CAF5C1, scan_type: IndexScan
    |   |   |   |   |   |   |
    |   |   |   |   |   |   \- SCALAR Function
    |   |   |   |   |   |      COUNT(1)
    |   |   |   |   |   |       |
    |   |   |   |   |   |       \- SCALAR Constant
    |   |   |   |   |   |          1
    |   |   |   |   |   |
    |   |   |   |   |   \- SCALAR Constant
    |   |   |   |   |   \- SCALAR Constant                                                                                            [2/9050]
    |   |   |   |   |      true
    |   |   |   |   |
    |   |   |   |   \- SCALAR Function
    |   |   |   |      COUNT_FINAL($v5)
    |   |   |   |       |
    |   |   |   |       \- SCALAR Reference
    |   |   |   |          $v5
    |   |   |   |
    |   |   |   +- SCALAR Constant
    |   |   |   |  'currencies_countries'
    |   |   |   |
    |   |   |   \- SCALAR Constant
    |   |   |      203
    |   |   |
    |   |   +- SCALAR Reference
    |   |   |  $Table_4
    |   |   |
    |   |   +- SCALAR Reference
    |   |   |  $provided_4
    |   |   |
    |   |   \- SCALAR Reference
    |   |      $actual_4
    |   |
    |   +- SCALAR Reference
    |   |  input_0
    |   |
    |   +- SCALAR Reference
    |   |  input_1
    |   |
    |   \- SCALAR Reference
    |      input_2
    |
    +- SCALAR Reference
    |  $Table_5
    |
    +- SCALAR Reference
    |  $provided_5
    |
    \- SCALAR Reference
       $actual_5

Table                 provided  actual

regions               7         7
countries             196       196
cities                204       204
currencies            146       146
currencies_countries  203       203

That’s a long one: scans, joins, concatenation, with some operations run in parallel, projection, aggregation. But basically, it scans each table, or the index when available as we can count the rows from the index which is smaller.

delete instance

This Google Spanner instance costs me $9 per hour (the pricing https://cloud.google.com/spanner/pricing for this eur6 configuration is $3 per node per hour and $50 per 100GB per month). As the provisioning takes 1 minute, and I can copy-paste everything from this blog post, I terminate the instance when I’ve finished my tests.

However, in order to avoid to re-insert those rows, I would be nice to have a backup. Let’s try:


time gcloud spanner backups create sample-data --retention-period=30d --database test --instance=franck
Create request issued for: [sample-data]
Waiting for operation [projects/disco-abacus-161115/instances/franck/backups/sample-data/operations/_auto_op_05f73852e07ae49f] to complete...
⠛
done.
Created backup [sample-data].
real    2m37.593s
user    0m0.908s
sys     0m0.175s

gcloud spanner instances delete franck
Delete instance [franck]. Are you sure?

Do you want to continue (Y/n)?  Y

ERROR: (gcloud.spanner.instances.delete) FAILED_PRECONDITION: Cannot delete instance projects/disco-abacus-161115/instances/franck because it contains backups. Please delete the backups before deleting the instance.

Yes, be careful, a backup is not a database backup here. Just a copy of the tables within the same instance, to be restored to the same region. This is documented of course: Backups reside in the same instance as their source database and cannot be moved. But you know how I find misleading to call that “backup” (a database is living and all the transactions must be backed-up, see What is a database backup). Anyway, this copy cannot help to save money by deleting the instance. The database is gone if I delete the instance and I cannot stop it. The solution is probably to export to the object storage, like a dump, but this will be for a future blog post, maybe.


gcloud spanner backups delete sample-data --instance=franck
You are about to delete backup [sample-data]

Do you want to continue (Y/n)?  Y

Deleted backup [sample-data].

real    0m7.744s
user    0m0.722s
sys     0m0.056s
[opc@a tmp]$ gcloud spanner instances delete franck --quiet   

The –quiet argument bypasses the interactive confirmation.

This test was with small volume, simple schema, from a SQL script that works as-is on the most common SQL databases. Here I had to adapt a few things, but the compatibility with RDBMS has improved a lot in the past year as we can have full referential integrity (foreign key not limited to hierarchical interleave) and logical independence where a secondary index can be used without explicitly mentioning it. Google Spanner is probably the most efficient distributed database when you develop with a NoSQL approach but want to benefit from some SQL features to get a more agile data model and easier integrity and transaction management. However, I don’t think you can port any existing SQL application easily, or even consider it as a general purpose SQL database. For this, you may look at YugaByteDB which uses the PostgreSQL upper layer on distributed database similar to Spanner. However, Spanner has probably the lowest latency for multi-shard operations as it has some cloud-vendor specific optimizations like TrueTime, for software and hardware synchronization.

Cet article Google Spanner – SQL compatibility est apparu en premier sur Blog dbi services.

How to quickly download the new bunch of 21c Oracle Database documentation?

$
0
0

Last month, Oracle released its new 21c version of the database documentation.
At that time, I was looking for a quick mean to get all the books of this so-called 21c Innovation Release.

I could remember I used a script to get them all in one run.
Quick look at google, remembered me I used the one from Christian Antognini which hasn’t been refreshed for a while.

In this blog, I will provide you the refreshed script to download all these recent oracle database docs.
By default, the script will download you 109 files and arrange them under the 9 below folders:
– Install and Upgrade
– Administration
– Development
– Security
– Performance
– Clusterware, RAC and Data Guard
– Data Warehousing, ML and OLAP
– Spatial and Graph
– Distributed Data

This will consume about 310 MB of space.

Both Mac and Windows versions are provided at the bottom of this page.

– On Mac, open a terminal window and run <get-21c-docs-on-mac.sh>
– On Windows, open a command prompt and call <get-21c-docs-on-win.cmd>

Both scripts need a working “wget” tool for retrieving the files from https://docs.oracle.com
“wget” is a small utility, created by the GNU foundation
If you haven’t yet installed this tool:

– on Mac, one way to get it, is to use “brew”, somehow an open source package manager (more details on brew.sh)

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Brew install wget
get-21c-docs-on-mac.sh

– on Windows, you can also get a recent “wget” binary from here
In the windows script, replace “C:\Downloaded Products\wget\wget” by the full path to your folder and call the script:

get-21c-docs-on-win.cmd

I hope this will spare you a bit of your time. Feel free to let me know your comments.

 

get-21c-docs-on-mac.sh


#!/bin/sh
mkdir "Install and Upgrade"
mkdir "Administration"
mkdir "Development"
mkdir "Security"
mkdir "Performance"
mkdir "Clusterware, RAC and Data Guard"
mkdir "Data Warehousing, ML and OLAP"
mkdir "Spatial and Graph"
mkdir "Distributed Data"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/nfcon/pdf/learning-database-new-features.pdf -O "Database New Features Guide.pdf"
cd "Install and Upgrade"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/dblic/database-licensing-information-user-manual.pdf -O "Database Licensing Information User Manual.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/rnrdm/database-release-notes.pdf -O "Database Release Notes.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/upgrd/database-upgrade-guide.pdf -O "Database Upgrade Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/odbcr/odbc-driver-release-notes.pdf -O "ODBC Driver Release Notes.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/sqprn/sqlplus-release-notes.pdf -O "SQL Plus Release Notes.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/comsc/database-sample-schemas.pdf -O "Database Sample Schemas.pdf"
cd ../"Administration"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/tdppt/2-day-performance-tuning-guide.pdf -O "2 Day + Performance Tuning Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/admqs/2-day-dba.pdf -O "2 Day DBA.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/admin/database-administrators-guide.pdf -O "Database Administrator's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/cncpt/database-concepts.pdf -O "Database Concepts.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/errmg/database-error-messages.pdf -O "Database Error Messages.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/refrn/database-reference.pdf -O "Database Reference.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/sutil/database-utilities.pdf -O "Database Utilities.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/multi/multitenant-administrators-guide.pdf -O "Multitenant Administrator's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/rcmrf/database-backup-and-recovery-reference.pdf -O "Database Backup and Recovery Reference.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/bradv/database-backup-and-recovery-users-guide.pdf -O "Database Backup and Recovery User's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/netag/database-net-services-administrators-guide.pdf -O "Net Services Administrator's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/netrf/database-net-services-reference.pdf -O "Net Services Reference.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/ratug/testing-guide.pdf -O "Testing Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/ostmg/automatic-storage-management-administrators-guide.pdf -O "Automatic Storage Management Administrator's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/acfsg/automatic-storage-management-cluster-file-system-administrators-guide.pdf -O "Automatic Storage Management Cluster File System Administrator's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/unxar/administrators-reference-linux-and-unix-system-based-operating-systems.pdf -O "Administrator's Reference for Linux and UNIX-Based Operating Systems.pdf"
cd ../"Development"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/tdpjd/2-day-java-developers-guide.pdf -O "2 Day + Java Developer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/tdddg/2-day-developers-guide.pdf -O "2 Day Developer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/adfns/database-development-guide.pdf -O "Database Development Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/addci/data-cartridge-developers-guide.pdf -O "Data Cartridge Developer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/lnpls/database-pl-sql-language-reference.pdf -O "Database PLSQL Language Reference.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/arpls/database-pl-sql-packages-and-types-reference.pdf -O "Database PLSQL Packages and Types Reference.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/jjdev/java-developers-guide.pdf -O "Java Developer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/jjdbc/jdbc-developers-guide.pdf -O "JDBC Developer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/adjsn/json-developers-guide.pdf -O "JSON Developer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/adobj/object-relational-developers-guide.pdf -O "Object-Relational Developer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/lncpp/oracle-c-call-interface-programmers-guide.pdf -O "Oracle C++ Call Interface Programmer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/lnoci/oracle-call-interface-programmers-guide.pdf -O "Oracle Call Interface Programmer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/lnpcc/c-c-programmers-guide.pdf -O "Pro C C++ Programmer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/lnpcb/cobol-programmers-guide.pdf -O "Pro COBOL Programmer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/adlob/securefiles-and-large-objects-developers-guide.pdf -O "SecureFiles and Large Objects Developer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/sql-language-reference.pdf -O "SQL Language Reference.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/sqprn/sqlplus-release-notes.pdf -O "SQL Plus Release Notes.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/sqpug/sqlplus-users-guide-and-reference.pdf -O "SQL Plus User's Guide and Reference.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/ccapp/text-application-developers-guide.pdf -O "Text Application Developer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/ccref/text-reference.pdf -O "Text Reference.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/jjucp/universal-connection-pool-developers-guide.pdf -O "Universal Connection Pool Developer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/adwsm/workspace-manager-developers-guide.pdf -O "Workspace Manager Developer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/caxml/xml-c-api-reference.pdf -O "XML C API Reference.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/cpxml/xml-c-api-reference.pdf -O "XML C++ API Reference.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/adxdb/xml-db-developers-guide.pdf -O "XML DB Developer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/adxdk/xml-developers-kit-programmers-guide.pdf -O "XML Developer's Kit Programmer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/nlspg/database-globalization-support-guide.pdf -O "Database Globalization Support Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/pccrn/c-c-release-notes.pdf -O "Pro C C++ Release Notes.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/pcbrn/cobol-release-notes.pdf -O "Pro COBOL Release Notes.pdf"
cd ../"Security"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/dbseg/database-security-guide.pdf -O "Database Security Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/dvadm/database-vault-administrators-guide.pdf -O "Database Vault Administrator's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/olsag/label-security-administrators-guide.pdf -O "Label Security Administrator's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/rasad/real-application-security-administration-console-rasadm-users-guide.pdf -O "Real Application Security Administration Console (RASADM) User's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/dbfsg/real-application-security-administrators-and-developers-guide.pdf -O "Real Application Security Administrator's and Developer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/dbimi/enterprise-user-security-administrators-guide.pdf -O "Enterprise User Security Administrator's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/asoag/advanced-security-guide.pdf -O "Advanced Security Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/12.2/dmksb/oracle-data-masking-and-subsetting-users-guide.pdf -O "Data Masking and Subsetting User's Guide.pdf"
cd ../"Performance"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/tdppt/2-day-performance-tuning-guide.pdf -O "Database 2 Day + Performance Tuning Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/tgdba/database-performance-tuning-guide.pdf -O "Database Performance Tuning Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/inmem/database-memory-guide.pdf -O "Database In-Memory Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/tgsql/sql-tuning-guide.pdf -O "SQL Tuning Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/vldbg/vldb-and-partitioning-guide.pdf -O "VLDB and Partitioning Guide.pdf"
cd ../"Clusterware, RAC and Data Guard"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/atnms/autonomous-health-framework-users-guide.pdf -O "Autonomous Health Framework User's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/cwadd/clusterware-administration-and-deployment-guide.pdf -O "Clusterware Administration and Deployment Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/gsmug/global-data-services-concepts-and-administration-guide.pdf -O "Global Data Services Concepts and Administration Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/racad/real-application-clusters-administration-and-deployment-guide.pdf -O "Real Application Clusters Administration and Deployment Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/dgbkr/data-guard-broker.pdf -O "Data Guard Broker.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/sbydb/data-guard-concepts-and-administration.pdf -O "Data Guard Concepts and Administration.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/shard/using-oracle-sharding.pdf -O "Using Oracle Sharding.pdf"
cd ../"Data Warehousing, ML and OLAP"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/dwhsg/database-data-warehousing-guide.pdf -O "Database Data Warehousing Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/machine-learning/oml4r/1.5.1/oread/oracle-machine-learning-r-installation-and-administration-guide.pdf -O "Machine Learning for R Installation and Administration Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/machine-learning/oml4r/1.5.1/omlrl/oracle-machine-learning-r-licensing-information-user-manual.pdf -O "Machine Learning for R Licensing Information User Manual.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/machine-learning/oml4r/1.5.1/orern/oracle-machine-learning-r-release-notes.pdf -O "Machine Learning for R Release Notes.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/machine-learning/oml4r/1.5.1/oreug/oracle-machine-learning-r-users-guide.pdf -O "Machine Learning for R User's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/machine-learning/oml4sql/21/dmapi/oracle-machine-learning-sql-api-guide.pdf -O "Machine Learning for SQL API Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/machine-learning/oml4sql/21/dmcon/oracle-machine-learning-sql-concepts.pdf -O "Machine Learning for SQL Concepts.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/machine-learning/oml4sql/21/dmprg/oracle-machine-learning-sql-users-guide.pdf -O "Machine Learning for SQL User's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/oladm/olap-dml-reference.pdf -O "OLAP DML Reference.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/olaap/olap-java-api-developers-guide.pdf -O "OLAP Expression Syntax Reference.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/olaap/olap-java-api-developers-guide.pdf -O "OLAP Java API Developer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/olaug/olap-users-guide.pdf -O "OLAP User's Guide.pdf"
cd ../"Spatial and Graph"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/spatl/spatial-developers-guide.pdf -O "Spatial Developer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/geors/spatial-georaster-developers-guide.pdf -O "Spatial GeoRaster Developer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/jimpv/spatial-map-visualization-developers-guide.pdf -O "Spatial Map Visualization Developer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/topol/spatial-topology-and-network-data-model-developers-guide.pdf -O "Spatial Topology and Network Data Model Developer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/property-graph/20.4/spgdg/oracle-graph-property-graph-developers-guide.pdf -O "Oracle Graph Property Graph Developer's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/rdfrm/graph-developers-guide-rdf-graph.pdf -O "Graph Developer's Guide for RDF Graph.pdf"
cd ../"Distributed Data"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/adque/database-transactional-event-queues-and-advanced-queuing-users-guide.pdf -O "Database Transactional Event Queues and Advanced Queuing User's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/odbcr/odbc-driver-release-notes.pdf -O "ODBC Driver Release Notes.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/drdas/provider-drda-users-guide.pdf -O "Provider for DRDA User's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/drdaa/sql-translation-and-migration-guide.pdf -O "SQL Translation and Migration Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/appci/database-gateway-appc-installation-and-configuration-guide-aix-5l-based-systems-64-bit-hp-ux-itanium-solaris-operating-system-sparc-64-bit-linux-x86-and-linux-x86-64.pdf -O "Database Gateway for APPC Installation and Configuration Guide for AIX 5L Based Systems (64-Bit), HP-UX Itanium, Solaris Operating System (SPARC 64-Bit), Linux x86, and Linux x86-64.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/appcw/database-gateway-appc-installation-and-configuration-guide-microsoft-windows.pdf -O "Database Gateway for APPC Installation and Configuration Guide for Microsoft Windows.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/appug/database-gateway-appc-users-guide.pdf -O "Database Gateway for APPC User's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/drdag/database-gateway-drda-users-guide.pdf -O "Database Gateway for DRDA User's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/tginu/database-gateway-informix-users-guide.pdf -O "Database Gateway for Informix User's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/odbcu/database-gateway-odbc-users-guide.pdf -O "Database Gateway for ODBC User's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/gmswn/database-gateway-sql-server-users-guide.pdf -O "Database Gateway for SQL Server User's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/tgsyu/database-gateway-sybase-users-guide.pdf -O "Database Gateway for Sybase User's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/tgteu/database-gateway-teradata-users-guide.pdf -O "Database Gateway for Teradata User's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/wsmqg/database-gateway-websphere-mq-installation-and-users-guide.pdf -O "Database Gateway for WebSphere MQ Installation and User's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/otgis/database-gateway-installation-and-configuration-guide-aix-5l-based-systems-64-bit-hp-ux-itanium-solaris-operating-system-sparc-64-bit-linux-x86-and-linux-x86-64.pdf -O "Database Gateway Installation and Configuration Guide for AIX 5L Based Systems (64-Bit), HP-UX Itanium, Solaris Operating System (SPARC 64-Bit), Linux x86, and Linux x86-64.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/otgiw/database-gateway-installation-and-configuration-guide-microsoft-windows.pdf -O "Database Gateway Installation and Configuration Guide for Microsoft Windows.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/heter/heterogeneous-connectivity-users-guide.pdf -O "Heterogeneous Connectivity User's Guide.pdf"
wget -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/xstrm/xstream-guide.pdf -O "XStream Guide.pdf"
cd ..

get-21c-docs-on-win.cmd

@echo off
mkdir "Install and Upgrade"
mkdir "Administration"
mkdir "Development"
mkdir "Security"
mkdir "Performance"
mkdir "Clusterware, RAC and Data Guard"
mkdir "Data Warehousing, ML and OLAP"
mkdir "Spatial and Graph"
mkdir "Distributed Data"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/nfcon/pdf/learning-database-new-features.pdf -O "Database New Features Guide.pdf"
cd "Install and Upgrade"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/dblic/database-licensing-information-user-manual.pdf -O "Database Licensing Information User Manual.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/rnrdm/database-release-notes.pdf -O "Database Release Notes.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/upgrd/database-upgrade-guide.pdf -O "Database Upgrade Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/odbcr/odbc-driver-release-notes.pdf -O "ODBC Driver Release Notes.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/sqprn/sqlplus-release-notes.pdf -O "SQL Plus Release Notes.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/comsc/database-sample-schemas.pdf -O "Database Sample Schemas.pdf"
cd ../"Administration"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/tdppt/2-day-performance-tuning-guide.pdf -O "2 Day + Performance Tuning Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/admqs/2-day-dba.pdf -O "2 Day DBA.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/admin/database-administrators-guide.pdf -O "Database Administrator's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/cncpt/database-concepts.pdf -O "Database Concepts.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/errmg/database-error-messages.pdf -O "Database Error Messages.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/refrn/database-reference.pdf -O "Database Reference.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/sutil/database-utilities.pdf -O "Database Utilities.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/multi/multitenant-administrators-guide.pdf -O "Multitenant Administrator's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/rcmrf/database-backup-and-recovery-reference.pdf -O "Database Backup and Recovery Reference.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/bradv/database-backup-and-recovery-users-guide.pdf -O "Database Backup and Recovery User's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/netag/database-net-services-administrators-guide.pdf -O "Net Services Administrator's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/netrf/database-net-services-reference.pdf -O "Net Services Reference.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/ratug/testing-guide.pdf -O "Testing Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/ostmg/automatic-storage-management-administrators-guide.pdf -O "Automatic Storage Management Administrator's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/acfsg/automatic-storage-management-cluster-file-system-administrators-guide.pdf -O "Automatic Storage Management Cluster File System Administrator's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/unxar/administrators-reference-linux-and-unix-system-based-operating-systems.pdf -O "Administrator's Reference for Linux and UNIX-Based Operating Systems.pdf"
cd ../"Development"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/tdpjd/2-day-java-developers-guide.pdf -O "2 Day + Java Developer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/tdddg/2-day-developers-guide.pdf -O "2 Day Developer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/adfns/database-development-guide.pdf -O "Database Development Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/addci/data-cartridge-developers-guide.pdf -O "Data Cartridge Developer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/lnpls/database-pl-sql-language-reference.pdf -O "Database PLSQL Language Reference.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/arpls/database-pl-sql-packages-and-types-reference.pdf -O "Database PLSQL Packages and Types Reference.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/jjdev/java-developers-guide.pdf -O "Java Developer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/jjdbc/jdbc-developers-guide.pdf -O "JDBC Developer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/adjsn/json-developers-guide.pdf -O "JSON Developer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/adobj/object-relational-developers-guide.pdf -O "Object-Relational Developer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/lncpp/oracle-c-call-interface-programmers-guide.pdf -O "Oracle C++ Call Interface Programmer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/lnoci/oracle-call-interface-programmers-guide.pdf -O "Oracle Call Interface Programmer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/lnpcc/c-c-programmers-guide.pdf -O "Pro C C++ Programmer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/lnpcb/cobol-programmers-guide.pdf -O "Pro COBOL Programmer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/adlob/securefiles-and-large-objects-developers-guide.pdf -O "SecureFiles and Large Objects Developer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/sql-language-reference.pdf -O "SQL Language Reference.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/sqprn/sqlplus-release-notes.pdf -O "SQL Plus Release Notes.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/sqpug/sqlplus-users-guide-and-reference.pdf -O "SQL Plus User's Guide and Reference.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/ccapp/text-application-developers-guide.pdf -O "Text Application Developer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/ccref/text-reference.pdf -O "Text Reference.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/jjucp/universal-connection-pool-developers-guide.pdf -O "Universal Connection Pool Developer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/adwsm/workspace-manager-developers-guide.pdf -O "Workspace Manager Developer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/caxml/xml-c-api-reference.pdf -O "XML C API Reference.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/cpxml/xml-c-api-reference.pdf -O "XML C++ API Reference.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/adxdb/xml-db-developers-guide.pdf -O "XML DB Developer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/adxdk/xml-developers-kit-programmers-guide.pdf -O "XML Developer's Kit Programmer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/nlspg/database-globalization-support-guide.pdf -O "Database Globalization Support Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/pccrn/c-c-release-notes.pdf -O "Pro C C++ Release Notes.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/pcbrn/cobol-release-notes.pdf -O "Pro COBOL Release Notes.pdf"
cd ../"Security"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/dbseg/database-security-guide.pdf -O "Database Security Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/dvadm/database-vault-administrators-guide.pdf -O "Database Vault Administrator's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/olsag/label-security-administrators-guide.pdf -O "Label Security Administrator's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/rasad/real-application-security-administration-console-rasadm-users-guide.pdf -O "Real Application Security Administration Console (RASADM) User's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/dbfsg/real-application-security-administrators-and-developers-guide.pdf -O "Real Application Security Administrator's and Developer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/dbimi/enterprise-user-security-administrators-guide.pdf -O "Enterprise User Security Administrator's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/asoag/advanced-security-guide.pdf -O "Advanced Security Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/12.2/dmksb/oracle-data-masking-and-subsetting-users-guide.pdf -O "Data Masking and Subsetting User's Guide.pdf"
cd ../"Performance"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/tdppt/2-day-performance-tuning-guide.pdf -O "Database 2 Day + Performance Tuning Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/tgdba/database-performance-tuning-guide.pdf -O "Database Performance Tuning Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/inmem/database-memory-guide.pdf -O "Database In-Memory Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/tgsql/sql-tuning-guide.pdf -O "SQL Tuning Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/vldbg/vldb-and-partitioning-guide.pdf -O "VLDB and Partitioning Guide.pdf"
cd ../"Clusterware, RAC and Data Guard"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/atnms/autonomous-health-framework-users-guide.pdf -O "Autonomous Health Framework User's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/cwadd/clusterware-administration-and-deployment-guide.pdf -O "Clusterware Administration and Deployment Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/gsmug/global-data-services-concepts-and-administration-guide.pdf -O "Global Data Services Concepts and Administration Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/racad/real-application-clusters-administration-and-deployment-guide.pdf -O "Real Application Clusters Administration and Deployment Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/dgbkr/data-guard-broker.pdf -O "Data Guard Broker.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/sbydb/data-guard-concepts-and-administration.pdf -O "Data Guard Concepts and Administration.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/shard/using-oracle-sharding.pdf -O "Using Oracle Sharding.pdf"
cd ../"Data Warehousing, ML and OLAP"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/dwhsg/database-data-warehousing-guide.pdf -O "Database Data Warehousing Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/machine-learning/oml4r/1.5.1/oread/oracle-machine-learning-r-installation-and-administration-guide.pdf -O "Machine Learning for R Installation and Administration Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/machine-learning/oml4r/1.5.1/omlrl/oracle-machine-learning-r-licensing-information-user-manual.pdf -O "Machine Learning for R Licensing Information User Manual.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/machine-learning/oml4r/1.5.1/orern/oracle-machine-learning-r-release-notes.pdf -O "Machine Learning for R Release Notes.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/machine-learning/oml4r/1.5.1/oreug/oracle-machine-learning-r-users-guide.pdf -O "Machine Learning for R User's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/machine-learning/oml4sql/21/dmapi/oracle-machine-learning-sql-api-guide.pdf -O "Machine Learning for SQL API Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/machine-learning/oml4sql/21/dmcon/oracle-machine-learning-sql-concepts.pdf -O "Machine Learning for SQL Concepts.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/machine-learning/oml4sql/21/dmprg/oracle-machine-learning-sql-users-guide.pdf -O "Machine Learning for SQL User's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/oladm/olap-dml-reference.pdf -O "OLAP DML Reference.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/olaap/olap-java-api-developers-guide.pdf -O "OLAP Expression Syntax Reference.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/olaap/olap-java-api-developers-guide.pdf -O "OLAP Java API Developer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/olaug/olap-users-guide.pdf -O "OLAP User's Guide.pdf"
cd ../"Spatial and Graph"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/spatl/spatial-developers-guide.pdf -O "Spatial Developer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/geors/spatial-georaster-developers-guide.pdf -O "Spatial GeoRaster Developer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/jimpv/spatial-map-visualization-developers-guide.pdf -O "Spatial Map Visualization Developer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/topol/spatial-topology-and-network-data-model-developers-guide.pdf -O "Spatial Topology and Network Data Model Developer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/property-graph/20.4/spgdg/oracle-graph-property-graph-developers-guide.pdf -O "Oracle Graph Property Graph Developer's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/rdfrm/graph-developers-guide-rdf-graph.pdf -O "Graph Developer's Guide for RDF Graph.pdf"
cd ../"Distributed Data"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/adque/database-transactional-event-queues-and-advanced-queuing-users-guide.pdf -O "Database Transactional Event Queues and Advanced Queuing User's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/odbcr/odbc-driver-release-notes.pdf -O "ODBC Driver Release Notes.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/drdas/provider-drda-users-guide.pdf -O "Provider for DRDA User's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/drdaa/sql-translation-and-migration-guide.pdf -O "SQL Translation and Migration Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/appci/database-gateway-appc-installation-and-configuration-guide-aix-5l-based-systems-64-bit-hp-ux-itanium-solaris-operating-system-sparc-64-bit-linux-x86-and-linux-x86-64.pdf -O "Database Gateway for APPC Installation and Configuration Guide for AIX 5L Based Systems (64-Bit), HP-UX Itanium, Solaris Operating System (SPARC 64-Bit), Linux x86, and Linux x86-64.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/appcw/database-gateway-appc-installation-and-configuration-guide-microsoft-windows.pdf -O "Database Gateway for APPC Installation and Configuration Guide for Microsoft Windows.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/appug/database-gateway-appc-users-guide.pdf -O "Database Gateway for APPC User's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/drdag/database-gateway-drda-users-guide.pdf -O "Database Gateway for DRDA User's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/tginu/database-gateway-informix-users-guide.pdf -O "Database Gateway for Informix User's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/odbcu/database-gateway-odbc-users-guide.pdf -O "Database Gateway for ODBC User's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/gmswn/database-gateway-sql-server-users-guide.pdf -O "Database Gateway for SQL Server User's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/tgsyu/database-gateway-sybase-users-guide.pdf -O "Database Gateway for Sybase User's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/tgteu/database-gateway-teradata-users-guide.pdf -O "Database Gateway for Teradata User's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/wsmqg/database-gateway-websphere-mq-installation-and-users-guide.pdf -O "Database Gateway for WebSphere MQ Installation and User's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/otgis/database-gateway-installation-and-configuration-guide-aix-5l-based-systems-64-bit-hp-ux-itanium-solaris-operating-system-sparc-64-bit-linux-x86-and-linux-x86-64.pdf -O "Database Gateway Installation and Configuration Guide for AIX 5L Based Systems (64-Bit), HP-UX Itanium, Solaris Operating System (SPARC 64-Bit), Linux x86, and Linux x86-64.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/otgiw/database-gateway-installation-and-configuration-guide-microsoft-windows.pdf -O "Database Gateway Installation and Configuration Guide for Microsoft Windows.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/heter/heterogeneous-connectivity-users-guide.pdf -O "Heterogeneous Connectivity User's Guide.pdf"
"C:\Downloaded Products\wget\wget" --no-check-certificate -nv https://docs.oracle.com/en/database/oracle/oracle-database/21/xstrm/xstream-guide.pdf -O "XStream Guide.pdf"
cd ..

 

Cet article How to quickly download the new bunch of 21c Oracle Database documentation? est apparu en premier sur Blog dbi services.


JENKINS Add plugins in Jenkins

$
0
0

Hi everybody,

First of all,I wish you a happy new year with lot of success for all of you 😀
Now ,to have a good start for 2021 we will learn how to use one of the most important characteristics of Jenkins!You already know how flexible is Jenkins to fit your need,by using the appropriate plugins.
Today we will see how to add plugins to your Jenkins.

There are many ways to add a plugin to your Jenkins master,the most common is to download them and add them from the plugin management tab ,there is also another one , by uploading the plugin by yourself,let’s check it:

Add plugin from plugin manager

  • Select Dashboard then Plugin manager on the user interface

Here you got many plugin related tabs

  • plugin updates
  • plugin available
  • plugin installed
  • advanced

 

  • We will select for example the slack notification plugin,type it in search bar then every plugin containing slack will appear

  • Select slack notification plugin click on the name and it will display a description of the plugin

  • Now we just have to check the box and start installation

  • When it’s done we can verify that plugin is installed by checking the installed tab(check if the box enabled is checked,it also allow you to uninstall it or just disable it by unchecking the box”Enabled”)

  • As plugin is now installed you can add slack notification to your post built execution,select one of your builds and update it’s configuration ,then select add post build action you will see slack notification in the drop down menu

Upload plugin manually

  • Download your plugin Manually,here we will use JDK_Parameter_Plugin

  • Go to advanced tab and select upload plugin then browse until your hpi file ( the plugin format )below we have downloaded a plugin JDK and we will upload it manually

  • When it is done , a message will show you success

  • Go now on installed tab to check if all is ok

  • Note :In the advanced tab you can add a repo for the plugins download, by default it is the Jenkins site repo

Conclusion

That’s it, you know now how to install your plugin on Jenkins by using the plugin manager and also manually!
Feel free to comment and share,don’t forget to check dbi bloggers for many others topics,you can also give a look on https://www.jenkins.io/ to have load of interesting info on Jenkins 😉

 

Cet article JENKINS Add plugins in Jenkins est apparu en premier sur Blog dbi services.

Dealing with German “Umlaute” in PostgreSQL’s full text search

$
0
0

PostgreSQL comes with build-in Full Text Search and you can do quite amazing stuff with it. A question that popped up during one of the last PostgreSQL for developers workshop was: How can I deal with German “Umlaute” such as “ä”, “ö” and “ü” in such a way, that I can search for e.g. “Schnösel” and the result will give me “Schnösel” as well as “Schnoesel”? One way to deal with that would be to replace all occurrences of “ä”, “ö” and “ü” with “ae”, “oe” and “ue” before doing the search, and then automatically convert the search string to “ae”, “oe” and “ue”, once a search with an “Umlaut” comes in. Even if this can be done quite easily (but probably introduces other issues), there is a more elegant solution to that with PostreSQL’s build-in Full Text Search.

Lets start with a simple table containing two strings:

postgres=# create table umlaute ( string text );
CREATE TABLE
postgres=# insert into umlaute values ('Schnösel');
INSERT 0 1
postgres=# insert into umlaute values ('Schnoesel');
INSERT 0 1
postgres=# select * from umlaute;
  string   
-----------
 Schnösel
 Schnoesel
(2 rows)

If you ask for the two variations of the string in the default configuration, you only get the exact match:

postgres=# select * from umlaute where to_tsvector(string) @@ to_tsquery('Schnösel');
  string  
----------
 Schnösel
(1 row)

postgres=# select * from umlaute where to_tsvector(string) @@ to_tsquery('Schnoesel');
  string   
-----------
 Schnoesel
(1 row)

By default PostgreSQL uses the English text search configuration:

postgres=# show default_text_search_config;
 default_text_search_config 
----------------------------
 pg_catalog.english
(1 row)

… and you might think that switching this to “German” already resolves the issue:

postgres=# set default_text_search_config='german';
SET
postgres=# select current_setting('default_text_search_config');
  current_setting  
-------------------
 pg_catalog.german
(1 row)

postgres=# select * from umlaute where to_tsvector(string) @@ to_tsquery('Schnösel');
  string  
----------
 Schnösel
(1 row)

postgres=# select * from umlaute where to_tsvector(string) @@ to_tsquery('Schnoesel');
  string   
-----------
 Schnoesel
(1 row)

This is definitely not the case, so more work needs to be done to get this working. PostgreSQL allows you to create your own text search configurations easily, and this is what happens in the next step:

postgres=# create text search configuration my_de ( copy = german );
CREATE TEXT SEARCH CONFIGURATION

We now have a copy of the default German text search configuration available as “my_de”. To test, that this actually works you can explicitly specify this configuration in our text search (although, it of course does not change anything right now):

postgres=# select * from umlaute where to_tsvector('my_de',string) @@ to_tsquery('my_de','Schnösel');
  string  
----------
 Schnösel
(1 row)

postgres=# select * from umlaute where to_tsvector('my_de',string) @@ to_tsquery('my_de','Schnoesel');
  string   
-----------
 Schnoesel
(1 row)

Before we can alter our new text search configuration any further we need the extension unaccent. This extension “removes accents (diacritic signs) from lexemes”. Here is a little example of what it does:

postgres=# create extension unaccent;
CREATE EXTENSION
postgres=# select unaccent ('aaÄ');
 unaccent 
----------
 aaA
(1 row)

postgres=# select unaccent ('ÆÆÆ');
 unaccent 
----------
 AEAEAE
(1 row)

Having that in place we can modify the text search configuration we just created:

postgres=# ALTER TEXT SEARCH CONFIGURATION my_de ALTER MAPPING FOR hword, hword_part, word WITH unaccent, german_stem;
ALTER TEXT SEARCH CONFIGURATION

If you wonder, what “hword”, “word” and “hword_part” actually mean, then have a look here. We basically instructed our text search configuration to “unaccent” our search terms by default. Is that enough?

postgres=# select * from umlaute where to_tsvector('my_de',string) @@ to_tsquery('my_de','Schnösel');
  string  
----------
 Schnösel
(1 row)

postgres=# select * from umlaute where to_tsvector('my_de',string) @@ to_tsquery('my_de','Schnoesel');
  string   
-----------
 Schnoesel
(1 row)

No, we still get the same results, but why? The “unaccent” extension uses a rules file to do the translation of the specific characters. By default the “unaccent.rules” file is used and you can find that in the SHAREDIR of your PostgreSQL installation:

postgres=# \! pg_config | grep SHAREDIR
SHAREDIR = /u01/app/postgres/product/DEV/db_1/share
postgres=# \! ls -la /u01/app/postgres/product/DEV/db_1/share/tsearch_data/*.rules
-rw-r--r-- 1 postgres postgres 9549 Dec 26 15:45 /u01/app/postgres/product/DEV/db_1/share/tsearch_data/unaccent.rules
-rw-r--r-- 1 postgres postgres  139 Dec 26 15:44 /u01/app/postgres/product/DEV/db_1/share/tsearch_data/xsyn_sample.rules

This file contains the rules for translating special characters to their desired target:

postgres=# \! head /u01/app/postgres/product/DEV/db_1/share/tsearch_data/unaccent.rules
©       (C)
«       <>
¼        1/4
½        1/2
¾        3/4
À       A
Á       A

If you look for the German “Umlaute” you’ll get this:

postgres=# \! egrep "ä|ö|ü" /u01/app/postgres/product/DEV/db_1/share/tsearch_data/unaccent.rules
ä       a
ö       o
ü       u

This is the reason why we still do not get the desired result. I’ll ge ahead and modify the rules directly but you should rather create your own rules file before modifying an existing one:

postgres=# \! egrep "ä|ö|ü" /u01/app/postgres/product/DEV/db_1/share/tsearch_data/unaccent.rules
ä       ae
ö       oe
ü       ue

From now on “ä” will be translated to “ae”, “ü” to “ue” and “ö” to “oe” and our search works as expected:

postgres=# select * from umlaute where to_tsvector('my_de',string) @@ to_tsquery('my_de','Schnoesel');
  string   
-----------
 Schnösel
 Schnoesel
(2 rows)

postgres=# select * from umlaute where to_tsvector('my_de',string) @@ to_tsquery('my_de','Schnösel');
  string   
-----------
 Schnösel
 Schnoesel
(2 rows)

Great stuff.

Cet article Dealing with German “Umlaute” in PostgreSQL’s full text search est apparu en premier sur Blog dbi services.

JENKINS Add new users on Jenkins

$
0
0

Hello everybody , today we will see how to manage user creation on Jenkins

As many other scheduling software, Jenkins has tool to manage credentials and also grant access to users.
Best practice to manage user right level is to associate them to a work group and/or a project they are assigned for.

User creation

Our user will need access to Jenkins  and especially to the applications he will work on,also he may have only restricted right for this and should not be allowed to work on other projects.Let’s create a new user with specific rights

  • Select Manage User in Security part

  • Complete your user profile with name password and mail address

  • Once done, the user creation will be validated and Jenkins interface will display you the fresh user created in the current user list

Connect with the new user

  • When you try to connect with you user you will probably get this message

It is due to a lack of access right(at least a read access), we will have to configure that in Global Security tool

Configure global security

Indeed , on Jenkins many security realm can be used to enhance security and authentication,but be aware that only one security realm can be active at a time.
Let’s configure access to the users created
By default, users and groups come from the Jenkins internal user database.Authentication can be coupled also with tools like Active Directory,Ldap

  • Go to manage Jenkins then select configure global security :

  • In authentication part select the security realm and update the matrix based security in the authorization’s part
    Select add user and groups and then enter your username

  • Add your freshly created user and grant him the appropriate rights (note that we used Jenkins own user database realm)

Connect again with new user

  • Connect again with your created user and check if rights are corresponding to what is expected

Conclusion

Here is an easy way to add users on your Jenkins and scale their rights,you have many other credentials options available and also it can be completed with security plugins.Again be sure to assign specific access rights for your users to allow them to work on their dedicated workspace and avoid access to the other projects.
Feel free to comment and also don’t forget to check news from dbi bloggers

Cet article JENKINS Add new users on Jenkins est apparu en premier sur Blog dbi services.

Handling unified auditing spillover files on the standby-site

$
0
0

Switching to Oracle Unified Auditing may produce lots of data when e.g. auditing activities of the SYS-user. I.e. according the documentation you can do the following to audit similarly as in traditional auditing with audit_sys_operations=TRUE:

SQL> CREATE AUDIT POLICY TOPLEVEL_ACTIONS ACTIONS ALL ONLY TOPLEVEL;
SQL> AUDIT POLICY TOPLEVEL_ACTIONS BY SYS;

REMARK1: You may check the Blog on traditional SYS-auditing here
REMARK2: Tests done in this Blog were done with Oracle 19.9. Auditing toplevel operations were not possible before 19c.

With unified auditing all data is written in the database – except when there’s no possibility to write to the database. If the database is e.g. not open then spillover files are written to the OS. By default files with extension bin are written to $ORACLE_BASE/audit/$ORACLE_SID.

REMARK: Container databases have a sub-directory per pluggable DB there, but all examples in this Blog are for a non-container-DB.

E.g.

oracle@boda1:/u01/app/oracle/audit/TISMED/ [TISMED] ls -ltr | tail -10
-rw-------. 1 oracle oinstall    6656 Jan  8 15:30 ora_audit_031.bin
-rw-------. 1 oracle oinstall   98304 Jan  8 15:35 ora_audit_0251.bin
-rw-------. 1 oracle oinstall    1536 Jan  8 15:57 ora_audit_0253.bin
-rw-------. 1 oracle oinstall 3140096 Jan  8 15:57 ora_audit_019.bin
-rw-------. 1 oracle oinstall    2048 Jan  8 15:57 ora_audit_0256.bin
-rw-------. 1 oracle oinstall    3584 Jan  8 15:58 ora_audit_0252.bin
-rw-------. 1 oracle oinstall    4096 Jan  8 15:58 ora_audit_0238.bin
-rw-------. 1 oracle oinstall    2048 Jan  8 15:58 ora_audit_024.bin
-rw-------. 1 oracle oinstall  314368 Jan  8 15:58 ora_audit_030.bin
-rw-------. 1 oracle oinstall    2048 Jan  8 15:58 ora_audit_029.bin
oracle@boda1:/u01/app/oracle/audit/TISMED/ [TISMED] 

You get an idea what’s in the files when doing a strings on them:


oracle@boda1:/u01/app/oracle/audit/TISMED/ [TISMED] strings ora_audit_030.bin | head -20
ANG Spillover Audit File
ORAAUDNG
oracle
boda2.localdomain
pts/0
(TYPE=(DATABASE));(CLIENT ADDRESS=((ADDRESS=(PROTOCOL=tcp)(HOST=192.168.56.6)(PORT=18890))));	
PUBLIC
oracle@boda2.localdomain (TNS V1-V3)
`)',
26801
SYSOPR
PUBLIC:
ORAAUDNG
oracle
boda2.localdomain
pts/0
(TYPE=(DATABASE));(CLIENT ADDRESS=((ADDRESS=(PROTOCOL=tcp)(HOST=192.168.56.6)(PORT=18892))));	
PUBLIC
oracle@boda2.localdomain (TNS V1-V3)
26813
oracle@boda1:/u01/app/oracle/audit/TISMED/ [TISMED] 

The data in the spillover-files are visible in the database when querying e.g. unified_audit_trail as the following simple example shows:

SQL> select count(*) from unified_audit_trail;

  COUNT(*)
----------
    237306

SQL> !mv /u01/app/oracle/audit/TISMED /u01/app/oracle/audit/TISMED_tmp

SQL> select count(*) from unified_audit_trail;

  COUNT(*)
----------
    233816

SQL> !mv /u01/app/oracle/audit/TISMED_tmp /u01/app/oracle/audit/TISMED

SQL> select count(*) from unified_audit_trail;

  COUNT(*)
----------
    237328

SQL> 

I.e. moving the spillover directory to a new name results in showing less data in UNIFIED_AUDIT_TRAIL. The view UNIFIED_AUDIT_TRAIL is a UNION ALL of the view v$unified_audit_trail and the table audsys.aud$unified (you may check $ORACLE_HOME/rdbms/admin/catuat.sql on what the metadata of UNIFIED_AUDIT_TRAIL is). The data of the spillover-files comes from the view v$unified_audit_trail:

SQL> select count(*) from v$unified_audit_trail;

  COUNT(*)
----------
      3501

SQL> select count(*) from audsys.aud$unified;

  COUNT(*)
----------
    233845

SQL> !mv /u01/app/oracle/audit/TISMED /u01/app/oracle/audit/TISMED_tmp

SQL> select count(*) from v$unified_audit_trail;

  COUNT(*)
----------
	 0

SQL> select count(*) from audsys.aud$unified;

  COUNT(*)
----------
    233849

SQL> !mv /u01/app/oracle/audit/TISMED_tmp /u01/app/oracle/audit/TISMED

SQL> select count(*) from v$unified_audit_trail;

  COUNT(*)
----------
      3501

SQL> 

Oracle provided the following possibility to load the data of the spillover-files into the database:

SQL> exec DBMS_AUDIT_MGMT.LOAD_UNIFIED_AUDIT_FILES;

The issue I want to talk about in this blog are spillover files on standby databases. Standby databases are usually running in MOUNT-mode and hence are not writable. I.e. all audit-data produced on the standby-DBs go to spillover-files. If you haven’t switched over to the standby database for a while and loaded the spillover-files to the database then there could be quite a lot of data in the spillover-directory. I saw systems with e.g. 10GB of data in the spillover-directory, especially when doing rman-backups on the standby-site.

That causes 3 issues:

1.) If you move your audit data on your primary database to history tables then those history-tables may not contain the full truth, because audit-records of spillover-files on the standby-DBs are not visible in the history tables.
2.) After a switchover a query on unified_audit_trail may be very slow, because reading spillover-files is slower than reading from the database.
3.) Loading the spillover files after a switchover to the new primary database may take a long time and causes the SYSAUX-tablespace to grow significantly.

So the question is: How to handle the spillover-files on the standby-DB?

One possibility is to just remove them regularly on the OS if your security rules can afford to lose auditing data of standby-DBs. If that’s not possible then you may move the spillover-files regularly to a backup-location, so that you can restore those if necessary. The third alternative I’ve tested was to copy the spillover-files from the standy DB to the primary DB and load them there. That worked for me, but 2 things have to be considered:

1. spillover-files are not unique over all DBs in data guard. I.e. don’t just copy files over to the primary. You have to move away the primnary spillover directory first and restore it when the data has been loaded.
2. The procedure is not documented and has to be confirmed by Oracle Support

E.g. here the process to copy and load spillover-files from standby to primary and load them:

1. Move away the primary spillover-folder

SQL> select count(*) from v$unified_audit_trail uview;

  COUNT(*)
----------
       239

SQL> !mv /u01/app/oracle/audit/${ORACLE_SID} /u01/app/oracle/audit/${ORACLE_SID}_tmp

SQL> select count(*) from v$unified_audit_trail uview;

  COUNT(*)
----------
	 0

2. Copy spillover-files from standby to primary

SQL> !scp -p -r standby:/u01/app/oracle/audit/${ORACLE_SID} /u01/app/oracle/audit
SQL> select count(*) from v$unified_audit_trail uview;

  COUNT(*)
----------
       101

SQL> exec DBMS_AUDIT_MGMT.LOAD_UNIFIED_AUDIT_FILES;

PL/SQL procedure successfully completed.

SQL> select count(*) from v$unified_audit_trail uview;

  COUNT(*)
----------
	24

–> Not all files were loaded. That’s normal.

3. Backup the current spillover-directory and restore the original spillover-directory

SQL> !mv /u01/app/oracle/audit/${ORACLE_SID} /u01/app/oracle/audit/${ORACLE_SID}_standby

SQL> !mv /u01/app/oracle/audit/${ORACLE_SID}_tmp /u01/app/oracle/audit/${ORACLE_SID}

SQL> select count(*) from v$unified_audit_trail uview;

  COUNT(*)
----------
       239

4. Backup the spillover-folder on the standby-site. I.e. do the following command on the standby-host

SQL> !mv /u01/app/oracle/audit/${ORACLE_SID} /u01/app/oracle/audit/${ORACLE_SID}_backup

5. Copy the remaining standby-files not loaded back to the standby host. I.e. do the following command on the standby-host

SQL> !scp -p -r primary:/u01/app/oracle/audit/${ORACLE_SID}_standby /u01/app/oracle/audit/${ORACLE_SID}
SQL> select count(*) from v$unified_audit_trail uview;

  COUNT(*)
----------
	24

REMARK: Above procedure is not 100% correct, because it doesn’t consider spillover-files produced while doing above steps.

Summary: Many people with standby-DBs and Unified Auditing active may not have realized that there are potential issues with spillover-files at the standby-site. Those spillover files on standby have to be considered. The easiest method is to just remove them regularly on the OS if that has been approved by the security team.

Cet article Handling unified auditing spillover files on the standby-site est apparu en premier sur Blog dbi services.

Pressure Stall Information on Autonomous Linux

$
0
0

By Franck Pachot

.
The Linux metrics that are usually considered for performance monitoring and troubleshooting have many drawbacks and frequent misinterpretations. The recent kernels provide PSI (Pressure Stall Information) which is not enabled by default in Oracle Linux “unbreakable” kernel. However, the kernel is compiled with it. Here is how to enabled it in a newly create Autonomous Linux instance (the Oracle Cloud Linux ,based on OEL 7.9, with no-downtime patching)

Check PSI availability


[opc@sillymetrics ~]$ tail /proc/pressure/*

==> /proc/pressure/cpu  /proc/pressure/io  /proc/pressure/memory <==
tail: error reading ‘/proc/pressure/memory’: Operation not supported

PSI (Pressure Stall Information) is not available


[opc@sillymetrics ~]$ uname --kernel-release

5.4.17-2036.101.2.el7uek.x86_64

Kernel >= 4.20 is ok


[opc@sillymetrics ~]$ grep PSI /boot/config-$(uname --kernel-release)

CONFIG_PSI=y
CONFIG_PSI_DEFAULT_DISABLED=y

Kernel is compiled with PSI but disabled by default


[opc@sillymetrics ~]$ cat /proc/cmdline

BOOT_IMAGE=/boot/vmlinuz-5.4.17-2036.101.2.el7uek.x86_64 root=UUID=53da1572-8973-42e7-965b-fa6c46efb805 ro crashkernel=auto LANG=en_US.UTF-8 console=tty0 console=ttyS0,9600 rd.luks=0 rd.lvm=0 rd.md=0 rd.dm=0 rd.iscsi.bypass=1 netroot=iscsi:169.254.0.2:::1:iqn.2015-02.oracle.boot:uefi iscsi_param=node.session.timeo.replacement_timeout=6000 net.ifnames=1 nvme_core.shutdown_timeout=10 ipmi_si.tryacpi=0 ipmi_si.trydmi=0 ipmi_si.trydefaults=0 libiscsi.debug_libiscsi_eh=1 loglevel=4 ip=single-dhcp crash_kexec_post_notifiers

I need to add “psi=1” to the kernel boot command line

Add boot command line option


[opc@sillymetrics ~]$ tuned-adm active

Current active profile: oci-rps-xps oci-busy-polling oci-cpu-power oci-nic

The system is managed by tuned, I’ll add a custom bootcmdline profile


[opc@sillymetrics ~]$ sudo su -
[root@sillymetrics ~]# mkdir /etc/tuned/my_bootcmdline && cat > /etc/tuned/my_bootcmdline/tuned.conf <<'TAC'

[main]
summary=Enable Pressure Stall Information as in https://blog.dbi-services.com/psi-pressure-stall-information/
[bootloader]
cmdline=psi=1

TAC

I’ve created a new profile for the bootloader commandline options


[root@sillymetrics ~]# tuned-adm profile oci-rps-xps oci-busy-polling oci-cpu-power oci-nic my_bootcmdline

I switched to this profile


[root@sillymetrics ~]# systemctl reboot
Connection to 129.213.62.243 closed by remote host.
Connection to 129.213.62.243 closed.

[opc@a pmm]$ ssh 129.213.62.243
Last login: Wed Jan  6 14:07:18 2021
Welcome to Autonomous Linux
Effective kernel version is 5.4.17-2036.101.2.el7uek.x86_64

I restarted the system


[opc@sillymetrics ~]$ cat /proc/cmdline

BOOT_IMAGE=/boot/vmlinuz-5.4.17-2036.101.2.el7uek.x86_64 root=UUID=53da1572-8973-42e7-965b-fa6c46efb805 ro crashkernel=auto LANG=en_US.UTF-8 console=tty0 console=ttyS0,9600 rd.luks=0 rd.lvm=0 rd.md=0 rd.dm=0 rd.iscsi.bypass=1 netroot=iscsi:169.254.0.2:::1:iqn.2015-02.oracle.boot:uefi iscsi_param=node.session.timeo.replacement_timeout=6000 net.ifnames=1 nvme_core.shutdown_timeout=10 ipmi_si.tryacpi=0 ipmi_si.trydmi=0 ipmi_si.trydefaults=0 libiscsi.debug_libiscsi_eh=1 loglevel=4 ip=single-dhcp crash_kexec_post_notifiers psi=1

My boot command line option is there (psi=1)


[opc@sillymetrics ~]$ sudo tail /proc/pressure/*

[root@psi log]# sudo tail /proc/pressure/*
==> /proc/pressure/cpu  /proc/pressure/io  /proc/pressure/memory <<==
some avg10=0.00 avg60=0.00 avg300=0.00 total=0
full avg10=0.00 avg60=0.00 avg300=0.00 total=0

The Pressure Stall Information is now available

Test on CPU resource

PSI measures the “pressure” on a ressource. There is no pressure if the resource is immediately available when needed: throughput can go to its maximum with the latency at its minimum. Pressure increases when contention happens on the resource usage, introducing delay, queuing, stalling. IO and RAM access can block all processes when pressure is at maximum, PSI will label that as “full” pressure where all tasks have to wait, and then the CPU being idle during that time, wasting all CPU cycles because all runnable tasks cannot run. But in this blog post I’ll test on CPU pressure only, where we never see this “full” state because the baseline of PSI is the time where the CPU can execute a task. And PSI measures the amount of this runnable time that is wasted because of CPU starvation. PSI has a counter for the time where not all, but “some” tasks are delayed because of resource pressure: accounted as soon as at least one runnable task was delayed, waiting in the runqueue. The important point is that PSI is about wallclock time where tasks are delayed. However, this time is normalized to account for the surface area of the pressure. One task waiting, on average, in front of many running ones, is a smaller pressure than one waiting in front of one running. So this wallclock time is divided by the number of CPU.

Here is what I’ve run on a 2 vCPU instance (the free tier one in the Oracle Cloud).


uptime ; cat /proc/pressure/cpu
for i in {1..1} ; do timeout 60 yes & done > /dev/null ; wait ; uptime ; cat /proc/pressure/cpu
for i in {1..2} ; do timeout 60 yes & done > /dev/null ; wait ; uptime ; cat /proc/pressure/cpu
for i in {1..3} ; do timeout 60 yes & done > /dev/null ; wait ; uptime ; cat /proc/pressure/cpu
for i in {1..4} ; do timeout 60 yes & done > /dev/null ; wait ; uptime ; cat /proc/pressure/cpu
for i in {1..5} ; do timeout 60 yes & done > /dev/null ; wait ; uptime ; cat /proc/pressure/cpu
for i in {1..6} ; do timeout 60 yes & done > /dev/null ; wait ; uptime ; cat /proc/pressure/cpu
sleep 10 ; uptime ; cat /proc/pressure/cpu
sleep 10 ; uptime ; cat /proc/pressure/cpu
sleep 10 ; uptime ; cat /proc/pressure/cpu
sleep 10 ; uptime ; cat /proc/pressure/cpu
sleep 10 ; uptime ; cat /proc/pressure/cpu
sleep 10 ; uptime ; cat /proc/pressure/cpu
sleep 10 ; uptime ; cat /proc/pressure/cpu

I’ll detail the result for each line (60 second run) where I scale the running processes.


 14:53:24 up 46 min,  1 user,  load average: 0.00, 0.10, 0.21
some avg10=0.24 avg60=0.45 avg300=2.01 total=124839977

After no activity for more than 5 minutes all counters are at zero.


 14:54:24 up 47 min,  1 user,  load average: 0.63, 0.27, 0.26
some avg10=0.46 avg60=0.54 avg300=1.74 total=125243785

After 1 process in R state for 60 seconds the pressure is negligible as I have two vCPUs here. PSI is much easier than load average: you don’t have to compare it to the number of vCPU. AS long as you run less tasks than the number of CPU thread, there is no pressure.


 14:55:24 up 48 min,  1 user,  load average: 1.70, 0.65, 0.39
some avg10=2.87 avg60=2.05 avg300=1.95 total=127191314

After 2 processes in R state for 60 seconds the pressure is still negligible. As my instance is an Oracle Cloud free tier one, it is actually 1/8th of OCPU. Here only 25% of the CPU is available (the remaining is seen as %steal by the hypervisor). But the Pressure Stall Information doesn’t look at physical processors. The baseline is the available clock time for each vCPU. Don’t forget that PSI measures the pressure, not the slowdown. Running one task on a 2-vCPU does not experience pressure, as it is always being able to run with the maximum potential of the vCPUs.


 14:56:24 up 49 min,  1 user,  load average: 2.52, 1.08, 0.56
some avg10=51.43 avg60=33.01 avg300=11.04 total=158241805

After 3 processes 100% in R state for 60 seconds I have encountered CPU starvation. With 3 runnable tasks and only 2 vCPU available, there is always 1 task stall, at each second. The wallclock time in the “some” state is 60 seconds. However, PSI divides this pressure, from one task starving, by the number of vCPU. Pressure will be 100% only when all vCPU will have a task waiting for it. So, here, 1 task on average was delayed out in front of 2 busy threads, and then each second adds only 0.5 second of pressure. This is why the total stall time (in microseconds) shows (158241805-127191314)/1e6=31 seconds during this 60 seconds of elapsed time. This, within 60 seconds, is 51%, as displayed by the average on the last 10 seconds. The 60 seconds average was not updated immediately and shows only 33%. But the “total” is real time and this is where PSI is great: fine grained visualization of stall situation. This is a big advantage over load average, where you have to wait 1 minute to get the full picture.


 14:57:24 up 50 min,  1 user,  load average: 3.43, 1.60, 0.77
some avg10=95.91 avg60=72.92 avg300=26.86 total=216575324

After 4 processes in the R state for 60 seconds, each were able to run on CPU only half of their time. The stall time is (216575324-158241805)/1e6=58 seconds (full seconds as 2 tasks are waiting in front of 2 CPUs) within this minute. The pressure is high.


 14:58:24 up 51 min,  1 user,  load average: 4.47, 2.22, 1.04
some avg10=99.86 avg60=89.92 avg300=40.43 total=276578094

 14:59:24 up 52 min,  1 user,  load average: 5.52, 2.94, 1.36
some avg10=99.93 avg60=96.20 avg300=51.49 total=336583746

After 5 processes in R state for 60 seconds, and then 6 processes, the pressure is very high. It will never reach 100% because you can always add a new task willing to run. But the pressure increases, with the runqueue and the wait time.


 14:59:34 up 52 min,  1 user,  load average: 4.75, 2.86, 1.35
some avg10=39.88 avg60=82.54 avg300=50.00 total=336584509
 14:59:44 up 53 min,  1 user,  load average: 4.02, 2.77, 1.34
some avg10=14.68 avg60=69.89 avg300=48.32 total=336584773
 14:59:54 up 53 min,  1 user,  load average: 3.40, 2.67, 1.32
some avg10=6.34 avg60=59.45 avg300=46.75 total=336782396
 15:00:04 up 53 min,  1 user,  load average: 2.88, 2.59, 1.31
some avg10=2.33 avg60=50.34 avg300=45.17 total=336787339
 15:00:14 up 53 min,  1 user,  load average: 2.44, 2.50, 1.30
some avg10=0.85 avg60=42.63 avg300=43.64 total=336791024
 15:00:24 up 53 min,  1 user,  load average: 2.65, 2.55, 1.32
some avg10=1.90 avg60=36.57 avg300=42.28 total=337114177
 15:00:34 up 53 min,  1 user,  load average: 2.32, 2.48, 1.31
some avg10=0.70 avg60=30.96 avg300=40.85 total=337114942

Here is the cooldown during one minute. Looking at the difference in the cumulative total shows that starvation is gone: (337114942-336583746)/1e6=0.5 second. The 10s and 60s time window counters take some time to decrease. PSI is real time, and can even trigger notifications. It makes it a very interesting metric to monitor for serverless elastic auto-scaling services.

I have the same test on different shapes of VM, with 2, 4 and 8 vCPUs. In the graph below, the X-axis is the number of processes running and the Y-axis is the PSI avg10 after running 20 seconds. The load average would be linear with the number of processes running (but I would have run at least 1 minute to get it). Here we see that PSI is linear when the number of running processes is between one per CPU and two per CPU. But when some CPU are idle, PSI is nearly 0% pressure. And when all CPU have one task stall, PSI is nearly 100%.

The test I did above is similar to the red line here, on a 2 vCPU shape:

The yellow line is on 4 vCPU and the green line on 8 vCPU. In summary, Pressure Stall Information divides the CPU load into 3 zone:

  • no pressure when some CPU are idle
  • pressure increases when all CPU are busy with some tasks stall
  • pressure is maximal when there are more stall task than running

Cet article Pressure Stall Information on Autonomous Linux est apparu en premier sur Blog dbi services.

Oracle 21c: Blockchain Tables

$
0
0

Oracle Blockchain Tables

With Oracle Database 20c/21c the new feature Oracle Blockchain Tables has been introduced.

Blockchain Tables enable Oracle Database users to create tamper-resistant data management without distributing a ledger across multiple parties.

Database security can be improved by using Blockchain Tables to avoid user fraud and administrator fraud as well.

One of the main characteristics of Oracle Blockchain Tables is that you can only append data. Table rows are chained using a cryptographic hashing approach.

In addition, to avoid administrator or identity fraud, rows can optionally be signed with PKI (public key infrastructure) based on the user’s private key.

Use cases can be a centralized storage of compliance data, audit trail or clinical trial.

Let’s have a look how it works.

Creating an Oracle Blockchain Table:
Quite easy, I’ve used Oracle Database 20.3


select version_full from v$instance;
VERSION_FULL     
-----------------
20.3.0.0.0

CREATE BLOCKCHAIN TABLE bank_ledger (bank VARCHAR2(128), deposit_date DATE, deposit_amount NUMBER)
         NO DROP UNTIL 31 DAYS IDLE
         NO DELETE LOCKED
         HASHING USING "SHA2_512" VERSION "v1";
Error report -
ORA-05729: blockchain table cannot be created in root container

select name, pdb from v$services;

alter session set container = pdb1;

CREATE BLOCKCHAIN TABLE bank_ledger (bank VARCHAR2(128), deposit_date DATE, deposit_amount NUMBER)
         NO DROP UNTIL 31 DAYS IDLE
         NO DELETE LOCKED
         HASHING USING "SHA2_512" VERSION "v1";
Blockchain TABLE created.

Changing retention period on Blockchain Tables:
The table was created with a retention time of “31 DAYS IDLE”, can we reset that value?


ALTER TABLE bank_ledger NO DROP UNTIL 16 DAYS IDLE; 
Error report - 
ORA-05732: retention value cannot be lowered 

ALTER TABLE bank_ledger NO DROP UNTIL 42 days idle; 
Table BANK_LEDGER altered.

Appending Data in Oracle Blockchain Tables:
That’s working fine.


SELECT user_name, distinguished_name, 
          UTL_RAW.LENGTH(certificate_guid) CERT_GUID_LEN, 
          DBMS_LOB.GETLENGTH(certificate) CERT_LEN 
          FROM DBA_CERTIFICATES ORDER BY user_name; 
no rows selected
 
desc bank_ledger 
Name Null? Type 
------------------------------------ 
BANK VARCHAR2(128) 
DEPOSIT_DATE 
DATE 
DEPOSIT_AMOUNT NUMBER 

select * from bank_ledger; 
no rows selected
... 
1 row inserted. 
1 row inserted. 
1 row inserted.

BANK             DEPOSIT_           DEPOSIT_AMOUNT 
-------------------------------------------------- 
UBS              01.01.20           444000000 
Credit Suisse    02.02.20           22000000 
Vontobel         03.03.20           1000000

DML and DDL on Oracle Blockchain Tables:
Let’s try to change some data.


update bank_ledger set deposit_amount=10000 where bank like 'UBS';
Error starting at line : 1 in command -
update bank_ledger set deposit_amount=10000 where bank like 'UBS'
Error at Command Line : 1 Column : 8
Error report -
SQL Error: ORA-05715: operation not allowed on the blockchain table

delete from bank_ledger where bank like 'UBS';
Error starting at line : 1 in command -
delete from bank_ledger where bank like 'UBS'
Error at Command Line : 1 Column : 13
Error report -
SQL Error: ORA-05715: operation not allowed on the blockchain table

drop table bank_ledger;
Error starting at line : 1 in command -
drop table bank_ledger
Error report -
ORA-05723: drop blockchain table BANK_LEDGER not allowed

Copying data from an Oracle Blockchain Table:
Ok, we can’t change data in the original table, let’s try to copy it.


create tablespace bank_data;
Tablespace BANK_DATA created.

CREATE BLOCKCHAIN TABLE bad_bank_ledger (bank VARCHAR2(128), deposit_date DATE, deposit_amount NUMBER) 
         NO DROP UNTIL 31 DAYS IDLE
         NO DELETE LOCKED
         HASHING USING "SHA2_512" VERSION "v1"
         tablespace bank_data;
Blockchain TABLE created.

insert into bad_bank_ledger select * from bank_ledger;
Error starting at line : 1 in command -
insert into bad_bank_ledger select * from bank_ledger
Error at Command Line : 1 Column : 13
Error report -
SQL Error: ORA-05715: operation not allowed on the blockchain table

Alternative actions on Oracle Blockchain Tables:
Can we move tablespaces or try to replace tables?


insert into bad_bank_ledger values ('Vader', '09-09-2099', '999999999');
insert into bad_bank_ledger values ('Blofeld', '07-07-1977', '7777777');
insert into bad_bank_ledger values ('Lecter', '08-08-1988', '888888');

1 row inserted.
1 row inserted.
1 row inserted.

select * from bad_bank_ledger;
BANK                                   DEPOSIT_ DEPOSIT_AMOUNT
----------------------------------------------- --------------
Vader                                  09.09.99      999999999
Blofeld                                07.07.77        7777777
Lecter                                 08.08.88         888888

create table new_bad_bank_ledger as select * from bad_bank_ledger;
Table NEW_BAD_BANK_LEDGER created.

update new_bad_bank_ledger set deposit_amount = 666666 where bank like 'Blofeld';
1 row updated.
commit;
commit complete.

select * from new_bad_bank_ledger;
BANK                                   DEPOSIT_ DEPOSIT_AMOUNT
----------------------------------------------- --------------
Vader                                  09.09.99      999999999
Blofeld                                07.07.77         666666
Lecter                                 08.08.88         888888

drop table bad_bank_ledger;
Error starting at line : 1 in command -
drop table bad_bank_ledger
Error report -
ORA-05723: drop blockchain table BAD_BANK_LEDGER not allowed

drop tablespace bank_data INCLUDING CONTENTS and datafiles;
Error starting at line : 1 in command -
drop tablespace bank_data INCLUDING CONTENTS and datafiles
Error report -
ORA-00604: error occurred at recursive SQL level 1
ORA-05723: drop blockchain table BAD_BANK_LEDGER not allowed
00604. 00000 -  "error occurred at recursive SQL level %s"
*Cause:    An error occurred while processing a recursive SQL statement
           (a statement applying to internal dictionary tables).
*Action:   If the situation described in the next error on the stack
           can be corrected, do so; otherwise contact Oracle Support.

Move or Compress on Oracle Blockchain Table:
Table operations are forbidden in either case.


alter table bank_ledger move tablespace bank_data COMPRESS;
Error starting at line : 1 in command -
alter table bank_ledger move tablespace bank_data COMPRESS
Error report -
ORA-05715: operation not allowed on the blockchain table

alter table bank_ledger move tablespace bank_data;
Error starting at line : 1 in command -
alter table bank_ledger move tablespace bank_data
Error report -
ORA-05715: operation not allowed on the blockchain table

Hidden Columns in Oracle Blockchain Tables:
Every row is identified by hidden attributes.


col table_name for a40
set lin 999
set pages 100

SELECT * FROM user_blockchain_tables;
desc bank_ledger
SELECT column_name, hidden_column FROM user_tab_cols WHERE table_name='BANK_LEDGER';

TABLE_NAME                         ROW_RETENTION ROW TABLE_INACTIVITY_RETENTION HASH_ALG
------------------------------------------------ --- -------------------------- --------
BANK_LEDGER                                  YES                             42 SHA2_512
BAD_BANK_LEDGER                              YES                             31 SHA2_512

Name           Null? Type          
-------------- ----- ------------- 
BANK                 VARCHAR2(128) 
DEPOSIT_DATE         DATE          
DEPOSIT_AMOUNT       NUMBER        

COLUMN_NAME                            HID
-------------------------------------- ---
ORABCTAB_SIGNATURE$                    YES
ORABCTAB_SIGNATURE_ALG$                YES
ORABCTAB_SIGNATURE_CERT$               YES
ORABCTAB_SPARE$                        YES
BANK                                   NO 
DEPOSIT_DATE                           NO 
DEPOSIT_AMOUNT                         NO 
ORABCTAB_INST_ID$                      YES
ORABCTAB_CHAIN_ID$                     YES
ORABCTAB_SEQ_NUM$                      YES
ORABCTAB_CREATION_TIME$                YES
ORABCTAB_USER_NUMBER$                  YES
ORABCTAB_HASH$                         YES
13 rows selected. 

set colinvisible on
desc bank_ledger
Name                                 Null? Type                        
------------------------------------ ----- --------------------------- 
BANK                                       VARCHAR2(128)               
DEPOSIT_DATE                               DATE                        
DEPOSIT_AMOUNT                             NUMBER                      
ORABCTAB_SPARE$ (INVISIBLE)                RAW(2000 BYTE)              
ORABCTAB_SIGNATURE_ALG$ (INVISIBLE)        NUMBER                      
ORABCTAB_SIGNATURE$ (INVISIBLE)            RAW(2000 BYTE)              
ORABCTAB_HASH$ (INVISIBLE)                 RAW(2000 BYTE)              
ORABCTAB_SIGNATURE_CERT$ (INVISIBLE)       RAW(16 BYTE)                
ORABCTAB_CHAIN_ID$ (INVISIBLE)             NUMBER                      
ORABCTAB_SEQ_NUM$ (INVISIBLE)              NUMBER                      
ORABCTAB_CREATION_TIME$ (INVISIBLE)        TIMESTAMP(6) WITH TIME ZONE 
ORABCTAB_USER_NUMBER$ (INVISIBLE)          NUMBER                      
ORABCTAB_INST_ID$ (INVISIBLE)              NUMBER    

set lin 999
set pages 100
col bank for a40

select bank, deposit_date, orabctab_creation_time$ from bank_ledger;
BANK                                     DEPOSIT_ ORABCTAB_CREATION_TIME$        
---------------------------------------- -------- -------------------------------
UBS                                      01.01.20 25.09.20 13:17:03.946615000 GMT
Credit Suisse                            02.02.20 25.09.20 13:17:03.951545000 GMT
Vontobel                                 03.03.20 25.09.20 13:17:03.952064000 GMT

We see that it is not possible to modify an Oracle Blockchain Table on database level. To avoid manipulations from users with root access there are several possibilities to protect data, e.g. by transferring cryptographic hashes and user signatures systematically to external vaults which would enable you to recover data against the most disaster scenarios.

Resources:

https://www.oracle.com/blockchain/#blockchain-platform-tab

https://docs.oracle.com/en/cloud/paas/blockchain-cloud/user/create-rich-history-database.html#GUID-266145A1-EF3A-4917-B174-C50D4DB1A0E3

https://docs.oracle.com/en/database/oracle/oracle-database/21/nfcon/details-oracle-blockchain-table-282449857.html

https://docs.oracle.com/en/database/oracle/oracle-database/21/admin/managing-tables.html#GUID-43470B0C-DE4A-4640-9278-B066901C3926

Cet article Oracle 21c: Blockchain Tables est apparu en premier sur Blog dbi services.

JENKINS Quick overview on Jenkins and Jenkins X

$
0
0

Hi everybody,

During previous blog ,we ever talked about Jenkins , but what about Jenkins X ?What are the differences between the two, which one is fitting the best for your needs?
History of Jenkins X
As the cloud technology were growing along years Developers were asking for a tool that fit to this new standard, helping them to increase their development and to simplify it especially with containerization and usage of Kubernetes orchestrator ,that is why Jenkins X was created
I will present you a quick overview of what Jenkins and Jenkins X are able to do (if you want to go further you can also check the Jenkins.io site that provides all the characteristics of both tools)
Of course it is only my feeling and feedback about these two softwares so if you have any suggestion or idea to share, feel free to comment 🙂

What you must know about Jenkins

Jenkins is the most famous Continuous Integration (CI) server tool. It is open source and written in Java language.
As it is built for continuous integration it allows developers to test software and easily integrate their changes.
It can adapt to every need as it is extendable by using the appropriate plugins

What you must know about Jenkins X

Jenkins X is a Kubernetes-native continuous integration and continuous delivery platform for developing cloud native applications
Jenkins X aims to automate and accelerate Continuous Integration ,performs automated testing and Continuous Delivery for developers on the cloud and/or Kubernetes
It’s defined as a cloud and Kubernetes’ native sub-project of Jenkins
Is supported by popular cloud platforms like Amazon,IBM Cloud or Azure
It is an ecosystem that can integrate many CI/CD tools such as:

  1. Kaniko (a tool to build container images from a Dockerfile)
  2. A Docker registry
  3. Helm which is a Kubernetes application manager
  4. Tekton,a powerful and flexible open-source framework for creating CI/CD systems
  5. Many others

Jenkins features:

  • Easy installation: Just launch msi or sh binary and you have your orchestrator ready on your machine,no deep configuration needed at start and your software is ready by opening your web browser (see my Jenkins installation blog)
  • Easy configuration: Gui interface allow you to easily add nodes , configure your builds to run and configure your master
  • Using GUI to create builds, pipelines and to administrate nodes,interact with Git,customize projects
  • High flexibility
  • Can adapt to every need with more than 1000 plugins to customize your environment

Jenkins X features:

  • Single CLI usage to perform every task efficient for Kubernetes management works as a pre configured tool
    1 command can create Kubernetes clusters application or deploy pipelines
  • Jenkins X is ready to work without adding plugins contrary to Jenkins
  • Automated CI and CD : instead of having deep skills in Jenkins Pipeline, Jenkins X produce generic  pipelines for your projects to  implements CI/CD (no need to have advanced knowledge on how to use the cloud or how to create complex pipelines)
  • Environment Promotion via GitOps – Each team gets a set of Environments. Jenkins X then automates the management of the Environments and the Promotion of new versions of Applications between Environments via GitOps

Architecture of Jenkins and Jenkins X

Although these two tools can perform such similar tasks their architecture are quite different let’s see that below:

A.Jenkins architecture

Jenkins is composed of the Jenkins master that will orchestrate different tasks given to nodes linked to him named slaves
One Jenkins master can manage one or many slaves ( it is best practice to performs the task on the slave instead of on the master for many reasons (for security purposes or  for performance ,to keep Jenkins master infrastructure healthy it is better to spread charge on the slave nodes instead of master)
Below a classic Jenkins distributed architecture with many slaves from different OS
Jenkins master will send the tasks to build to the slaves(Linux ,Window,Unix)

B.Jenkins X architecture

On the other hand Jenkins X is a complete ecosystem associating many softwares and can also have Jenkins itself embedded

To sum up the  characteristics of Jenkins and Jenkins X

Jenkins  Jenkins X
Jenkins can be configured to fit the need Jenkins X is more ready-to-go view and is Kubernetes and cloud native
Need to know, install configure plugins and requires several  integrations Works as a pre-configured tool to ease configuration and usage
Using GUI for configuration and based on plugins tuning.


Plugin management

Using CLI to manage every task ( for example create cluster or pipeline )
Example of command line
jx create cluster aks – Create a new Kubernetes cluster on AKS:Runs on Azure
jx create cluster eks – Create a new Kubernetes cluster on AWS using EKS
jx create cluster gke – Create a new Kubernetes cluster on GKE:Runs on Google Cloud
jx create cluster iks – Create a new kubernetes cluster on IBM Cloud Kubernetes Services
Jenkins adapts to the processes which are required or needed Jenkins X defines the process

Conclusion

Depending of what you will need you can choose Jenkins or Jenkins X
Jenkins will be used for quick tests analysis and also can do the same task as Jenkins X depending on plugin installation, it works as customizable tool.
Jenkins X is more cloud based and also developed for the use of Kubernetes it works as a pre-configured tool, it will accelerate your CI/CD and allow you to achieve your projects faster , in fact it is the best solution for modern cloud applications on Kubernetes.
Also we know now that Jenkins is  the tool that can be tuned as well to fit to your need especially by using loads of plugins whereas Jenkins X has been pre-configured in order to simply its usage (that can be extend and customize)
You can check more info on https://www.jenkins.io/ site and don’t forget to stay tuned on dbi bloggers site!

Cet article JENKINS Quick overview on Jenkins and Jenkins X est apparu en premier sur Blog dbi services.


How long does it take to redeploy an ODA X8-2M?

$
0
0

Introduction

One of the advantage of Oracle Database Appliance is its fast deployment. Most often, the initial setup of a lite ODA (with a reimaging) only takes half a day, from unboxing until a first test database is available. Trust me, it’s hard to do better. For some reason, for example if you need to patch to the latest release and you have some intermediate patch to apply before, or if you plan to change your network settings, or if your ODA does not work correctly anymore, you can imagine redeploying your ODA from scratch as it will not take days. But as you may know, redeployment means that everything will be erased, and you will have to rebuild all your databases, apply your various settings again, and so on. As a consequence, redeploying an ODA is only something you will consider if you use Data Guard or Dbvisit Standby in order to switch your databases to another ODA before redeployment. A standalone ODA is never a comfortable solution, and you’ll probably never be able to redeploy it.

Appart from your DBA job, redeploying an ODA is split in 2 tasks: reimaging the system, and creating the appliance. Reimaging is basically an OS installation that will erase the system disks, creating the appliance is installing all the Oracle stack, from Linux user creation until a first database creation.

But how much time do you need to redeploy an ODA? Let’s find out some clues based on a redeployment I did a few days ago.

Preparing redeployment

A good preparation is mandatory in order to minimize the downtime due to redeployment. Even if you use Data Guard or Dbvisit Standby, you’d better do the redeployment in few hours instead of few days because you will not be able to failover in case of a problem during this operation (and if you’re very unlucky).

What you need to prepare before starting:

  • all the files needed for redeployment according to your target version: ISO file, patch file, GI clone, RDBMS clones
  • unzip the ISO file for reimaging on your own computer (you don’t need to unzip the other files)
  • connect to ILOM interface to make sure that you still have the credentials and that remote console is working
  • backup each important file on an external volume in order to recover some parameters or files if needed

Important files for me are:

  • the result of odacli describe-system
  • history of root commands
  • history of oracle commands
  • crontab of oracle user
  • crontab of root user
  • content of fstab file
  • content of oratab file
  • list of all running databases ps -ef | grep pmon
  • list of all configured databases odacli list-databases
  • deployment file used for initial deployment of this ODA
  • or if deployment file is missing, information related to network configuration, disk sizing, redundancy, aso
  • tnsnames.ora, sqlnet.ora and listener.ora from the various homes

This list is absolutely not complete as it depends on your own environment and tools. Take enough time to be sure you miss nothing. And do this backup days before redeployment.

Cleanup data disks

Reimaging of an ODA will not care about existing headers on NVMe data disks. And will work as if the disks were empty. But odacli create-appliance cares and will prevent Grid Infrastructure to initialize the disks. And appliance creation will fail.

To avoid this behavior, the first step before starting the reimaging is to do a cleanup of the ODA. On an old ODA with spinning disks, it can take a while (many hours), but on a X8-2S or X8-2M it’s a matter of minutes. The longest operation being actually waiting for the node to reboot after cleanup.

My redeployment day started at 9.15AM. The target is to redeploy a X8-2M ODA, already running the latest 19.9 but with a new network configuration to apply. I did a backup of configuration files and already prepared newest json file for redeployment (I just had to change the network settings).

9.15AM => 9.25AM
Do the cleanup with /opt/oracle/oak/onecmd/cleanup.pl

Redeployment

First step of redeployment is reimaging the server with the ISO file corresponding to the target version.

9.25AM => 9.30AM
Start the remote console on ILOM, connect the ISO file in the storage menu, change boot options and restart the server from the CDROM

9.30AM => 10.15AM
Reimaging is automatic and quite long, after being sure the reimaging process is starting, it’s already time for a coffee

10.15AM => 10.20AM
Start configure-firstnet, the very basic initial network configuration (public IP, netmask and gateway). TFA restart is slowing down the operation.

10.20AM => 10.25AM
Copy the zipfiles from your computer to the server (I usually create a /opt/customer folder for these files): the GI clone and all the DB clones you will need

10.25AM => 10.30AM
Unzip and register in the ODA repository GI clone and first DB clone (I usually deploy the DB clone corresponding to the GI clone version)

10.30AM => 11.15AM
odacli create-appliance with your json file. Always use a json file for the create-appliance, GUI is slower and you could make mistakes during configuration.

11.15AM => 11.20AM
Configure the license with odacli update-cpucore -c x. Reimaging will enable again all the cores on your ODA, be careful about that. When using SE2, you do not need to decrease the number of cores, but I highly recommend you to do so.

11.20AM => 11.35AM
Unzip and install the other dbhomes (this is the time it took to register 3 more dbclones and create for each one a dbhome)

11.35AM => 11.50AM
Sanity checks: usually I do a describe-system and describe-components to see if everything is OK, I also check ASM disgroups available space and running instances (+ASM1, +APX1 and my DBTEST)

11.50AM => 12.10PM
It’s now time to configure all the system stuff: extend /opt and/or /u01 to your needs, declare nfs mountpoints (/etc/fstab), add networks with odacli create-network, set mtu on these networks if required, deploy the DBA tools and scripts, set the hugepages to a higher value (from 50% to 70% most often for me) and do a reboot of the server. Now I can enjoy lunch time and I’m quite confident for the next steps because everything looks fine on the pure ODA side.

It took me 3 hours to do the job, let’s say 4 hours if you’re not used to do that. It’s not so long compared to a single patch duration (about 3 hours).

Next steps

For sure, redeploying your ODA is just a (small) part of the job. You now have to recreate/restore your databases, or duplicate them again. And it takes time, especially if you have big databases and a limited number of cores. Never forget that SE2 is not able to parallelize operations like a restore, and EE is limited by the number of cores you enabled on your ODA (2 cores is one processor license).

The database tasks is most probably the biggest part: you may spend hours or days to rebuild your complete server. But with good procedures and good scripts, this task could finally be quite interesting.

If you experience problems during this part, you will learn how to solve them and improve your procedures for the other ODAs.

Advice

I strongly recommend to document everything you do on your ODAs, and script what you can script. And not just before a redeployment. Do that from the very moment you start working with ODA. You will never be affraid of a redeployment if you know exactly how your ODA was installed.

Conclusion

As I already concluded, it makes definitely sense to consider redeployment as an alternative way of patching, and much more. If you know how to quickly redeploy, it could be very helpful compared to days of complex troubleshooting for example. Keep in mind this possibility, even if you’d never consider this option on another platform.

Cet article How long does it take to redeploy an ODA X8-2M? est apparu en premier sur Blog dbi services.

19c serverless logon trigger

$
0
0

By Franck Pachot

.
I thought I already blogged about this but can’t find it. So here it is, with a funny title. I like to rename oracle features by their user point of view (they are usually named from the oracle development point of view). This is about setting session parameters for Oracle connections, directly from the connection string, especially when it cannot be set in the application (code) or in the DB server (logon trigger).

SESSION_SETTINGS

Here is a simple example. I connect with the full connection string (you can put it in a tnsnames.ora of course to use a small alias instead):


SQL*Plus: Release 21.0.0.0.0 - Production on Sun Jan 31 10:03:07 2021
Version 21.1.0.0.0

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

SQL> connect demo/demo1@(DESCRIPTION=(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=db21_pdb1.subnet.vcn.oraclevcn.com))(ADDRESS=(PROTOCOL=TCP)(HOST=cloud)(PORT=1521)))
Connected.
SQL> show parameter optimizer_mode

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
optimizer_mode                       string      ALL_ROWS
SQL>

the OPTIMIZER_MODE is at its default value – ALL_ROWS.

Let’s say that for this connection I want to use FIRST_ROWS_10 because I know that results will always be paginated to the screen. But I can’t change the application to issue an ALTER SESSION. I can do it from the client connection string by adding (SESSION_SETTINGS=(optimizer_mode=first_rows_10)) in CONNECT_DATA, at the same level as the SERVICE_NAME I connect to:


SQL> connect demo/demo1@(DESCRIPTION=(CONNECT_DATA=(SESSION_SETTINGS=(optimizer_mode=first_rows_10))(SERVER=DEDICATED)(SERVICE_NAME=db21_pdb1.subnet.vcn.oraclevcn.com))(ADDRESS=(PROTOCOL=TCP)(HOST=cloud)(PORT=1521)))
Connected.

SQL> show parameter optimizer_mode

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
optimizer_mode                       string      FIRST_ROWS_10
SQL>

This has been automatically set at connection time.

logon trigger

I could have done this from the server with a logon trigger:


SQL> create or replace trigger demo.set_session_settings after logon on demo.schema
  2  begin
  3    execute immediate 'alter session set optimizer_mode=first_rows_100';
  4  end;
  5  /

Trigger created.

SQL> connect demo/demo1@(DESCRIPTION=(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=db21_pdb1.subnet.vcn.oraclevcn.com))(ADDRESS=(PROTOCOL=TCP)(HOST=cloud)(PORT=1521)))
Connected.
SQL> show parameter optimizer_mode

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
optimizer_mode                       string      FIRST_ROWS_100

Here, with no SESSION_SETTINGS in the connection string, the session parameter is set. Of course the logon trigger may check additional context to set it for specific usage. You have the full power of PL/SQL here.

You probably use the connection string setting when you can’t or don’t want to define it in a logon trigger. But what happens when I use SESSION_SETTINGS in CONNECT_DATA in addition to the logon trigger?


SQL> connect demo/demo1@(DESCRIPTION=(CONNECT_DATA=(SESSION_SETTINGS=(optimizer_mode=first_rows_10))(SERVER=DEDICATED)(SERVICE_NAME=db21_pdb1.subnet.vcn.oraclevcn.com))(ADDRESS=(PROTOCOL=TCP)(HOST=cloud)(PORT=1521)))
Connected.
SQL> show parameter optimizer_mode

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
optimizer_mode                       string      FIRST_ROWS_100

There is a priority for the logon trigger. The DBA always wins 😉 And there’s no error or warning, because your setting works, but is just changed later.

SQL_TRACE

Of course this is very useful to set SQL_TRACE and TRACEFILE_IDENTIFIER that you may need to set temporarily:


SQL> connect demo/demo1@(DESCRIPTION=(CONNECT_DATA=(SESSION_SETTINGS=(sql_trace=true)(tracefile_identifier=franck))(SERVER=DEDICATED)(SERVICE_NAME=db21_pdb1.subnet.vcn.oraclevcn.com))(ADDRESS=(PROTOCOL=TCP)(HOST=cloud)(PORT=1521)))
Connected.

SQL> select value from v$diag_info where name='Default Trace File';

VALUE
--------------------------------------------------------------------------------
/u01/app/oracle/diag/rdbms/db21_iad36d/DB21/trace/DB21_ora_93108_FRANCK.trc

Here is what I see in the trace:


PARSING IN CURSOR #140571524262416 len=45 dep=1 uid=110 oct=42 lid=110 tim=4586453211272 hv=4113172360 ad='0' sqlid='1b8pu0mukn1w8'
ALTER SESSION SET tracefile_identifier=franck
END OF STMT
PARSE #140571524262416:c=312,e=312,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=0,plh=0,tim=4586453211272

*** TRACE CONTINUES IN FILE /u01/app/oracle/diag/rdbms/db21_iad36d/DB21/trace/DB21_ora_93108_FRANCK.trc ***

sql_trace was set and then tracefile_identifier.
The current trace file (with the tracefile_identifier) shows the code from my logon trigger:


*** 2021-01-31T10:44:38.949537+00:00 (DB21_PDB1(3))
*** SESSION ID:(30.47852) 2021-01-31T10:44:38.949596+00:00
*** CLIENT ID:() 2021-01-31T10:44:38.949609+00:00
*** SERVICE NAME:(db21_pdb1) 2021-01-31T10:44:38.949620+00:00
*** MODULE NAME:(sqlplus@cloud (TNS V1-V3)) 2021-01-31T10:44:38.949632+00:00
*** ACTION NAME:() 2021-01-31T10:44:38.949646+00:00
*** CLIENT DRIVER:(SQL*PLUS) 2021-01-31T10:44:38.949663+00:00
*** CONTAINER ID:(3) 2021-01-31T10:44:38.949684+00:00
*** CLIENT IP:(10.0.0.22) 2021-01-31T10:44:38.949700+00:00
*** CLIENT IP:(10.0.0.22) 2021-01-31T10:44:38.949700+00:00


*** TRACE CONTINUED FROM FILE /u01/app/oracle/diag/rdbms/db21_iad36d/DB21/trace/DB21_ora_93108.trc ***

EXEC #140571524262416:c=601,e=1332,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=0,plh=0,tim=4586453212715
CLOSE #140571524262416:c=4,e=4,dep=1,type=1,tim=4586453213206
=====================
PARSING IN CURSOR #140571524259888 len=81 dep=1 uid=110 oct=47 lid=110 tim=4586453215247 hv=303636932 ad='16d3143b0' sqlid='22pncan91k8f4'
begin
  execute immediate 'alter session set optimizer_mode=first_rows_100';
end;
END OF STMT
PARSE #140571524259888:c=1737,e=1966,p=0,cr=0,cu=0,mis=1,r=0,dep=1,og=1,plh=0,tim=4586453215246

this proves that the logon trigger has priority, or rather the last word, on settings as it is run after, before giving the session to the application.

Module, Action

Before it comes to tracing, we would like to identify our session for end-to-end profiling and this is also possible from the connection string. Oracle does that by defining the “module” and “action” application info. There’s no parameter to set module and action, but there are additional possibilities than SESSION_SETTINGS with MODULE_NAME and MODULE_ACTION:


SQL> connect demo/demo1@(DESCRIPTION=(CONNECT_DATA=(MODULE_NAME=my_application_tag)(MODULE_ACTION=my_action_tag)(SESSION_SETTINGS=(sql_trace=true))(SERVER=DEDICATED)(SERVICE_NAME=db21_pdb1.subnet.vcn.oraclevcn.com))(ADDRESS=(PROTOCOL=TCP)(HOST=cloud)(PORT=1521)))

This sets the module/action as soon as connected, which I can see in the trace:


*** 2021-01-31T10:57:54.404141+00:00 (DB21_PDB1(3))
*** SESSION ID:(484.11766) 2021-01-31T10:57:54.404177+00:00
*** CLIENT ID:() 2021-01-31T10:57:54.404193+00:00
*** SERVICE NAME:(db21_pdb1) 2021-01-31T10:57:54.404205+00:00
*** MODULE NAME:(sqlplus@cloud (TNS V1-V3)) 2021-01-31T10:57:54.404217+00:00
*** ACTION NAME:() 2021-01-31T10:57:54.404242+00:00
*** CLIENT DRIVER:(SQL*PLUS) 2021-01-31T10:57:54.404253+00:00
*** CONTAINER ID:(3) 2021-01-31T10:57:54.404265+00:00
*** CLIENT IP:(10.0.0.22) 2021-01-31T10:57:54.404277+00:00
*** CLIENT IP:(10.0.0.22) 2021-01-31T10:57:54.404277+00:00

CLOSE #139872849242800:c=1,e=2,dep=1,type=1,tim=4587248667205
*** MODULE NAME:(my_application_tag) 2021-01-31T10:57:54.404725+00:00
*** ACTION NAME:(my_action_tag) 2021-01-31T10:57:54.404756+00:00

However, because I run that from sqlplus, this is set later by sqlplus itself later:


SQL> select sid,module,action from v$session where sid=sys_context('userenv','sid');

       SID MODULE                         ACTION
---------- ------------------------------ ------------------------------
        30 SQL*Plus

What you pass it in the connection string, it is run immediately before running anything else (logon trigger or application statements). It is really useful when you cannot run an ALTER SESSION in any other way. But remember that it is just an initial setting and nothing is locked for future change.

more… mostly undocumented

I mentioned that there are more things that can be set from there. Here is how I’ve found about MODULE_NAME and MODULE_ACTION:


strings $ORACLE_HOME/bin/oracle | grep ^DESCRIPTION/CONNECT_DATA/ | cut -d/ -f3- | sort | paste -s

CID/PROGRAM     CID/USER        COLOCATION_TAG  COMMAND CONNECTION_ID   CONNECTION_ID_PREFIX    
DESIG   DUPLICITY       FAILOVER_MODE   FAILOVER_MODE/BACKUP  GLOBAL_NAME     INSTANCE_NAME   
MODULE_ACTION   MODULE_NAME     NUMA_PG ORACLE_HOME     PRESENTATION    REGION  RPC     SEPARATE_PROCESS      
SERVER  SERVER_WAIT_TIMEOUT     SERVICE_NAME    SESSION_SETTINGS        SESSION_STATE   SID     USE_DBROUTER

Most of them are not documented and probably not working the way you think. But FAILOVER_MODE is well known to keep the session running when it fails over to a different node or replica in HA (because the High Availability of the database is at maximum only if the application follows without interruption). SERVER is well know to choose the level of connection sharing and pooling (a must with microservices). The COLOCATION_TAG is a way to favor colocation of sessions processing the same data when you have scaled-out to multiple nodes, to avoid inter-node cache synchronization. You just set a character string that may have a business meaning and the load-balancer will try to keep together those who hash to the same value. INSTANCE_NAME (and SID) are documented to go to a specific instance (for the DBA, the application uses services for that). NUMA_PG looks interesting to colocated sessions in NUMA nodes (visible in x$ksmssinfo) but it is undocumented and then unsupported. And we are far from the “serverless” title when we mention those physical characteristics… I’ve put this title not only to be in the trend but also to mention than things that we are used to set on server side may have to be set on the client-side when we are in the cloud.

CONTAINER

Even in the SESSION_SETTINGS you can put some settings that are not properly session parameters. The CONTAINER one may be convenient:


Yes, as a connection string can be a BEQ protocol, this can be used for local connections (without going through a listener) and is a way to go directly to a PDB. Here is an example:


BEQ_PDB1=
 (DESCRIPTION=
  (ADDRESS_LIST=
   (ADDRESS=
    (PROTOCOL=BEQ)
    (PROGRAM=oracle)
    (ARGV0=oracleCDB1)
    (ARGS='(DESCRIPTION=(SILLY_EXAMPLE=TRUE)(LOCAL=YEP)(ADDRESS=(PROTOCOL=beq)))')
   )
  )
  (CONNECT_DATA=
   (SESSION_SETTINGS=(container=PDB1)(tracefile_identifier=HAVE_FUN))
   (SID=CDB1)
  )
 )

I’ve added a few funny things here, but don’t do that. A `ps` shows:


oracleCDB1 (DESCRIPTION=(SILLY_EXAMPLE=TRUE)(LOCAL=YEP)(ADDRESS=(PROTOCOL=beq)))

for this connection

undocumented parameters

I mentioned that the SESSION_SETTINGS happen before the logon trigger, and that the application can change the parameters, as usual, afterwards. It seems that there are two hidden parameters for that:

_connect_string_settings_after_logon_triggers     0                              set connect string session settings after logon triggers                                        integer
_connect_string_settings_unalterable                         0                              make connect string session settings unalterable                                        integer

However, I tested them and haven’t seen how it works (surprisingly they are not booleans but integers)

Cet article 19c serverless logon trigger est apparu en premier sur Blog dbi services.

Google Cloud SQL Insights: ASH, plans and statement tagging

$
0
0

By Franck Pachot

.
Looking at database performance has always been necessary to optimize the response time or throughput, but when it comes to public cloud where you are charged by resource usage, performance tuning is critical for cost optimization. When looking at host metrics, you see only the symptoms and blindly guess at some solutions: add more vCPU if CPU usage is high, more memory if I/O wait is high. And this can be done automatically with auto-scaling. However, scaling up or out doesn’t solve your problem but only adapts the resources to the usage. In order to know if you actually need those resources or not, you need to drill down on their usage: which queries are responsible for the high CPU or I/O usage, or suffer from pressure on those resources, or are just waiting on something else like background checkpoint, WAL write, application lock,… And look at the execution plan because this is where you can scale down the resource usage by factors of 10 or 100.

Oracle Database has been instrumented for a long time and has proven how the ASH approach is efficient: sampling of active sessions, displaying by Average Active Session on the wait class dimension, on a time axis. The same approach is implemented in AWS where Amazon RDS can provide Performance Insight. And here is the Google Cloud version of it that has been recently released: Cloud SQL Insights.

Database load

Here is an example:

The PostgreSQL wait events are limited but the main ones are displayed here: CPU in green, I/O in blue, and I have an application contention on many sessions, in orange. As you can see here, the retention is 7 days (good to troubleshoot recent issues, not enough to compare with the previous monthly report for example) and the sample frequency seems to be quite low: 1 minute.

Queries

When you have Lock Wait, there’s is something to do: identify the the query waiting. Because this query consumes elapsed time (the user is waiting) without doing any work, and no scaling can help here.
We have a list of queries active during this load samples:

The most important metrics are there, in the order of importance for me:

  • “Times called” is the number of executions during the observed time range. This is very important because you need to understand if this number is expected (given the number of concurrent users) or a mistake in design (row-by-row calls) or ORM mapping (the N+1 problem)
  • “Avg rows returned” is the number of rows per execution. This is very important to estimate the efficiency of the application design. There is a latency for each call, and then you should try to process many rows on each call. If the “Times called” is high, I check the “Avg rows returned”. If it is low, near 1, then there’s probably a row-by-row access to the database, which is the less efficient design. And the load in the database is only the emerged tip of the iceberg in this case. Context switches and network latency are not counted in the “Avg execution time”. Any you can’t imagine the many times I’ve seen a high load, with high number of executions, with 0 rows returned on average. Removing unnecessary work is the first tuning step: do not scale up the instance to run more queries doing nothing. Even if you have an infinite amount of cloud credits, this is not good for earth.
  • Finally “Avg execution time” is the part of the response time the query is responsible of within the database. It is important as a start for a performance analysis, but it requires more information. You must look at the query to understand how it is involved in the response time: called once per user interaction, or many times? Run asynchronously in background, or user waiting on it? And if the time is a significant part that must be improved, you should look at the execution plan to understand what you can improve.

The essentials are there but of course, I miss a few details from here. For Lock Wait, I want to know the blocking transaction. For Queries I want to see not only the top level call (when a query is executed from a procedure – which is the right design for security, agility and performance). And what I miss the most here is the number of buffers read, from shared buffer or from files. Because the execution time can vary with memory or cpu pressure. The real metric for query efficiency is the number of buffers which shows the amount of data read and processed.

Query details

For a query there is some additional details and, when you are lucky and it has been captured, you can see the execution plan in a nice graphical way:

I would have like to show a complex plan but unfortunately all complex queries I’ve run did not have their plan captured.

Query Tags

There’s something really interesting for end-to-end performance tuning. You can tag your queries with specific comments and they can be displayed, aggregated, and filtered:

The tags are defined in the SQL Commenter format for MVC applications: Action, Route, Controller, Framework, Application, DB Driver. And those can be injected in the ORM with sqlcommenter. Or you can mention them with in comments yourself, of course. For example, for the screenshot above I’ve run pgbench with the following file:


\set aid random(1, 100000 * :scale)
\set bid random(1, 1 * :scale)
\set tid random(1, 10 * :scale)
\set delta random(-5000, 5000)
BEGIN;
/* db_driver='LIBPQ', application='pgbench', framework='--file', controller='TPC-B 💰', action='update balance', route='pgbench/tpcb/update/acount' */
UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;

/* db_driver='LIBPQ', application='pgbench', framework='--file', controller='TPC-B 📃', action='ureport balance', route='pgbench/tpcb/select' /*
SELECT abalance FROM pgbench_accounts WHERE aid = :aid;

/* db_driver='LIBPQ', application='pgbench', framework='--file', controller='TPC-B 💰', action='update balance', route='pgbench/tpcb/update/teler' */
UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;

/* db_driver='LIBPQ', application='pgbench', framework='--file', controller='TPC-B 💰', action='update balance', route='pgbench/tpcb/update/brnch' */
UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;

/* db_driver='LIBPQ', application='pgbench', framework='--file', controller='TPC-B 💰', actionn='insert' route='gbench/tpcb/update' */
INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);
END;

This is run with pgbench –file but using the same queries as the TPC-B default one. Note that this workload is good to test many small queries but this is not a scalable application design. Many Application->DB calls for one User->Application call is a latency demultiplicator. All this should be run in a plpgsql procedure (see improving performance with stored procedures — a pgbench example)

There is also a view on the query plan operation timeline:

And more detail about the query execution time:

  • Here I like the presentation with percentiles. Because this is what the SLA with users should be. Some outliers are acceptable but a good user experience is about good response time most of the time
  • Here I don’t like that execution time is named “query latency”. This word is used anywhere in clouds and trendy architectures. But the time here is only about the execution time in the database server. The context switch latency, with row-by-row CRUD API instead of efficient SQL queries, and main source of CPU over-utilization, is not accounted in this metric.

Does this Google Insight use pg_stat_statement? pgSentinel? pg_stat_monitor? pg_stat_plans? or another of the many contributions from the PostgreSQL community?

Actually, those two Google extensions are installed in Cloud SQL PostgreSQL:

postgres=> SELECT * FROM pg_available_extensions where comment like '%Google%';
      name       | default_version | installed_version |                comment
-----------------+-----------------+-------------------+----------------------------------------
 cloudsql_stat   | 1.0             |                   | Google Cloud SQL statistics extension
 google_insights | 1.0             |                   | Google extension for database insights

The google_insights is the one used for this feature and provides some table functions, like: google_insights_aggregated_stats(), google_insights_consume_query_plans(), google_insights_get_internal_metrics(), google_insights_get_internal_metrics(), google_insights_get_query_string_length_distribution(), google_insights_query_stats(), google_insights_reset_all(), google_insights_statistics(), google_insights_tag_stats() and it reads some of those statistics: rows, shared_blks_hit, shared_blks_read, execution_time, write_time, read_time, wait_time_lwlock, wait_time_lock, wait_time_buffer_pin

Looking at this, there is a hope that shared_blks_hit and shared_blks_read will be exposed in the future. They are the most important metrics when comparing two executions, because time depend on too many other parameters.

Unfortunately, like AWS with Amazon RDS Performance Insight, this Google SQL Insight is an extension for the open source PostgreSQL built by a community. But their addition is full proprietary. I hope that one day those cloud providers will not only take the good work from the open source community, but also give back their additions. First because it is fair, even when the license doesn’t make it mandatory. But also because the experienced community looking at it may help to improve those additions. For example, here, I would like to see buffer metrics, more wait events, all queries (top level calls and recursive ones), normalized queries as well as parameters, more frequent sampling to troubleshoot small peaks, longer retention to compare seasonal activity, and be sure that all activity is captured (important ones were missing in my tests). Many of those things already exists in open source extensions. A good synergy between managed cloud vendors and open source community can benefit everybody. But for the moment, we are in cloud wars, and they probably want to personalize their service to differentiate themselves from the competitors.

Cet article Google Cloud SQL Insights: ASH, plans and statement tagging est apparu en premier sur Blog dbi services.

Learn ODA on Oracle Cloud

$
0
0

By Franck Pachot

.
You want to learn and practice your ODA command line and GUI without having an ODA at home? It should be possible to run the ODA image on VirtualBox but that’s probably a hard work as it is tied to the hardware. About the configuration, you can run the Oracle Appliance Manager Configurator on your laptop but I think it is not compatible with the latest odacli. However, for a long time Oracle provides an ODA simulator and it is now available in the Oracle Cloud Marketplace for free.

Here is it page:
https://console.us-ashburn-1.oraclecloud.com/marketplace/application/84422479/overview

You can get there by: Oracle Cloud Console > Create Compute Instance > Edit > Change Image > Oracle Images > Oracle Database Appliance (ODA) Simulator

I mentioned that this is for free. The marketplace does not allow me to run on an Always Free Eligible shape. But you may take the software and run it elsewhere (you will see the .tar.gz in the opc user home directory)

Cleanup and pull images

From the marketplace, the container is already running but I clean it and re-install. This does everything: it installs docker if not already there (you run all this as root).


# cleanup (incl. portainer)
sudo docker rm -f portainer ; sudo docker rmi -f portainer/portainer
yes | sudo ~/simulator*/cleanup_odasimulator_sw.sh
# setup (get images and start portainer)
sudo ~/simulator*/setup_odasimulator_sw.sh

With this you can connect by http on port 9000 to the Portainer. Of course, you need to open this in the Network Security Groups (I opened the range 9000-9100 as I’ll use those ports later). You can connect with user admin password welcome1… yes, that’s the CHANGE_ON_INSTALL password for ODA 😉

Choose Local repository and connect and you will se the users and containers created.

Create ODA simulators


sudo ~/simulator*/createOdaSimulatorContainer.sh -d class01 -t ha -n 2 -p 9004 \
 -i $(oci-public-ip | awk '/Primary public IP:/{print $NF}')

the -d option is a “department name”. You can put whatever you like and you can use it to create multiple classes.
-n is the number of simulators (one per participant in your class for example).
-t is ‘ha’ to create two docker containers to simulate a 2 nodes ODA HA or ‘single’ to simulate a one node ODA-lite.
The default starting port is 7094 but I start at 9004 as I opened the 9000-9100 range.

This has created the containers, storage and starts the ODA software: Zookeeper, DCS agent, DCS controller. You can see them from the Portainer console. It also creates users (the username is displayed, the password is welcome1) in portainer in the “odasimusers” team.

From the container list you have an icon ( >_ ) to go to a command line console (which is also displayed in the createOdaSimulatorContainer.sh output (“ODA cli access”) so that you can give it to your students. One for each node when you chose HA of course. It also displays the url to the ODA Console (“Browser User Interface”) at https://<public ip>:<displayed port>/mgmt/index.html for witch the user is “oda-admin” and the password must be changed at first connect.

Here is an example with mine:


***********************************************
ODA Simulator system info:
Executed on: 2021_02_03_09_39_AM
Executed by:

ADMIN:
ODA Simulator management GUI: http://150.136.58.254:9000
Username: admin Password: welcome1
num=          5
dept=       class01
hostpubip=    150.136.58.254

USERS:
Username: class01-1-node0  Password:welcome1
Container : class01-1-node0
ODA Console: https://150.136.58.254:9005/mgmt/index.html
ODA cli access: http://150.136.58.254:9000/#/containers/86a0846af46251c9389423ad440a807b83645b62a1ec893182e8d15b1d1179bd/exec

Those are my real IP addresses and those ports are opened so you can play with it if it is still up when you read it… it’s a lab.

The Portainer web shell is a possibility but you can go to the Portainer console from the machine where you have created all that you can:


[opc@instance-20210203-1009 simulator_19.9.0.0.0]$ ./connectContainer.sh -n class01-1-node0
[root@class01-1-node0 /]#

Of course you can also simply `sudo docker exec -i -t class01-1-node0 /bin/bash` – there’s nothing magic here. And then you can play with odacli:


[root@class01-1-node0 /]# odacli configure-firstnet

bonding interface is:
Using bonding public interface (yes/no) [yes]:
Select the Interface to configure the network on () [btbond1]:
Configure DHCP on btbond1 (yes/no) [no]:
INFO: You have chosen Static configuration
Use VLAN on btbond1 (yes/no) [no]:
Enter the IP address to configure : 192.168.0.100
Enter the Netmask address to configure : 255.255.255.0
Enter the Gateway address to configure[192.168.0.1] :
INFO: Restarting the network
Shutting down interface :           [  OK  ]
Shutting down interface em1:            [  OK  ]
Shutting down interface p1p1:           [  OK  ]
Shutting down interface p1p2:           [  OK  ]
Shutting down loopback interface:               [  OK  ]
Bringing up loopback interface:    [  OK  ]
Bringing up interface :     [  OK  ]
Bringing up interface em1:    [  OK  ]
Bringing up interface p1p1: Determining if ip address 192.168.16.24 is already in use for device p1p1...    [ OK  ]
Bringing up interface p1p2: Determining if ip address 192.168.17.24 is already in use for device p1p2...    [ OK  ]
Bringing up interface btbond1: Determining if ip address 192.168.0.100 is already in use for device btbond1...     [  OK  ]
INFO: Restarting the DCS agent
initdcsagent stop/waiting
initdcsagent start/running, process 20423

This is just a simulator, not a virtualized ODA, you need to use this address 192.168.0.100 on node0 and 192.168.0.101 on node 1

Some fake versions of the ODA software is there:


[root@class01-1-node0 /]# ls opt/oracle/dcs/patchfiles/

oda-sm-19.9.0.0.0-201020-server.zip
odacli-dcs-19.8.0.0.0-200714-DB-11.2.0.4.zip
odacli-dcs-19.8.0.0.0-200714-DB-12.1.0.2.zip
odacli-dcs-19.8.0.0.0-200714-DB-12.2.0.1.zip
odacli-dcs-19.8.0.0.0-200714-DB-18.11.0.0.zip
odacli-dcs-19.8.0.0.0-200714-DB-19.8.0.0.zip
odacli-dcs-19.8.0.0.0-200714-GI-19.8.0.0.zip
odacli-dcs-19.9.0.0.0-201020-DB-11.2.0.4.zip
odacli-dcs-19.9.0.0.0-201020-DB-12.1.0.2.zip
odacli-dcs-19.9.0.0.0-201020-DB-12.2.0.1.zip
odacli-dcs-19.9.0.0.0-201020-DB-18.12.0.0.zip
odacli-dcs-19.9.0.0.0-201020-DB-19.9.0.0.zip

On real ODA you download it from My Oracle Support but here the simulator will accept those to update the ODA repository:


odacli update-repository -f /opt/oracle/dcs/patchfiles/odacli-dcs-19.8.0.0.0-200714-GI-19.8.0.0.zip
odacli update-repository -f /opt/oracle/dcs/patchfiles/odacli-dcs-19.8.0.0.0-200714-DB-19.8.0.0.zip

You can also go to the web console where, at the first connection, you change the password to connect as oda-admin.


[root@class01-1-node0 opt]# odacli-adm set-credential -u oda-admin
User password: #HappyNewYear#2021
Confirm user password: #HappyNewYear#2021
[root@class01-1-node0 opt]#

Voila I shared my password for https://150.136.58.254:9005/mgmt/index.html 😉
You can also try the next even ports (9007, 9009…9021) and change the password yourself – I’ll leave this machine for a few days after publishing.

And then deploy the database. Here is my oda.json configuration that you can put in a file and load if you want a quick creation without typing:


{ "instance": { "instanceBaseName": "oda-c", "dbEdition": "EE", "objectStoreCredentials": null, "name": "oda", "systemPassword": null, "timeZone": "Europe/Zurich", "domainName": "pachot.net", "dnsServers": [ "8.8.8.8" ], "ntpServers": [], "isRoleSeparated": true, "osUserGroup": { "users": [ { "userName": "oracle", "userRole": "oracleUser", "userId": 1001 }, { "userName": "grid", "userRole": "gridUser", "userId": 1000 } ], "groups": [ { "groupName": "oinstall", "groupRole": "oinstall", "groupId": 1001 }, { "groupName": "dbaoper", "groupRole": "dbaoper", "groupId": 1002 }, { "groupName": "dba", "groupRole": "dba", "groupId": 1003 }, { "groupName": "asmadmin", "groupRole": "asmadmin", "groupId": 1004 }, { "groupName": "asmoper", "groupRole": "asmoper", "groupId": 1005 }, { "groupName": "asmdba", "groupRole": "asmdba", "groupId": 1006 } ] } }, "nodes": [ { "nodeNumber": "0", "nodeName": "GENEVA0", "network": [ { "ipAddress": "192.168.0.100", "subNetMask": "255.255.255.0", "gateway": "192.168.0.1", "nicName": "btbond1", "networkType": [ "Public" ], "isDefaultNetwork": true } ] }, { "nodeNumber": "1", "nodeName": "GENEVA1", "network": [ { "ipAddress": "192.168.0.101", "subNetMask": "255.255.255.0", "gateway": "192.168.0.1", "nicName": "btbond1", "networkType": [ "Public" ], "isDefaultNetwork": true } ] } ], "grid": { "vip": [ { "nodeNumber": "0", "vipName": "GENEVA0-vip", "ipAddress": "192.168.0.102" }, { "nodeNumber": "1", "vipName": "GENEVA1-vip", "ipAddress": "192.168.0.103" } ], "diskGroup": [ { "diskGroupName": "DATA", "diskPercentage": 80, "redundancy": "FLEX" }, { "diskGroupName": "RECO", "diskPercentage": 20, "redundancy": "FLEX" }, { "diskGroupName": "FLASH", "diskPercentage": 100, "redundancy": "FLEX" } ], "language": "en", "enableAFD": "TRUE", "scan": { "scanName": "oda-scan", "ipAddresses": [ "192.168.0.104", "192.168.0.105" ] } }, "database": { "dbName": "DB1", "dbCharacterSet": { "characterSet": "AL32UTF8", "nlsCharacterset": "AL16UTF16", "dbTerritory": "SWITZERLAND", "dbLanguage": "FRENCH" }, "dbRedundancy": "MIRROR", "adminPassword": null, "dbEdition": "EE", "databaseUniqueName": "DB1_GENEVA", "dbClass": "OLTP", "dbVersion": "19.8.0.0.200714", "dbHomeId": null, "instanceOnly": false, "isCdb": true, "pdBName": "PDB1", "dbShape": "odb1", "pdbAdminuserName": "pdbadmin", "enableTDE": false, "dbType": "RAC", "dbTargetNodeNumber": null, "dbStorage": "ASM", "dbConsoleEnable": false, "dbOnFlashStorage": false, "backupConfigId": null, "rmanBkupPassword": null } }

Of course the same can be done from command line. Here are my database created in this simulation:


[root@class01-1-node0 /]# odacli list-databases

ID                                       DB Name    DB Type  DB Version           CDB        Class    Shape    Storage    Status        DbHomeID
---------------------------------------- ---------- -------- -------------------- ---------- -------- -------- ---------- ------------ ----------------------------------------
cc08cd94-0e95-4521-97c8-025dd03a5554     DB1        Rac      19.8.0.0.200714      true       Oltp     Odb1     Asm        Configured   782c749c-ff0e-4665-bb2a-d75e2caa5568

This is the database created by this simulated deployment.

This is really nice to learn or check something without accessing a real ODA. There’s more in a video from Sam K Tan, Business Development Director at Oracle: https://www.youtube.com/watch?v=mrLp8TkcJMI and the hands-on-lab handbook to know more. And about real-live ODA problems and solutions, I have awesome colleagues sharing on our blog: https://blog.dbi-services.com/?s=oda and they give the following trining: https://www.dbi-services.com/trainings/oracle-database-appliance-oda/ in French, German, English, in site or remote.

Cet article Learn ODA on Oracle Cloud est apparu en premier sur Blog dbi services.

An introduction into server side programming in PostgreSQL – 1 – SQL functions, basics

$
0
0

Over the last years I’ve seen many different applications running against PostgreSQL. The use cases vary from simple applications, which only use basic data types and a few tables and views, to complex applications with custom types, more specific data types like jsonb or range types, that use hundreds of schemas/tables/views/materialized views etc. Surprisingly only a few of them make use of functions and procedures in PostgreSQL. I’ve always told people to process the data where it is, and usually the data is in the database, as this is best for performance and enables you to make us of advanced features of PostgreSQL. This does, of course, not only apply to PostgreSQL but to all other databases systems as well. This usually leads to discussion about being independent of the underlying databases and then it gets religious. While I understand the developer/business owner’s point of view of being as much independent of the database as possible, makes it easier to migrate from one database system to another in the future, this decision closes many doors when it comes to getting the maximum out of the database.

The most simple function you can create in PostgreSQL is a SQL function. A SQL functions contain one or more SQL statements and either return nothing, one row or a set of rows. To get started lets create a standard pgbench schema with a couple of rows:

postgres=# \! pgbench -i -s 10 postgres
dropping old tables...
NOTICE:  table "pgbench_accounts" does not exist, skipping
NOTICE:  table "pgbench_branches" does not exist, skipping
NOTICE:  table "pgbench_history" does not exist, skipping
NOTICE:  table "pgbench_tellers" does not exist, skipping
creating tables...
generating data (client-side)...
1000000 of 1000000 tuples (100%) done (elapsed 2.20 s, remaining 0.00 s)
vacuuming...
creating primary keys...
done in 3.52 s (drop tables 0.00 s, create tables 0.04 s, client-side generate 2.27 s, vacuum 0.30 s, primary keys 0.90 s).
postgres=# \d
              List of relations
 Schema |       Name       | Type  |  Owner   
--------+------------------+-------+----------
 public | pgbench_accounts | table | postgres
 public | pgbench_branches | table | postgres
 public | pgbench_history  | table | postgres
 public | pgbench_tellers  | table | postgres
(4 rows)

One of the most simple SQL functions is something like this:

postgres=# create or replace function get_bid_for_aid_1 () returns int
           as $$
           select bid from pgbench_accounts where aid = 1;
           $$ language SQL;
CREATE FUNCTION

This really is not rocket science but it is a good starting point to understand the concepts. PostgreSQL uses the so called dollar quoting. That means, everything between the first “$$” and the last “$$” is the body of the function. This makes it easier to write sting literals without escaping like, e.g. single or double quotes (a more detailed description can be found here). If you want to use a tag with dollar quoting you can do that as well:

postgres=# create or replace function get_bid_for_aid_1_tmp () returns int
           as $my_tag$
           select bid from pgbench_accounts where aid = 1;
           $my_tag$ language SQL;
CREATE FUNCTION

Side note: There is nothing like in “invalid object” as you might know that from Oracle. If your function body contains errors it will not be stored in the database, you’ll have to fix that first:

postgres=# create or replace function get_bid_for_aid_1_tmp_1 () returns int
           as $my_tag$
           select bid from pgbench_accountsXX where aid = 1;
           $my_tag$ language SQL;
ERROR:  relation "pgbench_accountsxx" does not exist
LINE 3: select bid from pgbench_accountsXX where aid = 1;

More information about this behavior can be found here. Coming back to our initial function: As soon as the function is stored in PostgreSQL you can reference it in your SQL queries:

postgres=# select * from get_bid_for_aid_1();
 get_bid_for_aid_1 
-------------------
                 1
(1 row)

Right now this function is quite limited as it returns the bid only for aid=1. To make that a bit more re-usable can you make use of parameters:

postgres=# drop function get_bid_for_aid_1();
DROP FUNCTION
postgres=# create or replace function get_bid_for_aid (int) returns int
           as $my_tag$
           select bid from pgbench_accounts where aid = $1;
           $my_tag$ language SQL;
CREATE FUNCTION
postgres=# select * from get_bid_for_aid(1);
 get_bid_for_aid 
-----------------
               1
(1 row)

Now the function is more flexible, as you can ask for any bid for a given aid. But there is even more we can do, to make the function more readable. Instead of specifying just the data type for the input parameter we can give the parameter a name:

postgres=# create or replace function get_bid_for_aid (pn_aid int) returns int
           as $my_tag$
           select bid from pgbench_accounts where aid = pn_aid;
           $my_tag$ language SQL;
CREATE FUNCTION
postgres=# select * from get_bid_for_aid(1);
 get_bid_for_aid 
-----------------
               1
(1 row)

Although this does not change the behavior of the function, it makes the function easier to read and we recommend to work with those named parameters instead of only specifying the data types. Especially when the body of a function becomes quite large and it uses many parameters, it saves you quite some scrolling forward and backward because you already know the data type from the parameter name (or at least you should be able to guess it).

You are not limited to use a function in the from clause of your statement. Actually you can use the function wherever you want, as long as the result is valid SQL, e.g. in the where clause:

postgres=# select count(*) 
             from pgbench_accounts 
            where bid = get_bid_for_aid (1);
 count  
--------
 100000
(1 row)

By now we used one simple select with our function, but you can also modify your data using SQL functions:

postgres=# create or replace function upd_pgbench_account_for_aid ( pn_aid int, pv_filler character ) returns void
           as $$
             update pgbench_accounts
                set filler = pv_filler
              where aid = pn_aid;
           $$ language SQL;
CREATE FUNCTION
postgres=# select * from upd_pgbench_account_for_aid (1,'dummy'); 
 upd_pgbench_account_for_aid 
-----------------------------
 
(1 row)

postgres=# select filler from pgbench_accounts where aid = 1;
                                        filler                                        
--------------------------------------------------------------------------------------
 dummy                                                                               
(1 row)
postgres=# select * from upd_pgbench_account_for_aid (pv_filler=>'dummy2',pn_aid=>1); 
 upd_pgbench_account_for_aid 
-----------------------------
 
(1 row)

postgres=# select filler from pgbench_accounts where aid = 1;
                                        filler                                        
--------------------------------------------------------------------------------------
 dummy2                                                                              
(1 row)

There are a couple of things to note here:

  • A function does not need to return anything and you can specify that by using “void” as the return type
  • You can use DML statements in SQL functions</p
  • You can use more than one parameter (100 by default)
  • You can use named parameters when calling the function as well, and if you do that, the order of the parameters does not matter. This again improves readability.

But there is more you can do, consider this simple example:

postgres=# create or replace function delete_and_add ( pn_aid int, pv_filler character ) returns int
           as $$
             delete
               from pgbench_accounts
              where aid = pn_aid;
             insert into pgbench_accounts 
                    select max(aid)+1, null, null, pv_filler
                      from pgbench_accounts
             returning aid;
           $$ language sql;
CREATE FUNCTION
postgres=# select * from delete_and_add (1,'xxxx');
 delete_and_add 
----------------
        1000001
(1 row)

postgres=# select * from pgbench_accounts where aid = 1000001;
   aid   | bid | abalance |                                        filler                                        
---------+-----+----------+--------------------------------------------------------------------------------------
 1000001 |     |          | xxxx                                                                                
(1 row)

A function is not restricted to a single statement, you can combine more than one statement, as you like and as it makes sense for you. Using the returning clause to get the new value(s) back from the function. This is actually not specific to functions but to the insert statement, but it is anyway good to know.

If you combine multiple statements into SQL function there are some rules that apply:

postgres=# create or replace function f_dummy() returns int
postgres-# as $$ 
postgres$#   select 1;
postgres$#   select 2;
postgres$#   select 3;
postgres$# $$ language sql;
CREATE FUNCTION
postgres=# select * from f_dummy();
 f_dummy 
---------
       3
(1 row)

Combining multiple statements into one function in such a way only gives you the last result. If the last statement of a SQL function does not return anything but the signature of the function specifies a return type you’ll run into an error (obviously):

postgres=# create or replace function f_dummy_2() returns int
postgres-# as $$ 
postgres$#   select 1;
postgres$#   select 2;
postgres$#   select 3;
postgres$#   insert into pgbench_accounts 
postgres$#          values ( -1,1,1,'aaaaa');
postgres$# $$ language sql;
CREATE FUNCTION
postgres=# select * from f_dummy_2();
ERROR:  return type mismatch in function declared to return integer
DETAIL:  Function's final statement must be SELECT or INSERT/UPDATE/DELETE RETURNING.
CONTEXT:  SQL function "f_dummy_2" during startup
postgres=# 


Note that transaction control is not allowed in functions, this needs to happen outside, e.g.:
postgres=# begin;
BEGIN
postgres=*# select delete_and_add ( pn_aid=>5, pv_filler=>'ddd' );
 delete_and_add 
----------------
        1000002
(1 row)

postgres=*# rollback;
ROLLBACK
postgres=# select * from pgbench_accounts where aid = 1000002;
 aid | bid | abalance | filler 
-----+-----+----------+--------
(0 rows)

The last point for today: If you want to edit/view your functions in psql there are the "\df" and "\ef" shortcuts. The first one will show you all your functions:

postgres-# \df
                                          List of functions
 Schema |            Name             | Result data type |         Argument data types         | Type 
--------+-----------------------------+------------------+-------------------------------------+------
 public | delete_and_add              | integer          | pn_aid integer, pv_filler character | func
 public | f_dumm                      | integer          |                                     | func
 public | f_dummy                     | integer          |                                     | func
 public | f_dummy_2                   | integer          |                                     | func
 public | f_dummy_3                   | void             |                                     | func
 public | get_bid_for_aid             | integer          | pn_aid integer                      | func
 public | get_bid_for_aid_1_tmp       | integer          |                                     | func
 public | get_bid_for_aid_1_tmp_1     | integer          |                                     | func
 public | upd_pgbench_account_for_aid | void             | pn_aid integer, pv_filler character | func

The second one is used to directly edit the function:

postgres=# \ef f_dummy
postgres=# CREATE OR REPLACE FUNCTION public.f_dummy()
 RETURNS integer
 LANGUAGE sql
AS $function$ 
  select 1;
  select 2;
  select 3;
  select 4;
$function$
postgres-# 
postgres-# ;
CREATE FUNCTION

In the next post we'll look into more advanced topics when it comes to SQL functions.

Cet article An introduction into server side programming in PostgreSQL – 1 – SQL functions, basics est apparu en premier sur Blog dbi services.

Viewing all 2878 articles
Browse latest View live