Na
Twitteru jsem slíbil, že napíši něco k problému, díky kterému byla na
Nahlížení do KN (dále jen Nahlížení) neaktuální data. Ostřílené Oraclisty prosím o shovívavost, protože jsem v rámci čtivosti občas něco zjednodušil.
Pokud čirou náhodou
Nahlížení neznáte, tak se ve zkratce jedná o www aplikaci, ve které můžete získat vybrané údaje týkající se vlastnictví parcel, staveb, jednotek evidovaných v katastru nemovitostí, informace o stavu řízení a další užitečné informace.
Architektura replikací
Nahlížení má svoji samostatnou databázi, do které se replikují data z interní databáze
ISKN:
Replikuje se cca 200 databázových tabulek, replikace probíhají každé 2 hodiny a jsou založeny na
Oracle Materialized View. Pro zachování logické konzistence dat a cizích klíčů (
Foreign Key) jsou všechny tabulky umístěny v jedné replikační skupině (Materialized View Group), což znamená, že se v rámci jednoho průběhu replikací musí úspěšně zreplikovat všechny tabulky v dané skupině, jinak replikace zhavarují. Takže systém vše nebo nic.
Pozn.: ve skutečnosti je replikační model košatější. Replikačních skupin tam mámě několik, včetně opačného směru, a ještě se do Nahlížení replikují data z databáze ISÚI.
Kódové stránky
Další věc, která je v tomto případě podstatná, je tzv.
character set databáze, což je
zjednodušeně řečeno kódová stránka. Protože v době vzniku ISKN nebyla v Oracle rozumná podpora pro
Unicode, tak databáze ISKN využívá
EE8ISO8859P2 se single-byte kódováním (každý znak je uložen právě v jednom byte).
Pozn 1.: české znaky se tuším vejdou do 2 byte, ale ruku do ohně za to teď nedám.
Pozn 2.: jsou i Multibyte kódování s pevnou délkou, např. UTF-32.
V Oracle se velikost sloupců pro ukládání textu (typicky
VARCHAR2) dá definovat dvěma způsoby - v
bytech nebo
znacích (Char). Velikost v bytech je maximální počet
byte, které do sloupečku půjdou uložit, velikost ve znacích udává maximální počet
znaků.
V Singlebyte databázi (
EE8ISO8859P2) je to jedno, ale v Multibyte (
AL32UTF8) je to podstatné, protože do sloupečku s definicí "VARCHAR2 9 Byte" nejde vložit řetězec "nahlížení", neboť má sice jen 9 znaků, ale jeho skutečná reprezentace má délku 12 byte (3 byte navíc jsou díky 3 českým znakům, každý je reprezentován 2 byte). Proto je v obou databázích ISKN a Nahlížení použita definice pomocí znaků (Char), což eliminuje problém s rozdílnou délkou skutečného uložení řetězců.
Vznik chyby a její příčina
V pátek 8.2.2019 nám dopoledne začal systém indikovat, že se nedaří provést replikace z ISKN do Nahlížení. Replikace končily na chybu:
ORA-12008: error in materialized view or zonemap refresh path
ORA-06512: at "SYS.DBMS_SNAPSHOT_KKXRCA", line 2952
ORA-06512: at "SYS.DBMS_SNAPSHOT_KKXRCA", line 2370
ORA-12899: value too large for column "ISKN"."AK_JINE_PRAV_VZTAHY"."POPIS_PRAVNIHO_VZTAHU" (actual: 4379, maximum:
4000)
Jednalo se o sloupec tabulky AK_JINE_PRAV_VZTAHY, do kterého se ukládá popis jiného právního vztahu a který je v obou databázích definován shodně, jak délka 4000, tak jednotky Char:
SQL> desc AK_JINE_PRAV_VZTAHY
Name Type
---------------------------------------
ID NUMBER(30)
VERZE NUMBER(30)
OPSUB_ID_K NUMBER(30)
TYPRAV_KOD VARCHAR2(4 CHAR)
POPIS_PRAVNIHO_VZTAHU VARCHAR2(4000 CHAR)
TEL_ID NUMBER(30)
(zkráceno...)
V ten okamžik jsme začali jsme tušit větší problém. Po detailním trasování jsme zjistili, že problém dělá řádek, který má v ISKN délku 3963 znaků a měl by se tedy do 4000 znaků v Nahlížení, kde jeho velikost díky českým znakům v
AL32UTF8 naskočila na 4379 byte, s rezervou vejít.
Zapojili jsme do pátrání i pracovníky podpory české pobočky Oracle a ti po dalším zkoumání přišli s tím, že Oracle sice povolí pro datový typ VARCHAR2 použít maximální velikost 4000 Char,
ale ve skutečnosti je strop na 4000 byte, bez ohledu na použití jednotky char. Je to interní limit, což je v případě vícebytového
AL32UTF8 docela průšvih.
Zrádné je to navíc v tom, že se na limit narazí,
až pokud se blíží oné maximální velikosti 4000. Pokud je sloupec definován jako "VARCHAR2 500 CHAR", tak se do něj v
AL32UTF8 bez problémů 500 českých znaků uloží, přestože zabírají například 560 byte. V našem případě byla velikost problémového záznamu v ISKN (Singlebyte
EE8ISO8859P2) 3963 znaků, ale po přenosu do Nahlížení se díky Multibyte
AL32UTF8 zvětšila na 4379 byte a narazilo se na strop 4000 byte.
Hledání řešení
Možných řešení bylo několik.
1) Změna character set databáze
Character set se v Oracle definuje při vytváření databáze a následně jíž nejde jednoduše změnit. Je potřeba vytvořit databázi novou, přeexportovat data atd. Je šílená práce, která by v případě Nahlížení trvala několik dnů (export dat, nová databáze, import dat, vše znovu nastavit atd) a musela by se ještě předem důkladně vyzkoušet.
Existují sice nějaké neoficiální postupy jak to změnit, ale to si nemůžeme u takového systému jen tak dovolit. V Oracle 12c se také objevil oficiální nástroj
Database Migration Assistant for Unicode, pomocí kterého by měl jít character set změnit, ale od jeho použití nás v produkčním prostředí odrazovali samotní pracovníci Oracle, protože je (zatím) velmi problémový a plný chyb. Vše by se muselo nejprve vyzkoušet na kopii databáze a ani tak by jistota funkčnost nebyla a navíc jsme nechtěli přecházet z Unicode zpět.
2) Změna parametru max_string_size
Další možností bylo změnit parametr
max_string_size na hodnotu "extended", což zvýší limit ze 4000 byte na 32767 byte, což by bohatě stačilo:
STARTUP UPGRADE;
ALTER SYSTEM SET max_string_size=extended;
@?/rdbms/admin/utl32k.sql
SHUTDOWN IMMEDIATE;
STARTUP;
Jenže je zde obdobný problém jako u předchozího řešení. Po dlouhém zkoumání opět samotný Oracle tento postup nedoporučil, protože součástí změny je nutnost spustit script utl32k.sql pro úpravu
Oracle Dictionary (katalog - interní tabulky popisující strukturu samotné databáze), na který v Oracle Supportu zrovna
chvála není:
Důsledkem by také byla byla fragmentace řetězců v databázi. Zároveň bychom museli změnit inicializačního parametr "compatible", který mám vliv i na chování
CBO optimalizátoru dotazů. takže to není bez důkladného testování možné. Opět tedy několik dnů testování a zkoušení, navíc s nejistým výsledkem.
Pozn.: Pokud by databáze byla vytvořena přímo s parametrem max_string_size nastaveným na extended, tak by odpadl problém s fragmentací řetězců a nemusel by se spouštět script utl32k.sql. To je ale jen teoretická možnost, protože to znamená vytvořené nové databáze, viz předchozí řešení 1). Navíc jsme od pracovníků podpory Oracle dostali následující informaci:
"ani po delším pátrání se mi nepodařilo najít reference na použití max_string_size = extended. To znamená, že nemůžeme vyloučit komplikace, bugy apod.".
3) Redefinice MVIEW AK_JINE_PRAV_VZTAHY
Jako jednoduché řešení se nabízelo vytvořit v Nahlížení znovu Materialized View pro dotčenou tabulku AK_JINE_PRAV_VZTAHY tak, že by se problémový sloupec rozdělil na dvě části, každá o velikosti 4000 CHAR. Reálně by se tedy, díky limitu, do každé části vešlo až 2000 českých znaků, celkem 4000 znaků, což je maximum, které lze v ISKN zapsat:
create mview as
select id,
substr(popis_pravniho_vztahu,1,2000) as popis_pravniho_vztahu, substr(popis_pravniho_vztahu,2001) as popis_pravniho_vztahu_1,
…. from ak_jine_prav_vztahy@iskni
To však narazilo z více důvodů. Prvním důvodem byla nutnost upravit aplikaci, která by musela interně oba sloupce zase zpět spojovat. To by nějakou dobu zabralo, ale asi by to šlo za víkend provést a otestovat (+ předělat MVIEW, i to nějakou dobu trvá, protože by se musela znovu přenést všechna data).
Druhý důvod byl závažnější. V Nahlížení je na replikované tabulky navázáno přes
databázové triggery hlídání přicházejících změn, na jejichž základě se následně provádějí další změny v grafické části (mapě). Novou definicí MVIEW bychom tedy přišli o možnost zpracovat o změny, které v tu dobu již byly nachystané v replikační frontě v ISKN a příslušná část grafiky by se musela kompletně od začátku znovu spočítat, což je časově velmi náročná akce (dny) a navíc jsme neměli vyzkoušen postup, kdy se přepočte jen jedna část.
4) Aplikační úprava problémového textu v ISKN
Toto řešení by spočívalo v tom, že by se v ISKN upravil problematický text tak, aby se i s českými znaky v Nahlížení vešel do 4000 byte. Použil by se standardní postup přes aplikaci ISKN, jako každá jiná změna v ISKN. Takže založit řízení a provést aktualizaci dat se vším, co k tomu formálně patří. To by ale znamenalo čekat až do pondělí, protože o víkendu nikoho z daného katastrálního pracoviště, které daný jiný právní vztah zapsalo, neseženeme, a do práce nenaženeme.
Následně jsme si ale uvědomili, že bychom tím problém vlastně nevyřešili. Datový model ISKN je navržen takovým způsobem, aby bylo možné zjistit stav data k jakémukoli času. Proto je možné dělat např. výpisy KN k zpětně. Funguje to tak, že pro každou entitu (jiný právní vztah, parcela, stavba, ...) existují 3 tabulky:
- AK_JINE_PRAV_VZTAHY
- AK_JINE_PRAV_VZTAHY_B
- AK_JINE_PRAV_VZTAHY_M
Tabulka AK_JINE_PRAV_VZTAHY (bez suffixu) slouží k uložení aktuálně platných dat, tzv. přítomnost.
Tabulka AK_JINE_PRAV_VZTAHY_B (budoucnost) slouží k vytváření budoucího stavu dat, který bude platit po tzv. zplatnění řízení (navrhují se v ní změny dat).
Tabulka AK_JINE_PRAV_VZTAHY_M (minulost) slouží k uložení již neplatných dat a využívá se právě pro zpětné výpisy, zjištění vývoje změn atd.
Zaměstnanec při zápisu změny navrhne v _B tabulce stav, jak by data měla vypadat. Poté proběhne kontrola jiným pracovníkem a pokud je úspěšná, tak se provede zplatnění řízení, což znamená, že se z přítomnostní tabulky (bez suffixu) data přesunou do minulostní (suffix _M) a z budoucnosti (suffix _B se přesunou do přítomnosti).
Z toho plyne, že bychom aplikační aktualizací problém s délkou textu jen přesunuli z tabulky pro přítomnost (AK_JINE_PRAV_VZTAHY) do tabulky pro minulost (AK_JINE_PRAV_VZTAHY_M).
Pozn.: Rozdělení na 3 tabulky a ne např. použitím atributu v jedné tabulce je dáno historicky. Bylo na zvoleno začátku tvorby ISKN (1997) pro optimalizaci výkonu, protože většina dotazů je na přítomnost, která oddělením od minulosti není velká.
V ISKN se následně pro přístup využívají tzv. Partitioned views, což v současné době není doporučované technika a působí problémy při migraci na vyšší verze (zhavaroval na ní pokus o přechod na Oracle 11, protože se k takovýmto view přistupuje přes opět hodně zabugovaný JPPD). Aktuálně se v rámci migrace na Oracle 12c připravuje změna založena na sloučení těchto 3 tabulek do jedné s využitím Oracle Partitioningu, což udělá docela vítr v datovém modelu a celé aplikaci, ale je to koncepční řešení.
5) Úprava problémového textu v ISKN na úrovni databáze
Protože jsme administrátoři, tak můžeme provést update záznamu na úrovni databáze. pomocí SQL příkazu. Jenže to si nemůžeme jen tak dovolit. Někdo ten záznam vytvořil, je za jeho obsah odpovědný a my bychom mu to změnili. Záznam vznikl na základě nějaké listiny, která byla doručena spolu s návrhem na vklad. Zasáhnout do zapsaného textu není tak jednoduché a mohlo by to znamenat problém, přestože bychom to provedli v dobré víře zprovoznění replikací.
Problém je v tom, že změna by se projevila nejen v Nahlížení, ale i v ISKN, ze kterého pracovníci vydávají veřejné listiny, a také ve výstupech
Dálkového přístupu, které mají také charakter veřejné listiny a jsou opatřeny
kvalifikovanou pečetí.
Další komplikací by mohlo být, pokud by někdo v rozmezí od okamžiku zápisu daného textu v ISKN do provedení této "tvrdé" opravy na úrovni databáze získal
Výpis z KN, ať už z Dálkového přístupu nebo na přepážce, případně by odebral data ve výměnném formátu nebo jako geodet přes
WS GP. Pokud bychom do textu sáhli takto natvrdo, mimo aplikační logiku, tak by se při vyhotovení výpisu ke zpětnému časovému okamžiku toulaly po světě dva výpisy z KN s platností ke stejnému časovému okamžiku, ale s jinými texty. U výměnného formátu bychom zase porušili logiku změnových vět.
Ačkoli by tedy technicky tato změna byla jednoduchá (jeden SQL příkaz), tak z formálního hlediska zase tak jednoduchá není.
Řešení
Nějak jsme situaci ale vyřešit museli. Replikace stály už více jak 24 hodin, což není dobré ani pro uživatele, ani pro systém, protože se hromadí data v replikačních frontách a následné spuštění replikací je o to delší.
Varianty 1) a 2) byly hodně rizikové. Komplikuje je také fakt, že v databázi s replikacemi se nemůžete jen tak v případě problémů vrátit v čase zpět. Kdyby se nějaký problém objevil po tom, co by úspěšně do Nahlížení proběhly replikace z ISKN nebo ISÚI, tak už by se nešlo vrátit zpět a následně zkusit jiné řešení, protože by se replikace rozsynchronizovaly (v ISKN nebo ISÚI by byla data, která by se tvářila, že už jsou zreplikovaná i v Nahlížení, ale tam by díky vrácení v čase se zpět nebyla). Data by se musela přenést všechna znovu (provést tzv.
complete refresh, který přenese vše, ne pouze fast refresh, který přenáší jen změny). Complete refresh je na dlouho a ještě komplikovaný tím, že ve zdrojové databázi (ISKN, ISÚI) nesmí v tu dobu pro aktuálně přenášení MVIEW probíhat žádné změny. Následně by se v Nahlížení musela kompletně znovu spočítat grafika, viz konec bodu 3). V neposlední řadě bychom vrácením se zpět také přišli o všechna data pořízení přímo v Nahlížení.
Varianta 3) by sice byla funkční a bez problémů, ale časově velmi náročná (dny) a řešila by jen jednu tabulku. Varianta 4) by nepomohla a tak nám po dlouhém zvažování (pořád jsme přemýšleli nad 1) a 2) ) zbyla varianta 5).
Varianta 5) také nebyla ideální, nicméně jsme se pro ni rozhodli s tím, že nám umožní vyřešit co nejrychleji aktuální problém a získáme čas na provedení koncepčního řešení.
Přemýšleli jsme, co s tím, jak změnit text, aby z toho nebyl problém. Nakonec jsme se rozhodli, že zkusíme jen odstranit diakritiku. Nejprve jsme zkontrolovali, že se odstraněním diakritiky nijak
nezmění význam textu. Pak jsme prověřili vydané výpisy, výstupy výměnného formátu atd., viz popis ve variantě 5). Naštěstí proběhl jen relativně krátký časový okamžik a ještě to bylo v pátek. Odešlo sice vyrozumění o zápisu daného řízení a s ním výpis, ale to je kontrolovaný a podchycený proces. Do karet nám také hrál fakt, že se by se "jen" odstranila diakritika, takže i kdyby byl výpis vydaný, nebyl by to takový průšvih, nicméně příjemné by to nebylo.
Museli jsme také ověřit, že daným zásahem nezpůsobíme nějakou aplikační nekonzistenci. Některé akce v ISKN jsou hlídány přes databázové triggery a jejich nové spuštění by mohlo vyvolat např. duplicity v navázaných datech atd.
Po prověření jsme požádali vedení o schválení provedené této "tvrdé" úpravy přímo v databázi a po vysvětlení situace, našich zjištění a možných řešení jsme obdrželi souhlas. Zazálohovali jsme tedy měněný záznam a provedli jeho aktualizaci jednoduchým příkazem:
update ak_jine_prav_vztahy set
popis_pravniho_vztahu=convert(popis_pravniho_vztahu,'US7ASCII')
where id=1234
Za dalších 30 minut už všechny replikace na Nahlížení úspěšně proběhly.
Závěr
Řešení tedy bylo triviální a otázka 5 sekund. Než jsme k němu ale dospěli, tak to trvalo 1.5 dne (1/2 pátku a sobotu) perné práce, protože jsme museli zkoumat možné varianty, ověřovat je atd.
Není to ale řešení trvalé a teď zvažujeme co dál. Naštěstí pravděpodobnost, že by se situace mohla opakovat velká není (délka textu byla opravdu extrémní). Do definitivního vyřešení jsme přijali i organizační opatření. Zatím váháme, zda se v rámci připravovaného cross-platform upgrade (v rámci konsolidace prostředí přechod databáze Nahlížení z Linuxu na AIX) pustit do řešení podle poznámky v bodu 2), které by bylo sice systémovější, ale s možnými problémy, nebo zda půjdeme řešením 3) a sloupec (vlastně sloupce, celkem takových s VARCHAR2(4000) máme v různých tabulkách 4) v Nahlížení rozdělíme na 2. Pokud bychom to dělali řízeně, s prázdnou replikační frontou, tak bychom nepřišli o žádné změny a nebylo by nutné přepočítávat grafickou část.
Pár zajímavostí a poznámek pod čarou
1) Kdybychom nebyli "pokrokoví" a databází Nahlížení udělali také v Singlebyte kódování a nikoli Unicode s proměnnou délkou, tak jsme si tyto problémy ušetřili, obzvláště v kombinaci s replikacemi databází databází s různým character set.
2) Pokud vás zajímá, na čem ztroskotal náš předchozí pokus o upgrade ISKN na Oracle 11g nebo problematika Partiton Views zmíněná v řešení 3) s bugy
JPPD, tak si přečtete naši
podrobnou analýzu. Výživné a velmi poučné čtení.
3) Chyba "ORA-12899: value too large for column" se při replikacích začala objevovat až po nedávné migraci (prosinec 2018) databáze Nahlížení na Oracle 12.2. Ten samý problém ale byl i v předchozích verzích, jenže Oracle při replikacích chybu nehlásil, ale data prostě
bez jakéhokoli upozornění ořezal, což je ještě horší.
4) Tvrdým limitem 4000 byte pro VARCHAR2 byli překvapení i samotní pracovníci podpory Oracle. Nemohli ve svých interních systémech dohledat hlášený obdobný problém, což zase překvapilo nás. Zřejmě se kombinace tak dlouhých VARCHAR2 položek v kombinaci s proměnnou délkou kódování a znaky, které se nevejdou do 1 byte, tak často neobjevuje a používá se spíše
CLOB.
5) Chování Oracle při použití Multibyte s proměnnou délkou (Nahlížení,
AL32UTF8) je opravdu zákeřné, příklad:
(funkce
length(rpad('X',4000,'X')) doplní řetězec "X" až do délky 4000 znaků dalšími "X")
SQL> select length(rpad('X',4000,'X')) as delka from dual;
DELKA
--------------------------
4000 (očekávaný výsledek)
Když místo US znaku X použijete znak český, tak najednou dostanete pouze 2000 znaků (reprezentovaných 4000 byte):
SQL> select length(rpad('Ň',4000,'Ň')) as delka from dual;
DELKA
--------------------------
2000
Pro mě neočekávaný výsledek, který je daný tím, že interní reprezentace v rámci implicitního kurzoru pro 2000 českých znaků je v Multibyte 4000 byte a Oracle to prostě na 2000 znacích bez uzardění ořízne. Když ten samý dotaz spustíte v Singlebyte databázi (ISKN,
EE8ISO8859P2), tak je vše OK:
SQL> select length(rpad('Ň',4000,'Ň')) as delka from dual;
DELKA
--------------------------
4000
Dotaz vypadá nevinně a různý výsledek v různých databázích docela zaskočí. Na takových věcí pak narazíte spousty, pokud už víte, co hledat.
6) V dokumentaci Oracle se píše:
The VARCHAR2 datatype stores variable-length character strings. When you create a table with a VARCHAR2 column, you specify a maximum string length (in bytes or characters) between 1 and 4000 bytes for the VARCHAR2 column.
Je to zavádějící a jak jsem psal, překvapilo to i samotné pracovníky Oracle. Tohle vám prostě nedocvakne, když o tom nevíte. O nutnosti používat v Multibyte jednotky Char jsme věděli (narazili jsme na to v replikacích již dříve u kratších polí), ale o tvrdém limitu 4000 byte při použití Char ne. Chtělo by to velké červené varování.
7) Z mého textu mohlo zdát, že Oracle je ten nejvíce zabugovaný SW na světě a je prakticky nepoužitelný. Přesto si myslím, že Oracle je v oblastí SQL databází špička, daleko před ostatními (např. technologie
RAC), a jinou databází bychom si nijak nepolepšili. Bugy jsou všude a u nás většinou bohužel narazíme na nějaké speciality, které se jinde neřeší. Navíc český support se opravdu snaží. Ale je pravda, že si Oracle jak za licence, tak služby supportu, nechá řádně zaplatit.
8) K druhé větě v odstavci
Řešení - někteří uživatelé jsou tak nedočkaví, že se občas stane situace, že náš zaměstnanec zplatní řízení a než připraví a odešle účastníkům řízení
vyrozumění o zápisu (to jde i Datovou schránkou), tak se data stihnou zreplikovat na Nahlížení (replikace mohou proběhnout třeba za 5 minut), kde si uživatelé hlídají jejich řízení/LV a volají našemu zaměstnanci, kdy si mohou přijít pro výsledek atd. :-)