Pokazywanie postów oznaczonych etykietą tips and tricks. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą tips and tricks. Pokaż wszystkie posty

sobota, 25 lutego 2012

Twarde fluszowanie buforów

Dawno nie było "twardego" wpisu...

Gdy tworzymy aplikację i kod zapisujący do pliku, zwykle traktujemy operację zapisu jako wykonaną gdy sterowanie w kodzie przejdzie do kolejnych instrukcji. Zazwyczaj to fałszywe poczucie bezpieczeństwa w niczym nie przeszkadza. Do czasu, gdy pojawi się sytuacja awaryjna: aplikacja, system operacyjny, dyski lub zasilanie padną.

Odkrywamy jednak operację typu (f)flush() i czujemy się lepiej, bufory mamy opróżnione. Bardzo miło, ale nasza sytuacja w razie awarii systemu nie jest wiele lepsza. Jedyne co osiągnęliśmy, to wypchnięcie danych z jednych buforów do drugich (OS). Dane nadal nie są utrwalone. Jeśli oprogramowanie zapisuje krytyczne dane, realizuje jakąś transakcję, to kiepsko to wygląda.

Aby je utrwalić na nośnikach, musimy sięgnąć po fsync(). Bardzo ładnie jest to wyjaśnione tutaj.

Oczywiście, one nadal nie muszą być utrwalone. Jest jeszcze sterownik systemu plików, sterownik urządzenia blokowego, bufor w macierzy/kontrolerze dysków i w samym dysku! Ale jeśli sterowniki, kontroler dysków i firmware dysku są OK, no to w końcu czujemy się lepiej. Problem w tym, że nie zawsze są OK.

Poza tym, nawet jeśli są OK, awaria dysku/zasilania może przyjść w trakcie zapisywania danych na dysk i zapiszą się tylko częściowo i nie w tej kolejności, w której były zlecone. Implikacje mogą być bardzo daleko idące i dotyczą głównie implementacji: systemów plików, baz danych, systemów transakcyjnych, systemów kolejkowych czy nawet serwera pocztowego. Na pewno wszędzie, gdzie liczy się ACID.

Niektóre systemy udostępniają dodatkowe możliwość wpłynięcia na proces utrwalania, np. Mac OS X udostępnia w fnctl() opcję F_FULLFSYNC. Również Windows udostępnia funkcję FlushFileBuffers() do czyszczenia (całego) cache dysku oraz tryb otwarcia pliku FILE_FLAG_WRITE_THROUGH, których opis sugeruje, że załatwiają sprawę (głowy nie dam, ale wg tego porównania wypada nieźle).

Istnieją narzędzia, które świadomie korzystają z tego dobrodziejstwa, np.:

Istnieją ponadto techniki radzenia sobie z częściowym utrwaleniem danych na podsystemie dyskowym w aplikacjach, które przepisują daną "w miejscu". Dla przykładu Websphere MQ zapisuje dziennik operacji (journal) w stronach i może się zdarzyć, że strona nie jest pełna. Wówczas zapisywana jest część strony, a reszta zostaje dopisana później. Ta druga operacja potencjalnie może skutkować utratą danych tego pierwszego zapisu. Aby to obejść, Websphere MQ domyślnie stosuje dla loga operacji metodę zapisu (LogWriteIntegrity) TripleWrite. Powoduje to dla niepełnych stron loga zapis pierwszej "połówki", na kolejnej stronie commit drugiej "połówki", a następnie (trzeci zapis) commit obu połówek razem do tej pierwszej strony.

To na ostateczne popsucie humoru:
  • znacie programistów krytycznych aplikacji biznesowych ;) w Java, którzy świadomie robią coś więcej niż flush() by utrwalić dane?
  • znacie kogoś, kto ma transakcyjną bazę danych SQL lub serwer SMTP na "oszukujących" dyskach SATA? (co lepsze, te low-endowe dyski mają duże 16-64MB cache)
  • co się stanie z e-mailem, gdy serwer SMTP potwierdzi jego przyjęcie i nie zrobi fsync() na pliku w spoolu, po czym padnie zasilanie?
  • znacie administratorów Linux, którzy świadomie wyłączają write cache na dyskach? (czasami robią to za nich same kontrolery macierzowe)
  • zastanawialiście się co czeka system plików w wirtualnej maszynie po padzie hosta, zwłaszcza gdy ów filesystem gościa jest plikiem na systemie hosta? (hint)
  • czy Wasz system plików księguje (journalling) dane, czy tylko metadane?
  • ile lat temu pojawiło się wsparcie dla wiarygodnego utrwalania danych w Twoich krytycznych aplikacjach i systemach?

środa, 21 września 2011

Zaawansowane filtry w Gmail

Na niektórych kontach Gmail korzystam z dostępu POP3 lub IMAP z opcją usuwania wiadomości po pobraniu. Niestety, domyślnie listy wysłane po SMTP są zapisywane z etykietą "Wysłane" i pozostają w Gmailu. Można je usuwać ręcznie lub IMAP-em.

Postanowiłem dziś przyjrzeć się uważniej opcjom konta pocztowego i odkryłem, że sporo można osiągnąć przy pomocy zaawansowanych kryteriów wyszukiwania w filtrach. Kryteria można wpisać w polu "zawiera słowa", wspierane słowa kluczowe to np. label:sent filename:pdf is:read. Wiele pomysłów na ich wykorzystanie można znaleźć w artykule "20 Ways to Use Gmail Filters".

piątek, 8 października 2010

Luka międzyramkowa

Czasem człowiek myśli, że zna jakiś temat dość dobrze. Może nie bardzo dogłębnie, ale generalnie - ciężko go czymś zaskoczyć... do czasu. ;)

Pisałem w sierpniu o zmiennym narzucie stosu protokołów w ATM. Okazuje się, że podobny problem występuje w sieci Ethernet. Między ramki Ethernet wprowadzana jest luka (interframe gap) o stałym rozmiarze 96 bitów. Taka międzyramkowa luka wprowadza więc większy narzut (overhead) w przypadku, gdy w transmisji dominują małe ramki, a mniejszy, gdy w transmisji dominują większe ramki.

Pocieszam się, że inżynierów sieciowych CCIE też potrafi to zaskoczyć. ;)

niedziela, 18 lipca 2010

VPN z OpenSSH

Kiedy potrzebujemy zestawić między dwoma sieciami VPN na publicznej sieci IP, wybór pada zwykle na jedno z dwóch rozwiązań:
W przypadku IPseca mamy implementację na poziomie jądra systemu operacyjnego i opcjonalnego demona do wymiany kluczy, np. racoon, isakmpd lub inny.

Konfiguracja IPseca jest zwykle dość zagmatwana i można spotkać problemy ze współpracą różnych implementacji ;) toteż administratorzy lubią SSL VPN-y, które "po prostu działają".

Implementacja SSL VPN działa w całości w przestrzeni użytkownika, posiłkując się sterownikiem jądra dostarczającym pseudointerfejsu tunelującego (np. tun). Jedną z najpopularniejszych implementacji SSL VPN-a jest OpenVPN.

W tym wpisie chciałbym jednak pokazać inny wariant SSL VPN-a, nieco mniej znany, ale za to oprogramowanie mamy już w systemie więc zwykle da się to odpalić "od ręki" - mowa o OpenSSH. Nie, nie chodzi o przekierowywanie portów. Od wersji 4.3 (2006 rok) mamy dostępną opcję -w, która umożliwia zestawienie VPN-a z użyciem pseudointerfejsu tun/tap.

Aby to działało, musimy włączyć w serwerze SSH następujące opcje: PermitTunnel i PermitRootLogin.

W celu zilustrowania uruchomię sobie 2 maszyny wirtualne: jedną z Linuksem (serwer) i jedną z OpenBSD (klient). Maszyny mają ze sobą bezpośrednią łączność po IP.

debian:~# grep -i permittunnel /etc/ssh/sshd_config

PermitTunnel yes
debian:~# /etc/init.d/ssh restart
Restarting OpenBSD Secure Shell server: sshd.

Następnie łączę się z OpenBSD do Linuksa:
open# ssh -NTf -w any:any 192.168.1.182

root@192.168.1.182's password:

W wyniku mamy po obu stronach pseudointerfejsy tun i możemy je zaadresować:

open# ifconfig tun
tun3: flags=51 mtu 1500
priority: 0
groups: tun

debian:~# ip a sh dev tun0

5: tun0: mtu 1500 qdisc noop state DOWN qlen 500
link/[65534]

debian:~# ip a add 172.16.1.2 dev tun0 peer 172.16.1.1
debian:~# ip link set tun0 up


open# ifconfig tun3 172.16.1.1/32 172.16.1.2


Testujemy... działa:
open# ping -c 2 172.16.1.2

PING 172.16.1.2 (172.16.1.2): 56 data bytes

64 bytes from 172.16.1.2: icmp_seq=0 ttl=64 time=1.785 ms
64 bytes from 172.16.1.2: icmp_seq=1 ttl=64 time=7.274 ms
--- 172.16.1.2 ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/std-dev = 1.785/4.529/7.274/2.745 ms

Pozostaje ustawić ruting do sieci zdalnej (i opcjonalny NAT). Można także wymusić (klient, opcja Tunnel) użycie pseudointerfejsu tap, czyli emulację łącza Ethernet.


Obecne w OpenSSH wsparcie dla tworzenia szyfrowanych tuneli może być w niektórych sytuacjach znacznie lepszym rozwiązaniem niż przekierowywanie portów. Mamy możliwość transportowania innych protokołów niż TCP, a nawet ramek Ethernet (bridge poprzez VPN). Jeśli potrzebujemy uzyskać dostęp do wielu systemów w zdalnej sieci, może to być prostsze rozwiązanie niż przekierowywanie długiej listy portów. Przekierowanie portów może też utrudnić korzystanie z wirtualnych serwerów WWW (name-based) - tutaj nie mamy tego problemu. Jednak aby się tym pobawić, musimy mieć prawa roota zarówno po stronie serwera, jak i klienta.

poniedziałek, 24 maja 2010

rwące potoki

Każdy elementarz UNIX-a opisuje mechanizm potoków. Przypomnijmy, że potokiem łączymy 2 strumienie: standardowe wyjście stdout jednego programu ze standardowym wejściem stdin drugiego. Standardowe wyjście błędów stderr domyślnie nie trafia do potoku, co można zaobserwować w niektórych programach robiąc program --help | more i klnąc, że 3 strony pomocy przeleciały nam przez ekran niepowstrzymane. ;)

Istnieje kilka narzędzi, które można wykorzystać do zmierzenia i wizualizacji wolumenu danych płynących potokiem, a nawet do ograniczenia natężenia:
Są one często dostępne w standardowym repozytorium pakietów oprogramowania.

Przy ich pomocy można "zobaczyć", jak szybko przetwarzane są dane np. podczas obliczania sumy kontrolnej pliku. Można też zobaczyć na bieżąco, jak dane na wejściu kompresują się po przejściu przez archiwizer. Można wreszcie spowolnić przesyłanie danych, aby zmniejszyć wykorzystanie zasobów systemu (np. sieci, jeśli w potoku uczestniczy program sieciowy) lub... po prostu wyświetlać animację w ASCII. ;)

Przykłady? Dla testu wyślę do potoku dane pobrane dd z generatora liczb pseudolosowych urandom (o nieco niższej jakości, ale za to nieblokującego po wyczerpaniu entropii). Następnie spróbuję ograniczyć prędkość, z jaką pobieram pseudolosowe dane.


$ dd if=/dev/urandom bs=1M count=10 | pipebench > /dev/null
10+0 records in  9.37 MB    6.25 MB/second (Sun May 23 13:24:40 2010)
10+0 records out
10485760 bytes (10 MB) copied, 1,52052 s, 6,9 MB/s
Summary:
Piped   10.00 MB in 00h00m01.52s:    6.55 MB/second



$ dd if=/dev/urandom bs=512k count=1 | cpipe -vr -vt -vw -ngr -s 100 > /dev/null
in:  96.327ms at    1.3MB/s (   1.3MB/s avg)  128.0kB
out:   0.002ms at   61.0GB/s (  61.0GB/s avg)  128.0kB
thru: 1256.614ms at  101.9kB/s ( 101.9kB/s avg)  128.0kB
in:   0.266ms at  469.9MB/s (   2.6MB/s avg)  256.0kB
out:   0.002ms at   61.0GB/s (  61.0GB/s avg)  256.0kB
thru: 1257.576ms at  101.8kB/s ( 101.8kB/s avg)  256.0kB
in:   0.220ms at  568.2MB/s (   3.9MB/s avg)  384.0kB
out:   0.003ms at   40.7GB/s (  52.3GB/s avg)  384.0kB
thru: 1260.403ms at  101.6kB/s ( 101.7kB/s avg)  384.0kB
1+0 records in
1+0 records out
524288 bytes (524 kB) copied, 3,77087 s, 139 kB/s
in:   0.985ms at  126.9MB/s (   5.1MB/s avg)  512.0kB
out:   0.003ms at   40.7GB/s (  48.8GB/s avg)  512.0kB
thru: 1262.864ms at  101.4kB/s ( 101.6kB/s avg)  512.0kB
in:   0.005ms at       0B/s (   5.1MB/s avg)  512.0kB   (bsize=0)
out:   0.001ms at       0B/s (  44.4GB/s avg)  512.0kB
thru:   0.055ms at       0B/s ( 101.6kB/s avg)  512.0kB



$ dd if=/dev/urandom bs=1M count=1 | pv -L 100K > /dev/null
90kB 0:00:03 [ 101kB/s] [ <=> ]


Potoki mogą też być nazwane - widnieją wówczas w systemie plików jako plik typu FIFO i możemy do nich zapisywać dane jak do każdego innego pliku. Różnica jest taka, że zapis będzie blokujący tzn. będzie czekał, aż "z drugiej strony" do potoku nie podłączy się konsument.


$ mkfifo pipe1 pipe2
$ ls -las pipe*
0 prw-r--r-- 1 blog blog 0 maj 24 22:33 pipe1
0 prw-r--r-- 1 blog blog 0 maj 24 22:33 pipe2



$ echo "zapis do potoku w tle" > pipe1 &
[1] 2834
$ cat pipe1
zapis do potoku w tle
[1]+  Done                    echo "zapis do potoku w tle" > pipe1

$ cat pipe1 &
[1] 2836
$ echo "zapis do potoku czytanego w tle" > pipe1
zapis do potoku czytanego w tle
[1]+  Done                    cat pipe1


Tak uzbrojeni możemy sięgnąć po narzędzie do rozszczepiania potoku, czyli tee. tee przepuszcza swoje wejście na wyjście, ale robi kopię danych i wysyła do wskazanego pliku.


$ echo DANE TESTOWE | tee kopia-strumienia
DANE TESTOWE
$ cat kopia-strumienia
DANE TESTOWE


Przy pomocy tee można np. kompresować plik i jednocześnie liczyć sobie sumę kontrolną pliku wynikowego "w trakcie".

W szczególności plik podany tee może być potokiem nazwanym, czyli strumień trafi do 2 potoków jednocześnie.


$ echo DANE TESTOWE | tee pipe1 | sed s/DANE/POTOCZYSCIE/ &
[1] 2932
$ cat pipe1
POTOCZYSCIE TESTOWE
DANE TESTOWE
[1]+  Done                    echo DANE TESTOWE | tee pipe1 | sed s/DANE/POTOCZYSCIE/


Świetnie, a czy da się np. zamienić miejscami strumień stdout i stderr? Albo czy da się otworzyć więcej niż te dwa standardowe strumienie? Oczywiście. :)

Dla przypomnienia: proces ma listę otwartych deskryptorów. Standardowo mamy otwarte:
  • deskryptor 0, czyli stdin
  • deskryptor 1, czyli stdout
  • deskryptor 2, czyli stderr
Możemy otworzyć kolejne. W przypadku powłoki bash, robimy to np. następująco (do czytania):


$ echo DANE > myfile
$ cat <&3
DANE


No to zamieńmy miejscami stdout i stderr:


$ echo DANE | grep -v DANE
$ echo DANE 3>&1 1>&2 2>&3 | grep -v DANE
DANE


A jeśli naotwieramy więcej deskryptorów, to czy da się jakoś operować więcej niż na parze z nich? Z pomocą przychodzi narzędzie multitee.


$ echo DANE1>plik1
$ echo DANE2>plik2
$ echo DANE3>plik3
$ exec 3wyjscie1
$ exec 7>wyjscie2
$ exec 8>>wyjscie3
$ echo WEJSCIOWE | multitee 0:6,7,8 3:8 4:8 5:6
$ cat wyjscie1
DANE3
WEJSCIOWE
$ cat wyjscie2
WEJSCIOWE
$ cat wyjscie3
DANE2
DANE1
WEJSCIOWE



Na koniec pozostaje pozamykać deskryptory:


$ exec 3>&-
itd.

sobota, 8 maja 2010

nasty filenames

Przyzwyczailiśmy się, że w nazwach plików występują głównie "normalne" znaki, z wyjątkiem ukośnika (separatora katalogów w ścieżce). Możliwe jest jednak wystąpienie innych znaków.

Stwórzmy katalog ze znakiem końca wiersza w środku jego nazwy. Dla odmiany po poprzednim wpisie użyjmy Perla.

linux$ perl -e 'mkdir("prefix\nsuffix")'


Co możemy powiedzieć o naszym dziele?

linux$ ls
prefix?suffix
linux$ ls | cat
prefix
suffix


Wygląda podejrzanie. Ma to pewne implikacje nie tylko dla mechanizmu autouzupełniania powłoki :) ale także dla innych aplikacji:

linux$ find . -type d -name 'prefix*' | xargs rmdir
rmdir: nie udało się usunąć `./prefix': Nie ma takiego pliku ani katalogu
rmdir: nie udało się usunąć `suffix': Nie ma takiego pliku ani katalogu


Po to właśnie wymyślono opcję użycia znaku NULL jako separatora wierszy:

linux$ find . -type d -name 'prefix*' | xargs printf "received [%s]\n"
received [./prefix]
received [suffix]
linux$ find . -type d -name 'prefix*' -print0 | xargs -0 printf "received [%s]\n"
received [./prefix
suffix]


Teraz już możemy bezpiecznie i skutecznie usunąć katalog.

linux$ find . -type d -name 'prefix*' -print0 | xargs -0 rmdir
linux$ ls
linux$


Na marginesie: spotkałem się kiedyś z komercyjnym UNIX-em, który zwyczajnie nie wspierał takich opcji w find i xargs. :)

Wniosek z tego płynie taki: jeśli przetwarzasz wynik dowolnych poleceń zwracających nazwy plików, lepiej nie polegaj na znaku końca wiersza jako faktycznym znaczniku końca linii. Możesz ryzykować nie tylko załamaniem procesu przetwarzania, ale także stworzyć zagrożenie.

Co by się stało, gdybyśmy tu mieli xargs rm -rf?

linux$ perl -e 'mkdir("prefix\n..")'
linux$ find . -type d -name 'prefix*' | xargs printf "received: [%s]\n"
received: [./prefix]
received: [..]


Potrzeba czujności nie dotyczy tylko skryptów powłoki, ale także np. logów. Wiele aplikacji prowadzi logi tekstowe, które parsuje się później wiersz po wierszu. Jeśli aplikacja wpisuje do loga nazwę pliku, może się okazać, że ten konkretny wpis został podzielony na wiele wierszy. Tego typu zachowanie zaobserwowałem kiedyś w logach serwera aplikacyjnego JEE: aplikacja przetwarzała pliki ze spoola i odkładała w logach ślad swojej pracy, w tym nazwę otwieranego pliku.

piątek, 7 maja 2010

unix time

Systemy uniksowe tradycyjnie reprezentują czas jako liczbę sekund, które upłynęły od czasu t0 równego 1970-01-01 00:00:00 UTC (tzw. Epoch).

Większość aplikacji formatuje znaczniki czasu do postaci strawnej dla człowieka. Czasem jednak pojawia się konieczność przeanalizowania danych bardziej niskopoziomowych, w których znajdziemy znacznik czasu w postaci surowej.

Jak szybko dokonać konwersji w jedną i drugą stronę? Choćby przy pomocy Perla. A bez Perla da się? Da się. Otóż można do tego celu użyć po prostu polecenia... date.

Implementacja date różni się między systemami. Zauważmy na przykład, że w GNU date czas sekundowy należy poprzedzić znakiem @ (nie ma tego w podręczniku ekranowym man, ale jest w info).


Konwersja czasu na format uniksowy:

freebsd$ date -j -f "%Y-%m-%d %H:%M:%S" "2010-05-07 02:34:59" "+%s"
1273192499

linux$ date -d "2010-05-07 02:34:59" +%s
1273192499

Konwersja czasu z formatu uniksowego:

freebsd$ date -j -f "%s" 1273192499
ptk 7 maj 02:34:59 2010 CEST

linux$ date -d @1273192499
pią, 7 maj 2010, 02:34:59 CEST

A gdybyśmy chcieli przetworzyć format sekundowy jako jeden z elementów wiersza podanego w potoku? Z pomocą może przyjść choćby GNU awk:

$ echo 1273192499 | gawk '{print strftime("%c", $1)}'
pią, 7 maj 2010, 02:34:59

Niestety, nie każdy system (i nie każda dystrybucja Linuksa) ma domyślnie zainstalowane GNU awk.

Na koniec warto wspomnieć o wygodnych podróżach w czasie, które umożliwia nam date na FreeBSD przy pomocy opcji -v.

Aby ustalić ostatni poniedziałek przyszłego miesiąca:
  1. ustawiamy się na pierwszym dniu obecnego miesiąca
  2. przeskakujemy o 2 miesiące do przodu
  3. cofamy się o jeden dzień (na ostatni dzień miesiąca poprzedniego)
  4. cofamy się, aż znajdziemy poniedziałek 
freebsd$ date -v1d -v+2m -v-1d -v-mon
pon 28 cze 17:06:13 2010 CEST




Więcej informacji: