mysqldump cu rename

O chestie ce lipsește de multicel în mysqldump este opțiunea de a putea da un “rename” la o bază de date în fișierul de dump. Din moment ce nu pot aștepta la infinit pentru ca acest lucru să apară în versiunea standard, rămân două alternative:

  1. aplicații de dump ce știu deja să facă aceasta
  2. un pic de lucru cu sed prin fișierul de dump

Din moment ce nu am găsit decât o chestie în Python ce nu știu exact ce face, am ales a doua variantă, ceva mai rumegabilă. Să zicem că am scriptul dbdump în directorul curent, cu privilegiu de execuție:

#!/bin/sh
mysqldump -u $1 -B $2 --skip-comments -xvnp > $3.sql
sed -i "s/USE \`$2\`;/USE \`$3\`;/g" $3.sql
pbzip2 -fv $3.sql

Invocarea este simplă: ./dbdump mysql_username database_to_dump new_name

Mai departe face totul de unul singur. Dă dump, modifică numele bazei de date și face o compresie cu pbzip2 (Parallel bzip2). Se poate modifica să funcționeze cu bzip2 standard, dar pe servere multi-core și / sau multi-procesor merită efortul de a instala pbzip2.

Opțional, pentru puturoși precum subsemnatul, se mai poate trânti o linie în script ce să mute arhiva pe noul server:

scp -i transfer.pem $3.sql.bz2 transfer@example.com:$3.sql.bz2

Am pus sintaxa pentru oameni plictisiți de parole de SSH, recte subsemnatul, folosind o cheie privată. Nu e musai, dar e mai comod, în special dacă sunt mai multe baze de date de transferat.

Lookup Table în PHP

Problema implementării unui lookup table pare uneori simplă. În cazul în care se implementează ca un array indexat, iar obținerea unei valori se face prin metoda dă-mi valoarea Y de la cheia X, atunci problemele de performanță nu intervin.

M-am lovit de altă bubă, și anume implementarea unui lookup table simplu bazat pe căutare liniară construit pe baza unui pre-fetch. Ideea este simplu de implementat, și deși procesul respectiv nu rulează prea des, numărul valorilor distincte complică problema destul de mult. Într-un lookup table de 70.000 – 80.000 valori căutarea unui număr mai mare de valori durează peste jumătate de oră (nu știu exact, am oprit benchmark-ul după 30 minute) cu procesorul (sau un core) la ~100% load, pe când restul operațiilor se execută în secunde. Cam multicel pentru o căutare ce ar trebui să returneze TRUE / FALSE. Faptul că numărul de căutări este mai mare decât lookup table-ul în sine mai bate un cui în coșciugul metodei de mai sus. Se mai adaugă și faptul că deși în teorie valorie ar trebui să fie unice, datele din pre-fetch nu sunt neapărat validate în acest sens.

Pre-fetch – pseudo implementare

$haystack = array();
while($value = get_data())
{
	$haystack[] = $value;
}

Ideea este simplă. Stiva $haystack va conține toate valorile, inclusiv cele duplicat. Mă rog, pre-fetch-ul implementează ideea de “stivă” pentru că auto-indexarea cu [] are aceeași funcționalitate ca și array_push(), dar câștigă puțin la viteza de inserare a datelor. Durează câteva secunde, nu este o operație foarte complexă. De aici începe greul.

Căutare – implementare uzuală

Există tentația de a face căutarea pe baza lui in_array() sau array_search(). Ambele sunt aproximativ la fel de rapide (in_array() este cu maxim 1% mai lentă). Problema principală este căutarea liniară, fără indexare.

Dau chiar exemplele din codul meu de benchmark:

	public function bench_in_array($needle)
	{
		return in_array($needle, $this->haystack);
	}
 
	public function bench_array_search($needle)
	{
		if (array_search($needle, $this->haystack) === FALSE)
			return FALSE;
		return TRUE;
	}

Căutare – soluția optimă

Ambele soluții anterioare sunt aproximativ la fel de rapide, dar aproximativ la fel de lente. Interesul este dacă există o valoare. Duplicatele doar încurcă.

$haystack = array_flip($haystack);

Inversează valoarea cu cheile dintr-un array. Valorile duplicat dispar din moment ce un array (mă rog, devine ordered map) nu poate avea chei duplicat. Se pot aplica alte două metode distincte. Dau din nou exemplu din codul meu de benchmark:

	public function bench_array_flip_isset($needle)
	{
		return isset($this->flipped_haystack[$needle]);
	}
 
	public function bench_array_flip_array_key_exists($needle)
	{
		return array_key_exists($needle, $this->flipped_haystack);
	}

Pentru o listă de $needle scurtă (1% din $haystack, valori 100% valide), sunt aproximativ echivalente. Pentru un input 50% valid de dimensiunea $haystack, array_key_exists() este cu aproximativ 80% – 100% mai lent. Dar acum urmează partea frumoasă: căutarea se face indexat. Metoda este de cel puțin 2000 ori mai rapidă decât căutarea cu in_array() / array_search(). Adică în mod uzual căutarea de mai devreme de peste jumatate de oră durează aproximativ sub 3 secunde cu isset() și sub 5 secunde cu array_key_search(). Metodă testată pe un lookup table de 100.000 valori ce mi-a cam ucis browserul. Chrome a crăpat, Firefox a încărcat output-ul de la Codebench în vreo 3.5 GiB RAM. Atașez și imagini ce stau dovadă atrocității de mai devreme.

Parole aleatoare în Linux, “extended ASCII”

Deși în teorie problema din titlu este una rezolvată, în practică de multe ori mi-am dorit o parolă de o complexitate mai mare față de ce-mi oferă majoritatea uneltelor ce nu ies din standard ASCII. Prin metoda subsemnatului, se ia următoarea funcție și se trântește în .bashrc:

function urandompass()
{
	cat /dev/urandom | tr -dc '[\41-\176\241-\254\256-\376]' | fold -w $1 | head -n 1
}

Apelul este cretin de simplu: urandompass 12 va genera o parolă de 12 caractere. Sau cheie, am zis-o și pe asta. Funcția poate fi folosită pentru a genera chei pentru un “block cipher”.

Cu toate acestea, pentru cei cu terminalul setat pe UTF-8, caracterele dincolo de range-ul celui de-al 7-lea bit (aka cele ce intră în extended ASCII) nu vor fi printate corect. Teoria spune faptul că UTF-8 este compatibil doar cu setul ASCII, ăla standard. În prealabil este nevoie să se seteze terminalul pe un char encoding single-byte ce implementează vreo formă de extended ASCII gen ISO-8859-x, unde x = 1 este Western, x = 2 = Central European, etc. Personal prefer Western. Pentru cei cu Gnome Terminal, “Terminal » Set Character Encoding » Add or Remove …” dacă e pentru prima dată, dacă nu, atunci ar trebui să poată fi ales din meniu un encoding diferit de cel implicit. Buba este faptul că Gnome Terminal are memorie destul de proastă deci va da reset la UTF-8 dacă terminalul este închis sau se execută reset, deci operația trebuie repetată înainte de a genera orice parolă / cheie.

Acum urmează partea pentru obsedații de detalii aka cei după chipul și asemănarea subsemnatului. “Limitarea” la extended ASCII, ce oricum oferă o complexitate mai bună decât majoritatea generatoarelor, provine de fapt din tr. Un info coreutils ‘tr invocation’ în shell specifică destul de clar: “Currently `tr’ fully supports only single-byte characters.”, deci suportă multi-byte din jumătatea lui cinci. Pentru amatorii de detalii, acele range-uri din funcția mea au fost documentate, oarecum, în prealabil.

În DEC:

33-126
161-172
174-254

În OCT:

41-176
241-254
256-376

Evident, cu ochiul liber se poate vedea că tr primește octal, precum zice manualul. Pentru a le determina am încercat cu encoding ISO-8859-1 și ISO-8859-2 o aplicație ce printează tot range-ul single-byte (mă rog, fără ultimul caracter).

Pentru PHP-iști:

for ($i = 0; $i < 256; $i++)
{
	echo $i.': '.chr($i)."\n";
}

Iar pentru snobii cu C, avem alternativă 😀 :

#include 
 
int main()
{
        for (int i = 0; i < 256; i++)
        {
                printf("%d: %c\n", i, i);
        }
        return 0;
}

gcc -std=c99 -o chars chars.c

Inițial m-am inspirtat dintr-o versiune *sh de pe linuxquestions.org, dar arată cretin, nu dau paste la așa ceva. Mă cam strânge octalul de dinți. O versiune rescrisă pentru DEC junkies:

#!/bin/bash
 
chr()
{
	printf \\$(printf '%03o' $1)
}
 
i=0
while [ $i -lt 256 ]; do
	echo "$i: `chr $i`"
	let "i++"
done

Pentru cei ce preferă alte encoding-uri single-byte, dacă range-urile de mai sus nu sunt potrivite, atunci se poate rula oricând vreo aplicație de mai sus pentru a face un test de char printing.

Pentru cei ce nu s-au născut cu “DEC to OCT converter” în cap, așa ca subsemnatul, Calculator din Gnome (gcalctool) are și un mod Programming (Ctrl+P).

Căi UNIX corecte în PHP

Se dă următoarea problemă, aparent mai simplă decât zice titlul: să se determine dacă o cale de tip UNIX este corectă. Buba este faptul că această cale poate să fie către un fișier ce nu există, spre exemplu o cale de UNIX socket ce va fi creat într-un director ce va conține un pool de socket-uri. Deci funcțiile la nivel de filesystem nu vor ajuta, cel puțin nu în primă fază. În cazul  în care există vreo încercare de intuiție a scopului, da, scriu server scripting și în PHP. *sh este prea limitat pentru anumite chestii mai avansate, sau are nevoie de prea mult “boilerplate code”, pe când în Perl nu sunt încă fluent. Pentru cei ce au pierdut știrile de la ora 5, PHP nu mai este de mult un limbaj strict “web oriented”, funcționează bine merci și pentru “general purpose”.

Să revin la problema inițială. Practic se pot identifica două probleme: a) validarea unei căi inexistente – deci apelăm la regex; b) validarea directorului – deși fișierul poate să nu existe în timpul execuției de validare, directorul e musai să fie acolo. De regulă aplicațiile care creează câte un UNIX socket nu verifică aceasta și vor eșua să pornească.

Rezumat, codul arată cam așa:

$validate = preg_match('/^(\/[^\0]*?\/?)[^\0\/]+$/', $path, $matches);
if (empty($validate) === TRUE OR is_dir($matches[1]) !== TRUE OR is_dir($matches[0]) !== FALSE)
{
	// handle error here
}

Acum, discuție pe marginea textului. Acest regex satisface direct ambele probleme. Prima, este fix problema de validare. Se ține cont doar de căi absolute – acesta fiind contextul în care operez. Cu toate acestea, soluția se poate adapta ușor pentru căi relative.

Singurul capturing group din expresie preia întreaga cale, mai puțin numele fișierului. A fost făcută să funcționeze inclusiv pentru root (/). Calea capturată trebuie să fie către un director, pe când calea întreagă trebuie să nu fie un director.

Teoria căilor este simplă. O cale UNIX poate să conțină orice caracter, mai puțin nul, iar / este folosit pe post de separator de directoare, deci este rezervat acestui scop. Căile de tip //var///www//// sunt valide. Exemplul anterior va duce în //var/www unde // în general (Linux, BSD) este tot una cu /. Nu am lucrat pe sisteme unde // să fie distinct față de /, deci nu am considerat că este nevoie să tratez cazul prin expresia de mai sus.

Scanarea disk-urilor sub Linux

O să intru puţin şi în acest subiect. Nu, nu mă refer la chestii de filesystem gen fsck plus familia de prieteni, LVM şi alte avioane, ci la chestii dincolo de date, mai apropiate de fier. Nu oricine deţine un motan idiot ce să folosească un minitower pe post de suprafaţă de frânare, şi să-l dărâme, dar problemele pot apărea şi în stare naturală, “fat free, batteries not included”. Chiar dacă maşina respectivă în timpul impactului era oprita (fapt ce a oferit oportunitatea de a fi dărâmată), cele două disk-uri aveau capetele parcate, conform specificaţiilor suportă acceleraţii de până la 300G dacă sunt staţionate, wonk, wonk, wonk. Ideea simplă este faptul ca pentru a fi sigur de stabilitatea soluţiei, sunt necesare nişte măsuri ulterioare producerii unui eveniment nedorit.

a) S.M.A.R.T.

S.M.A.R.T.-ul (Self-Monitoring, Analysis, and Reporting Technology) acesta deştept, pe lângă faptul de a furniza o târlă “geeky parameters” ştie să ruleze şi nişte teste automate la nivel de disk, a căror rezultate se salvează în log-ul propriu, fapt ce duce această tehnologie dincolo de graniţele sistemului de operare.

O să presupun că disk-ul în cauză este /dev/sda, eventual faptul că scanarea se face dintr-un live edition pentru a elimina anumite probleme potenţiale. Instalarea sub Debian & Friends se face prin pachetul smartmontools. Utilitarul interesant este smartctl.

smartctl -i /dev/sda

Comanda va returna informaţiile de bază despre disk, dintre care cele mai importante pentru a continua sunt ultimele două:

SMART support is: Available – device has SMART capability.
SMART support is: Enabled

Dacă e “Available” şi e “Disabled”, atunci se poate activa şi fără reboot pentru a meşteri prin BIOS. Dacă e “Unavailable”, atunci poţi să arunci rabla de HDD de pe bloc sau intru într-o zonă a disk-urilor ce mă depăşeşte.

smartctl -s on /dev/sda

Această comandă va activa S.M.A.R.T. dacă există. Dacă nu, citeşte paragraful anterior. Dacă da, lectură plăcută în continuare.

smartctl -a /dev/sda

Va da lista aceea de “geeky parameters” despre starea HDD-ului.

SMART overall-health self-assessment test result: PASSED

Ar fi bine să indice acel PASSED. Dacă nu, ar fi cazul să îţi salvezi datele şi să înlocuieşti echipamentul. De regulă ar trebui să fie acompaniate erorile şi de ceva descrieri. In principiu informaţiile cu flag-ul “Pre-fail” ar trebui să fie OK.

Dacă totul este OK, atunci se poate trece fără grijă la auto-teste. Ca idee există vreo 3, două rapide şi unul lent. Le recomand pe cele rapide să fie primele:

smartctl -t short /dev/sda

După ce trece timpul alocat testului se va rula:

smartctl -t conveyance /dev/sda

Atenţie, testele nu pot fi rulate în paralel sau puse în coadă. Execuţia unui nou test o va anula pe cea anterioară.

Se poate trage cu ochiul la progres prin:

smartctl -c /dev/sda

Cu toate acestea, nu are vreo formă de “progress bar”, doar un procent rămas pâna la terminare, actualizat din 10% în 10%.

Lista rezultatelor stocate în log-ul HDD-ului se poate afla prin:

smartctl -l selftest /dev/sda

Ca regulă generală, log-ul stochează ultimele 20 teste. Nu îţi pierde vremea (precum subsemnatul) căutând o metodă de resetare a log-ului. O fi existând vreo soluţie, nu am dat de ea în smartctl.

=== START OF READ SMART DATA SECTION ===
SMART Self-test log structure revision number 1
Num Test_Description Status Remaining LifeTime(hours) LBA_of_first_error
# 1 Extended offline Completed without error 00% 19368 –
# 2 Conveyance offline Completed without error 00% 19366 –
# 3 Short offline Completed without error 00% 19366 –

Cam aşa arată la mine după cele 3 teste, iar acestea sunt rezultatele “ochei”. Nu am zis de ultimul, acesta durează cât căderea turnului din Pisa şi a doua venire a lui Hristos la un loc. Este direct proporţional cu dimensiunea disk-ului şi bănuiesc faptul că ţine şi de viteza acestuia. Ultimul test se rulează prin:

smartctl -t long /dev/sda

Este un moment bun să îţi faci cafeaua, dacă bei cafea, să scoţi câinele la plimbare, dacă ai vreunul, etc. O să dureze suficient de mult, în special la dimensiunile disk-urilor din zilele de astăzi.

Parametrul -c al smartctl pe lângă starea execuţiei unui test anume furnizează şi durata lor:

Short self-test routine
recommended polling time: ( 2) minutes.
Extended self-test routine
recommended polling time: ( 147) minutes.
Conveyance self-test routine
recommended polling time: ( 5) minutes.

În concluzie îţi poţi programa din timp ceva ce să umple acel gol. Din păcate numerele nu sunt precum progress bar-ul din Windows 98 unde arăta sute de ani şi termina în 5 minute sau un progress bar ce avansează dar timpul rămas creşte. Cam atâta durează testele pe bune şi pe rele. Datele de mai sus sunt de la un WD5000AAJS, adică un HDD de 500GB, 8MiB cache şi peste 19300 de ore de funcţionare. Da, are peste 2 ani de uptime, e normal din moment ce rulează în propriul server. Fratele geamăn, camarad de mirror, a murit răpus de Buleptrica, fie-i platanele uşoare. Cu această ocazie am făcut şi comemorarea soldatului căzut la datorie.

b) badblocks

badblocks este un utilitar, potenţial agresiv, menit să scormonească platanele şi să indice existenţa sau inexistenţa sectoarelor defecte. Spre exemplu eu am o mândreţe de HDD Fujitsu-Siemenes cules din notebook (ce a primit la scurt timp după achiziţionare un binemeritat upgrade la 7200 RPM). Junghiul acesta de disk corupe datele, n-are S.M.A.R.T. şi nici bad-uri. L-am scanat de câteva ori, cu mai multe utilitare, de unde concluzia că suferă de alte genuri de defecţiuni, recte electronice. Concluzia scurtă a poveştii de mai sus este faptul că badblocks nu este panaceu, identifică problemele pentru care a fost creat. Disk-urile nu trebuie să fie montate pentru a evita orice problemă, dacă au partiții. Deasemenea se recomandă prezența unei distribuții live, eventual în cazul în care aceasta nu este disponibilă, single user mode cu root montat ca read only.

badblocks -sv /dev/sda

Va rula un read only test şi arată progresul operaţiei. Durează ceva vreme, cam o oră şi ceva în cazul meu.

root@ubuntu:~# badblocks -sv /dev/sda
Checking blocks 0 to 488386583
Checking for bad blocks (read-only test): done
Pass completed, 0 bad blocks found.

Dacă vrei un test mai agresiv pentru disk, există și moduri read-write. Există doi parametri mari și lați: -n pentru read-write non-distructiv, recomandat pentru disk-uri ce au date pe ele, sau -w pentru read-write distructiv și îți poți lua gândul de la date cu -w, deci atenție. Parametrii -n și -w sunt disjuncți deci nu se vor utiliza împreună în aceeași comandă.

EOF