printf(" SaltwaterC ");

/dev/urandom

  Archive for the ‘Programare’ Category


Amazon EC2 Micro pentru un cluster memcached

Am stat și am rumegat puțin problema unui cluster memcached ce s-ar putea face peste Amazon EC2. Patru instanțe t1.micro costă mai puțin decât o instanță m1.small ($0.02/h vs $0.085/h, la preț on-demand). În plus, pentru a avea o oarecare disponibilitate a unui cluster de caching, este nevoie de minim două instanțe din moment ce în mod cert în producție vor exista momente în care există mentenanță planificată. Iar la capitolul capacitate, patru instanțe t1.micro depășesc o instanță m1.small. t1.micro are 613MiB RAM dintre care se pot utiliza să zicem vreo 512MiB pentru memcached, pe când dintre cei 1.7GiB RAM disponibili pe o m1.small, sunt utilizabili vreo 1.6GiB. Patru instanțe t1.micro oferă așadar o capacitate mai mare unui cluster memcached decât o singură instanță m1.small. Iar rezervarea pe 3 ani pentru t1.micro costă $84. Deci nu e gaură în cer sau la buzunar.

Cu toate acestea, pentru început, dacă aplicația nu are cerințe mari (iar în general nu sunt pentru multe aplicații), două instanțe t1.micro pentru un cluster minimal sunt suficiente. Iar dacă este nevoie de mai multă capacitate, se poate adăuga oricând o nouă instanță, deci un “building block” poate fi prestabilit.

Acum nu vreau ca lumea să privească memcached ca pe un panaceu. Au pornit mulți de la ideea că a face caching în memcached este eficient în orice mod de utilizare. Nu este. Pentru o singură instanță, un “file based caching” este mult mai eficient. Da, se stochează pe disk, da disk-ul este mai lent decât memoria, dar acel kernel cache nu doarme degeaba. Acolo se duce memoria pe care bobocii cred că le-o mănâncă Linux-ul. Până și acele API-uri de caching din extensiile de PHP ce fac opcode caching sunt mai eficiente decât memcached, deși mai puțin eficiente decât acel PHP API cu condiția ca mașina care rulează aplicația să aibă suficientă memorie disponibilă pentru a face și kernel cache. Da, memcached pe local e mai ineficient pentru că IPC-ul presupune overhead. Au fost concluziile simple pe care le-am tras cu un benchmark simplu scris folosind Codebench. Nu mai am sursele, dar cred că o să-l reproduc curând și o să public rezultatele, pentru a încheia cu mitul memcached = panaceu.

Este totuși util pentru instanțe ce sunt puse în spatele unui ELB, Auto Scaling are grijă de instanțele EC2 ce rulează în spatele acestui ELB, și este nevoie de un cache consistent, distribuit. Un cache local pentru un cluster de mașini ce rulează o aplicație poate pune probleme de coerență și complică mult situația dacă aceasta rulează pe un cluster. Deși e mai lent ca o soluție locală, lentoarea unui RDBMS este în general mai mare, deci aici intervine modul potrivit de utilizare al memcached. S-ar putea utiliza și instanțele pe care rulează efectiv aplicația, dar în acest caz, adăugarea de capacitate devine dificilă. Auto Scaling (folosind strict uneltele Amazon EC2) devine imposibil. Deci, are sens un cluster extern mașinilor ce rulează aplicația. Și nu, nu vreau să aud de cluster file system. Introduce mai multe probleme decât rezolvă și spun asta pe cuvând de cercetaș. Iar Auto Scaling nu este musai să fie acel nume pompos de marketing ce îi face pe unii să creadă că au scăpat de “capacity planning” sau că e o soluție auto-magică la problemele de scalabilitate. Cel mai civilizat mod de utilizare este acela de a menține un număr minim de instanțe de un anume tip să fie “up and running”.

Să dau și o listă de cerințe.

  1. OS cu suport îndelungat
  2. 32-bit
  3. Configurație minimală
  4. Mod de adresare fix

Să le iau pe rând.

  1. Ubuntu 10.04 LTS este potrivit obiectivului stabilit. Canonical oferă 5 ani de actualizări pentru versiunile de server LTS . În plus, Canonical oferă AMI oficiale pe care le menține, deci pe lângă base system și userland, sunt asigurate și actualizările pentru kernel.
  2. Pentru că 64-bit nu aduce un plus de performanță, doar mai mult “overhead” pentru memorie. Deci nu merită efortul de a construi o imagine pentru 64-bit. De la t1.micro pe 64-bit se poate face upgrade doar la instanță m1.large, deci costurile cresc pe măsură iar aplicația trebuie să fie una destul de mare ca să necesite asemenea capacități de memorie pentru un cluster de caching. În același timp, dacă management-ul unui cluster t1.small pe 32-bit devine prea complicat (deși nu ar trebui să fie cu uneltele potrivite), se poate trece la m1.small, deși se primește mai puțin “bang for the buck”.
  3. Deși imaginile Canonical sunt destul de subțiri (ideea de JeOS), au totuși prea multe servicii ce rulează în mod implicit și rulează degeaba. În plus, deși în prezent nu mai vin cu un root EBS de 15GiB (de când s-a introdus acel “free tier”), au totuși un root EBS de 8GiB. La exemplul meu inițial cu 4 instanțe, să zicem că 6GiB sunt aproximativ degeaba. 2GiB pentru o instanță memcached sunt suficienți. Într-un an, acei 24GiB de EBS storage nefolosiți practic, se traduc într-un cost suplimentar de $28.8. Nu este mult ținând cont de prețul altor servicii, dar se adună acolo iar cum Amazon au obiceiul să pună taxă pe absolut cel mai mic detaliu, optimizez în această direcție. Pentru că pot.
  4. Amazon EC2 în mod implicit are un mod de adresare a mașinilor folosind adrese ce se pot schimba. Pentru a elimina reconfigurarea aplicației, Elastic IP vine în ajutor, dar nu în modul intuit de oricine. Modul implicit presupune costuri suplimentare ce se pot rezolva.

1+2. Ubuntu 10.04 LTS 32-bit

Pe alestic se găsesc multe resurse legate de Ubuntu pe EC2. Partea interesantă este acel tabel de pe prima pagină ce listează imaginile oficiale ale Canonical în funcție de zonă, arhitectură și root device. Restul e can-can. O instanță poate fi adusă sus în maxim câteva minute, deci nu o să insist cu partea ușoară a lucrurilor.

3. Configurație minimală

Din moment ce nu insist asupra lucrurilor ușoare, este evident că o să insist asupra celor mai puțin evidente la o primă vedere. Pentru început, subțierea numărului de servicii. Se ia scriptul de mai jos, se trântește într-un *.sh și se execută.

#!/bin/bash
 
if [ $(id -u) -ne 0 ]
then
	echo "Please run me as root."
	exit 1
fi
 
apt-get -y remove --purge consolekit
rm -rf /var/log/ConsoleKit
apt-get -y autoremove --purge
 
function disable_service
{
	if [ -f /etc/init/$1.conf ]
	then
		echo "Disabling $1"
		stop $1
		mv /etc/init/$1.conf /etc/init/$1.conf.noexec
	else
		echo "$1 is already disabled"
	fi
}
 
function map
{
	if [ $# -le 1 ]
	then
		return
	else
		local f=$1
		local x=$2
		shift 2
		local xs=$@
		$f $x
		map "$f" $xs
	fi
}
 
map disable_service atd cron dbus rsyslog tty2 tty3 tty4 tty5 tty6
 
cpucount=$(grep -c 'model name' /proc/cpuinfo)
if [ $cpucount -ne 1 ]
then
	echo "You have a multicore system, irqbalance is fine"
else
	disable_service irqbalance
fi

Maniacii ar putea stoarce mai mult cu mingetty vs getty, busybox vs Core Utils și Dropbear în loc de OpenSSH, dar personal nu-mi complic viața atât de mult pentru câțiva megi de RAM. Acestea fiind spuse, se poate trece la faza următoare.

Memoria swap nu există pe t1.micro, dar se poate pune într-un fișier. Personal am optat pentru 256MiB. În mod normal nu ar trebui să se atingă pentru un memcached de 512MiB, dar e mai sigur așa decât un crash de sistem pe spinarea unui OOM error.

dd if=/dev/zero of=/swap bs=1M count=256
mkswap /swap
swapon /swap

Pentru a-l pune la boot, trebuie adăugată următoarea linie în coada lui /etc/fstab:

/swap none swap sw 0 0

Se poate șterge linia aia ce ar trebui să monteze “ceva” în /mnt. t1.micro nu are “ephemeral storage” spre deosebire de instanțele mai mari. Pentru mai multe detalii legate de swap, se poate apela cu încredere la documentație, deși înafară de vm.swappiness nu prea văd ce s-ar putea mânări prin configurare.

Instalarea memcached care în zona ușoară: apt-get install memcached. Configurarea presupune editarea lui /etc/memcached.conf și anume -m 64 modificat în -m 512, comentată linia cu -l 127.0.0.1 și restart la serviciu cu /etc/init.d/memcached restart. Cei îngrijorați de faptul că se ascultă pe interfața “externă” pot să stea liniștiți atâta timp cât n-au un Security Group configurat cu picioarele iar 11211 nu e port public.

Recomand instalarea lui ntp pentru a minimiza problemele legate de timp în sistem. apt-get install ntp și se poate uita de faptul că a fost instalat.

Opțional se poate instala și monit pentru a avea un polițai ce să țină un ochi pe celelalte servicii. Personal îl folosesc cu următoarea configurare:

set daemon 60 with start delay 120
set eventqueue basedir /var/monit slots 256
set logfile /var/log/monit.log
include /etc/monit/conf.d/*

Nu mi-am bătut capul cu email reporting aka să instalez și un MTA (sendmail sau echivalent). De fapt totul e minimal. În conf.d am următoarele:

 # fișierul memcached
check process memcached with pidfile /var/run/memcached.pid
start = "/etc/init.d/memcached start"
stop = "/etc/init.d/memcached stop"
if failed host 127.0.0.1 port 11211 type TCP then restart
if 5 restarts within 5 cycles then timeout
 
# fișierul ntp
check process ntpd with pidfile /var/run/ntpd.pid
start program = "/etc/init.d/ntp start"
stop  program = "/etc/init.d/ntp stop"
if failed host 127.0.0.1 port 123 type UDP then restart
if 5 restarts within 5 cycles then timeout
 
# fișierul ssh
check process sshd with pidfile /var/run/sshd.pid
start program "/etc/init.d/ssh start"
stop program "/etc/init.d/ssh stop"
if failed port 22 protocol SSH then restart
if 5 restarts within 5 cycles then timeout

Acum ajung la partea cu disk space-ul minimal pentru root EBS. Amazon cere bani pentru spațiul rezervat, nu pentru spațiul utilizat. Un sistem complet instalat, cu swap de 256MiB și cache apt gol (apt-get clean) ocupă în jur de 894MiB. Am zis 2GiB pentru siguranță și eventualele upgrade-uri mai mari. Deși nu e “rocket science”, este totuși o procedură puțin mai dificilă față de majoritatea operațiilor. Recomand salvarea setup-ului de până acum ca AMI pentru a avea un backup în caz de erori și a nu fi nevoit să repeți toți pașii de mai sus. Personal, folosesc următoarea metodă:

aminame="image-name"
volumeid="vol-volume-id"
snapshotid=$(ec2-create-snapshot "$volumeid" | cut -f2)
kernel="aki-kernel-id" # Spre exemplu pentru 2.6.32-311 este aki-407d9529
 
while ec2-describe-snapshots "$snapshotid" | grep -q pending
do
	echo -n .
	sleep 5
done
 
ec2-register --architecture i386 --name "$aminame" --snapshot "$snapshotid" --kernel "$kernel"

Metoda de mai sus se va folosi și pentru a salva AMI-ul final. Deci nu o să o mai repet.

Partea de resize presupune o nouă instanță la care se va atașa root EBS-ul instanței folosite până în prezent din moment ce nu se poate edita un filesystem montat. Deci se oprește instanța pe care s-a lucrat până acum. EBS-ul se detașează tot din EC2 API Tools:

auxinstanceid='i-instance-id-aux'
ec2-stop-instances $instanceid
while ! ec2-detach-volume $volumeid; do sleep 5; done
ec2-attach-volume --instance $auxinstanceid --device /dev/sdf $volumeid

$auxinstance – așa am botezat noua instață auxiliară pe care se va face resize-ul. Se purcede la micșorarea filesystem-ului existent:

fsck -n /dev/sdf # device-ul montat pe $auxinstance, dacă e totul OK se merge mai departe
tune2fs -O ^has_journal /dev/sdf # se oprește jurnalul - practic partiția devine una ext2
e2fsck -f /dev/sdf
resize2fs /dev/sdf 1500M # aici nu are dimensiunea finală, dar am pus mai mic ca să nu am probleme mai încolo
fsck -n /dev/sdf
tune2fs -j /dev/sdf

După aceasta am creat noul disk de 2GiB pe care să-l atașez lui $auxinstance ca /dev/sdg. De îndată ce noul disk este montat, se poate copia de pe vechiul disk:

dd if=/dev/sdf ibs=16M of=/dev/sdg obs=16M count=128
resize2fs -p /dev/sdg
e2fsck -f /dev/sdg

Este nevoie de aceasta deoarece Amazon nu permite să se creeze un disk mai mic decât snapshot-ul de bază, deci metoda directă: snapshot – create new smaller disk – nu convine. Ambele volume se pot detașa de instanța auxiliară. Noul volum mai mic se va atașa la vechea instanță de bază. Dacă totul e OK, și ar trebui să fie dacă la acele (e2)fsck nu a dat erori, atunci o să pornească instanța pe disk-ul mai mic. Se poate verifica ușor cu df -h. După aceasta se poate creea un AMI pornind de la această instanță și folosind metoda descrisă anterior pentru salvarea unui AMI intermediar.

4. Adresare fixă prin rețeaua internă

Elastic IP-ul este prin definiție public. Adică cel puțin peste tot apare ca fiind un IP public. Din moment ce protocolul memcached nu are autentificare în mod implicit, complică problema și prin faptul că trebuie făcut un firewall pe fiecare instanță ce să țină cont de acest aspect, problemă ce poate pune bețe în roate unui auto-scaling (sau să-l complice foarte mult). În același timp, memcached folosește client side clustering, deci ordinea de adresare a serverelor și adresarea fixă este esențială.

Amazon ar putea oferi un subdomeniu fix ce ține de instanță nu de adresă. Chestia asta ar rezolva problema cel puțin parțial (depinde de context). Cu toate acestea (încă?) n-am văzut astfel de implementare. Ar reduce necesitățile de Elastic IP ce au un număr limitat de 5 per cont, după care trebuie cerută creșterea acestei limite. Cererea nu costă, dar este enervant atunci când se epuizează spațiul de adrese disponibil.

De ce am pomenit de DNS? Simplu. Adresele acelea de Amazon, spre exemplu 1.1.1.1 au atașate un subdomeniu: ec2-1-1-1-1.compute-1.amazonaws.com. Rezolvat din afara rețelei Amazon, acesta va returna 1.1.1.1. Magia se întâmplă în interiorul rețelei Amazon, unde rezolvarea lui ec2-1-1-1-1.compute-1.amazonaws.com se face către adresa internă a serverului, spre exemplu 10.0.0.1. Elastic IP se supune acestei reguli. Folosirea unui Security Group reduce pacostea de a configura dinamic un firewall deoarece mașinile parte a unui grup pot comunica nerestricționat între ele folosind rețeaua internă, cu condiția ca acel grup să conțină o referință către sine. Iar traficul intern, spre deosebire de cel extern, nu este contorizat și facturat. Deci, clientul memcached, dacă se conectează la două mașini, spre exemplu 1.1.1.1 și 1.1.1.2 va folosi adresele ec2-1-1-1-1.compute-1.amazonaws.com și ec2-1-1-1-2.compute-1.amazonaws.com ce vor rezolva adresa internă. Dacă o mașină este oprită, IP-ul intern alocat se va schimba. Deasemenea acest lucru se va întâmpla și dacă se înlocuiește instanța din spatele acelui Elastic IP. Ori, folosind strategia anterioară, aplicația client nu va trebui modificată cu nimic. Totul se face transparent, în spatele scenei.

Câteva referințe:

http://www.howtoforge.com/linux_resizing_ext3_partitions
http://alestic.com/2010/02/ec2-resize-running-ebs-root
http://serverfault.com/questions/183552/shrinking-amazon-ebs-volume-size

Instalarea phpUnit sub Windows 7 / Zend Server Community Edition

Ca de obicei, PHP sub Windows reușește prin varii metode să dea cu mucii în fasole. Anumite chestii ar trebui să meargă OOTB în teorie. În practică apar dificultăți.

În primul rând am golit directorul C:\Program Files (x86)\Zend\ZendServer\bin\PEAR de toate mizeriile mai puțin go-pear.phar, din varii motive, oricum era un “fresh install” de Zend Server CE. C:\Program Files pentru cei ce încă trag cu dinții de 32-bit. După care am purces, folosind un shell rulat sub Administrator:

cd C:\Program Files (x86)\Zend\ZendServer\bin
php -dphar.require_hash=0 PEAR\go-pear.phar
pear channel-update pear.php.net
pear upgrade PEAR
pear channel-discover pear.phpunit.de
pear channel-discover components.ez.no
pear channel-discover pear.symfony-project.com
pear install --alldeps phpunit/PHPUnit

PEAR_ENV.reg se generează în C:\Program Files (x86)\Zend\ZendServer\bin. Se adaugă în registru la un simplu dublu click. Rulat sub un simplu user:

C:\Users\SaltwaterC>phpunit
PHPUnit 3.5.11 by Sebastian Bergmann.
 
Usage: phpunit [switches] UnitTest [UnitTest.php]
[...]

Restul e can-can.

Update / Disclaimer: în cazul în care n-am fost suficient de explicit, este vorba de Zend Server Community Edition ca distribuție pe post de purtător de vină. Nu este vorba de PHP / Windows. Dar cum Zend susțin faptul lor că distribuția lor de PHP este “certificată”, “production & development ready”, ca tot omul mă aștept ca anumită funcționalitate să funcționeze fără flotări logice.

Eroarea ține de faptul că o arhivă PHAR are o semnătură ce e fie invalidă, fie lipsește în cazul distribuției respective iar PHP dă un warning și refuză instalarea. Motiv pentru care se aplică la runtime flag-ul phar.require_hash=0. Nu știu dacă e lipsă sau e invalidă, puțin mă interesează. Faptul că installer-ul PEAR din distribuție este vechi a fost doar un bonus. 2008-05-17 – în changelog această dată apare alături de versiunea 1.7.2 pe care o dă Zend “de bună” în ultima versiune, 5.0.4. Scuza că e un pachet comunitar iar dacă nu-mi convine pot să dau $xxxx pentru versiunea comercială nu ține din moment ce Zend se laudă a fi “The PHP Company”. Da, mă deranjează teribil ipocrizia din industria software.

Personal am încetat să mai discut cu cei de la Zend probleme ce țin de anumite alegeri cretine pe care le fac în distribuția lor. Sunt grei de cap și încăpățânați. Singura “victorie” din partea mea a fost includerea unei valori predefinite pentru sendmail_path în distribuția standard. Vezi acest post de prin 2009. Aceasta după ce a trebuit să depun efoturi ca să le explic faptul că /usr/sbin/sendmail este standard pentru toate distribuțiile majore și să mă lupt cu astfel de răspunsuri, după ce ei susțineau sus și tare că nu e nici un bug de configurație faptul că sendmail_path e invalid, deși există n-implementări de MTA ce oferă compatibilitate cu sendmail (personal folosesc postfix, deși postfix pe lângă MTA are și un server SMTP). Ca după aceea să o rezolve până la urmă și să pună un articol pe KB. Îmi este greu să urmăresc logica de a rezolva o problemă pe care inițial susțineai că nu o ai în loc să te ridici de pe curul puturos și să verfici.

Singurul motiv pentru care încă tolerez această distribuție este faptul că oferă pachete binare decente precum update-uri la versiunea de PHP, spre deosebire de anumite distribuții Linux ce au version lock, partea de backports e aproximativ inexistentă, iar un 3rd party oferă mai puțină încredere. Iar ca sysadmin am și altceva de făcut înafară de presupunerea că aș sta să-mi compilez singur serviciile. O fac atunci când nu am alternativă. Chiar și versiunea de PHP din Ubuntu Lucid este jenant configurată, are bug-uri prin ini-uri, etc. Dar măcar ăia nu emit pretenții de distribuție “certificată”.

Având în vedere experiențele proaste cu Zend, slabe șanse să le cumpăr vreodată vreun produs sau să-mi iau “certificare” de la ei din moment ce distribuțiile lor “certificate” nu îndeplinesc un minim nivel al bunului simț.

phbench – PHP Benchmark Test

Obișnuiam să testez varii implementări de stive PHP. Îmi doream un test ceva mai complex ce să vizeze strict PHP. Din nefericire majoritatea chestiilor pe care le-am găsit erau fie prea simple (testau doar operații matematice în bună parte), fie erau prea dificil de instalat. Iar chestiile dificil de instalat nu sunt de mine atunci când vine vorba de munci de astea simple.

La un moment dat am scris phbench, o astfel de soluție, dar rezultatele erau prezentate într-o formă destul de dificilă pentru teste ceva mai îndelungate. Din fericire, mi-a trecut lenea și am rescris complet aplicația, păstrând ideea inițială. Iar acum mi-a trecut lenea de a anunța faptul că de aproape o lună am pus sus noua versiune, de care nu știe nici naiba pentru că nu am mai vorbit de phbench de o vreme. Și am rescris-o folosind Kohana Framework. Iar de această dată este mult mai utilizabilă din moment ce are o interfață AJAX iar rezultatele sunt detaliate și centralizate de către aplicația în sine.

Spre deosebire de versiunea anterioară, lipsește interfața CLI, deși nu știu dacă o voi mai implementa vreodată. De cele mai multe ori am fost pus în situația să-mi pese de cum se mișcă PHP în server side și aproape deloc de cum se mișcă PHP pentru o aplicație CLI.

Printre altele, este și un mini-framework pentru a scrie alte teste. Un nou test este practic o clasă ce respectă niște reguli prestabilite, detaliate în README. Se integrează direct în suportul existent, fără a face configurări de interfațare. phbench folosește reflection pentru aceasta. Sunt deschis la sugestii pentru a include noi teste în distribuția oficială. Cu toate acestea, am evitat teste ce folosesc API-uri externe (gen MySQL, cURL) din moment ce sunt influențate de serverul la care se conectează. Eventual voi face o categorie de teste opționale ce se vor putea include în teste ce pot fi făcute relevante (exemplu: să se conecteze la același server MySQL în teste). Printre altele, deși proiectul nu este o prioritate a mea, voi implementa chestii minore (și mai am vreo 2-3 idei pe rol pentru interfață).

Download.

Și un screenshot:

VirtualBox ca serviciu de sistem

M-am lovit în mod repetitiv de problema rulării VirtualBox ca serviciu, dar până acum niciodată nu am fost mulțumit de soluție. Majoritatea scripturilor de init pe care le găseam aiurea în tramvai pe Internet făceau parte din categoria acelor scripturi ce necesită un serviciu per mașină virtuală. Iar cum subsemnatul e comod din fire și n-are chef să facă clasicul:

cp vboxservice-template vboxservice-machine-name
nano vboxservice-machine-name
# write all that machine dependent junk, save
update-rc.d vboxservice-machine-name defaults

a inventat o soluție. Posibil nu cea mai bună, dar simplu de utilizat, implementat, extins. Stau și mă gândesc cum naiba geniile de pe “Teh Internets” n-au fost în stare să găsească o soluție mai rumegabilă până acum. Iar prin rumegabilă mă refer să abstractizeze un proces repetitiv iar ca parametru să fie un ID de mașină. Deci am luat o copie de /etc/init.d/skeleton și am purces la modificat. Ce-a ieșit, e downloadabil aici.

Acest articol folosește și pe post de manual, deși nu e mare scofală.

Pentru instalare se ia scriptul de mai sus, se trântește în /etc/init.d/ și se activează cu:

chmod +x /etc/init.d/vbox-machines
update-rc.d vbox-machines defaults

Metoda de mai sus este valabilă pentru Debian & friends. Cu toate că bâzâie ceva mizerii despre LSB, n-am încercat pe alte nații de distribuții, deci nu garantez că funcționează, chiar dacă nu mai este nici o referință la start-stop-daemon, tipic pentru Debian și gașca.

În mod implicit utilizatorul sub care rulează mașinile e root. Se poate modifica prin metoda standard de a edita parametrii din scripturile de Debian init:

nano /etc/default/vbox-machines

și se adaugă

CONTROL_USER=virtualbox

unde virtualbox este utilizatorul sub care se intenționează să se lanseze mașina virtuală. Evident, nu trebuie să fie acesta. Aceasta s-a implementat pentru a minimiza editările la nivel de script de init și pentru a avea bucuria sufletească a faptului de a fi câtuși de cât “standards compliant”.

Cam atât pe partea de instalare de serviciu. Dacă nu există directorul /etc/vbox.d/, o să aibă el grijă să zbiere. Pentru a-l face funcțional, se pun în directorul anterior fișiere cu nume identic cu cel al ID-urilor de mașină virtuală. Practic acel ID e un UUID ce se găsește în XML-ul de configurare al mașinii, fișier ce se găsește pe undeva pe aici:

/home/virtualbox/.VirtualBox/Machines/Machine Name/Machine Name.xml

în funcție de utilizatorul folosit și mașina creată (exemplul de aici: Machine Name).

Tehnic vorbind, în abstract s-ar putea folosi și numele mașinii în loc de UUID, dar dacă are spații în nume, gen “Retard OS RTM VM”, atunci este problematic, cu toate că sintaxa VBoxManage acceptă și numele mașinii în loc de UUID, iar serviciul subsemnatului pescuiește numele de pe disk și nu face mofturi la formă. Face mofturi la faptul că un spațiu primește escaping pe undeva pe acolo și la execuția lui VBoxManage for fi mizerii prin comandă. Mi-e mult prea lene să-l fac panaceu. Oricum acel UUID se găsește pe linia numărul 3 din fișier, iar un simplu:

touch /etc/vbox.d/uuid-of-the-machine

activează pornirea / oprirea automată. În plus, phpVirtualBox este un proiect genial, recomandat chiar de către Oracle pe pagina lui VirtualBox iar subsemnatul intenționează să creeze / șteargă acele fișiere ce se integrează cu serviciul meu direct din interfața acestuia. Mai bine fac scurtă la click decât scurtă la taste. Îmi mai trebuie doar chef să fac un patch pentru phpVirtualBox.

PS: ar mai fi de adăugat un mic detaliu elementar și anume faptul că mașina nu e oprită la stop, ci se folosește “savestate”, mai mult din motive ce țin de VirtualBox și mai puțin de opțiune personală. Pentru un shutdown, se recomandă metoda standard a sistemului de operare ce este găzduit.

Ca actualizare de ultimă oră: este nevoie ca un serviciu de VirtualBox să ruleze în prealabil. Datorită faptului că aveam un init script pentru VirtualBox web service, scriptul meu funcționa. În pachetul standard de la Oracle, vine un serviciu vboxdrv. Pentru funcționare corectă, serviciul meu trebuie să ruleze după acesta, deci fie scriptului din init.d i se dă un nume ce în ordinea lexicografică este după vboxdrv sau se mânărește prin update-rc.d sau scripturi de startup în funcție de runlevel / prioritate la pornire.

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.


Designed by: studentzFM | Theme made for free by: Casino, punkzFM, and mygroovez | Heavily modified by SaltwaterC

Switch to our mobile site