Jak zmniejszyłem maksymalny NIP

Prawie 3 lata temu zastanawialiśmy się z klientem biuro obsługi podatkowej jak stworzyć system webowy, który pozwalałby klientom oraz pracownikom biura na przekazywanie danych istotnych z punktu widzenie prowadzenia firmy, a jednocześnie na tyle prostych, żeby nie trzeba było przechodzić kursu księgowości, żeby zrozumieć działanie takiego systemu.

Na początku został stworzony prosty rejestr klientów w którym każdy z nich może sobie sprawdzić podstawowe dane księgowe. System na start został wyposażony w możliwość wysyłania powiadomień, w których to zawarte są informacje na temat nadciągających nieuchronnie terminów płatności różnych rabowanych nam przez państwo składek. Sam je płacę, więc wiem, a latami miałem ustawioną przypominajkę z kalendarza google.

Wracając do meritum sprawy. Po kilkunastu miesiącach pracy pojawiła się w systemie, jedna z wielu, mała modyfikacja, polegająca na tym, że do firm typu „spółka” można było dołączyć dane wspólników, które mogli oni sobie sprawdzać samodzielnie.

A potem pojawił się błąd. Klient zgłasza, że nie można wprowadzić numeru NIP wspólnika. Zaczynam sprawdzać, raz, drugim trzeci. W kodzie nie ma NICZEGO co sugerowałoby jakiekolwiek operacje na nipie, a walidacja nigdy klientowi nie była potrzebna, bo i sam nip raczej służy do dodatkowej weryfikacji, a nie do czegoś istotnego. Co grosza, moje testy nie pokazywały występowania błędu!!! Wpisywałem prawdziwe, zmyślone i całkiem złe numery nip i nigdy nie napotkałem na opisywany problem. Najgorszym objawem było to że w NIP pojawiał się, niezależnie od wpisywanego numer: 4294967295. Zupełnie nie zwróciłem na tą liczbę uwagi, a powinienem, bo jest ona rozwiązaniem kłopotu.

Problem polegał na użytym polu w bazie danych MEDIUMINT który jest typem domyślnym w czasie deklaracji w mysql pola typu INT ma, bez znaku, taką właśnie wartość maksymalną.

Co ciekawe, przy konstrukcji bazy kilkanaście miesięcy wcześniej i definicji pola NIP na potrzeby firm, nie popełniłem tego błądu i wszystko działało prawidłowo.

wordpress i nginx w jednym miejscu stali

Przenoszę serwisy z jednego serwera na drugi i na tym drugim z założenia ma nie być apache’a. na pierwszy ogień poszedł najmniejszy z wordpressów, taki którego można by przenieść nawet ręcznie. Kilka wpisów na krzyż, kilka wtyczek i tworzony wciąż szablon wyglądu.

Na pierwszy ogień przeszła przeprowadzka bazy, więc dump z bazy mysql (systemowa baza serwera) w celu wyciągnięcia danych z tabel db oraz user oraz przerzucenie dwóch rekordów do nowej bazy. Potem CREATE DATABASE nazwa; i import dumpa.

Sama konfiguracja wirtuala to moment, choć sposób wykorzystania php w trybie cgi wymaga własnego wpisu, poszła bez kłopotu.

Do typowej konfiguracji musiałem dodać tylko wpisy dotyczące kodowania i zmienić index katalogu na index.php.

Ponieważ odnośniki są ustawione jako /%postname%/ wymagane jest użycie reguł rewrite’ów, które dla apache’a są tworzone z automatu.

Dla nginx’a wygląda to następująco:

location / {
    root   /ścieżka_do_document_roota;
    if (-e $request_filename) {
        break;
    }
    rewrite ^(.+)$ /index.php?q=$1 last;
}

IP.Board i uszkodzony index

Jest sobie forum postawione na IP.Board, który jest prawie idealny. Prawie, ponieważ używa mysql’a zamiast postgresql’q. Gdyby używał tego ostatniego były perfekcyjny.

Tabela posts używa inno db, o którym im więcej wiem, tym bardziej mam go dość. No ale wracając.

Z cron’a chodzą sobie backupy baz danych, między innymi mysqldump, który od kilku dni (niestety) wywalał się z następującym komunikatem:

mysqldump: Error 2013: Lost connection to MySQL server during query when dumping table `ibf_posts` at row: 111598
mysqldump: Got error: 2002: Can't connect to local MySQL server through socket

Co w logach widziane było tak: (zresztą select count(*) from ibf_posts, skutkowało tym samym błędem)

InnoDB: Page checksum 396689542, prior-to-4.0.14-form checksum 4074025863 stored checksum 4054066906, prior-to-4.0.14-form stored checksum 1768235130
Page may be an index page where index id is 0 6702
Database page corruption on disk or a failed file read of page 400.
You may have to recover from a backup.
It is also possible that your operating system has corrupted its own file cache and rebooting your computer removes the error.
If the corrupt page is an index page you can also try to fix the corruption by dumping, dropping, and reimporting the corrupt table. You can use CHECK TABLE to scan your table for corruption.
See also InnoDB: about forcing recovery.
Ending processing because of a corrupt database page.
Number of processes running now: 0
restarted

Strona polecana przez logi jest ok, poza tym, że procedura tam opisana do niczego nie prowadzi. Mimo:
InnoDB: !!! innodb_force_recovery is set to 4 !!!
Skończyło się propozycją wysłania stack trace’a, zgodnie zkolejną opisaną procedurą: http://dev.mysql.com/doc/mysql/en/using-stack-trace.html

Mam to w nosie.

A ponieważ cała baza i tak działa (aktualnie backupuje się co godzina katalog /var/lib/mysql i jest spokój.)

W sumie trzeba mi było od razu zastosować się do procedury opisanej na pierwszej polecanej stronie. Czyli skopiować dane, wywalić tabelę, założyć tabelę, przywrócić dane.

W sumie wygląda prosto, ale … nie miałem jak wykonać dumpa, co gorzej większość zapytań korzystających z całych kluczy wywalało się. Całe szczęście dało się wyciągnąć z całej tabeli pole pid, które jest kluczem głównym tej tabeli.

Mając to pole, mogłem wyciągnąć dane po kolei i zapisać je sobie na boczku, potem już łatwo: skasować tabelę, załadować (bez kluczy) wgrać dane i zapiąć klucze.

W czasie pracy powstały następujące pliki:

  • read.pl – czyta dane z bazy
  • write.pl – zapisuje do bazy dane z pliku
  • ifb_posts.sql – kasuje
  • ifb_posts_keys.sql

Które można sobie pobrać tutaj: ip.board.posts.tar.bz2

Na koniec warto wykonać:

OPTIMIZE TABLE ibf_posts;

Z ciekawostek w czasie pracy:

Dla niektórych zapytań pojawiał się następujący błąd: ERROR 1030 (HY000): Got error -1 from storage engine co doprowadziło do przeczytania wątku na stronie mysql’a: http://bugs.mysql.com/bug.php?id=30225 który mnie po prostu rozbawił, jako że nie uznano tego błędu za błąd, a receptą była aktualizacja. Słabo?

jakie klucze stosować, jakie wybrać

Josh Berkus w cyklu „Primary KeyvilI, IIIII rozważa kwestie związane z używaniem kluczy głównych w tabelach. Zastanawia się nad kluczami będącymi w jego odczuciu surogatami prawdziwych kluczy głównych. Za taki surogat uważa klucze oparte na automatycznie inkrementowanych polach w bazie (autoincrement dla mysql’a oraz serial dla postgres’a) i przyznam, że podane argumenty są bardzo mocno i ciężko mi z nimi polemizować.

Generalnie autor zachęca do stosowania kluczy głównych które są rzeczywistym wyróżnikiem. Rozważmy sytuację w której mamy trzy proste tabele: users, groups, user2group – dwie pierwsze zawierają dane dotyczące odpowiednio: użytkowników oraz grup, ostatnia zawiera informację do których grup należy użytkownik.

Jeżeli zastosujemy jako klucze główne pola „ID” to w efekcie tabela user2group przyjmie zawartość:

+---------+----------+
| user_id | group_id |
+---------+----------+
|       1 |        1 |
|       2 |        2 |
|       2 |        6 |
+---------+----------+

Co jak widać niewiele mówi.

W przypadku w którym za klucze przyjmiemy odpowiednio email użytkownika oraz nazwę grupy to taka tabela będzie miała następującą zawartość:

+----------+----------+
| email    | group    |
+----------+----------+
| illi@x   | root     |
| gurthg@x | adm      |
| gurthg@x | www-data |
+----------+----------+

Trudno się nie zgodzić, że pomyłka w insercie typu: INSERT INTO tabela VALUES ( 1, 2 );
jest trudniejsza do wychwycenia niż w: INSERT INTO tabela VALUES ( 'illi@x', 'adm' );

Czytanie danych, szczególnie po latach albo obcych jest o niebo łatwiejsze wtedy kiedy naszymi kluczami są dane które coś znaczą.

Następnym drobnym zyskiem jest fakt zmniejszenia ilości zapytań lub joinów w zapytaniach.

Zauważymy, że w ostatnim przypadku żeby wylistować adresy email użytkowników należących do danej grupy, wtedy kiedy znamy tylko jej nazwę, wymusza w pierwszym przypadku użycie dwóch joinów. W drugim przypadku jest to prosty select. Oczywiście sytuacje tak proste raczej nie występują w życiu, ale są dobrym przykładem na zobrazowanie korzyści.

Oczywiście są sytuacje w których będę nadal używał kluczy opartych o inkrementowaną liczbę naturalną, ale tylko tam gdzie uznam to za naprawdę konieczne.

Natomiast w kwestiach prostych zostałem całkowicie przekonany do używania jako klucze główne takich danych, które niosą ze sobą rzeczywistą różnicę między rekordami.

Aktualizacja IP.Board 2.3.5 do 3.0.2

IP.Board jest najlepszym jaki do tej pory spotkałem oprogramowaniem do forum. Jest to oprogramowanie płatne, na dodatek w sposób mieszany. Po pierwsze płacimy za zakup licencji $150, a potem co pół roku (jeżeli chcemy mieć dostęp do aktualizacji) $25. Nie są to duże sumy jeżeli utrzymujemy forum nie jest hobbystyczne.

Nadszedł czas aktualizacji, wymuszony niejako przez exploita, który zainfekował stronę. Było trochę problemów z uzyskaniem plików instalacyjnych, bo nie była opłacona licencja (ta odnawialna). Ale po kilkudniowej przygodzie i wymianie kilkunastu maili ze wschodnim wybrzeżem, co jest zadaniem dość niewdzięcznym ze względu na 6 godzin różnicy (u nich jest wcześniej).

Jestem naprawdę pod wrażeniem tego jak zostało napisane narzędzie do aktualizacji. Sama aktualizacja przebiegła następująco:

  1. wyłączenie forum z informacją o planowanej aktualizacji
  2. wykonanie kopii plików
  3. wykonanie kopii bazy danych
  4. wgranie plików aktualizacyjnych
  5. uruchomienie

Po pobraniu plików wgrywamy całość do naszej instalacji i odpalamy admin/upgrade/index.php prawda że maksymalnie proste?

To taka teoria. Praktyka pokazała, że mysql znowu pokazał swoje złe strony. Procedura aktualizacji zakończyła się porażką, ponieważ polskie znaki diakrytyczne … znikły z bazy, wymienione na znaki zapytania. Niemiło. Okazało się że musiałem przestawić ustawienia mysql’a żeby osiągnąć pożądany efekt. Niestety przestawienie ustawień mysql’a spowodowało, że jeden z blogów przestał wyświetlać polskie znaki. Krótka analiza kosztów ujawniła, że łatwiej mi będzie doprowadzić ów blog do porządku, niż walczyć z forum.

Druga próba aktualizacji została zakończona całkowitym sukcesem.

Niestety wieczorne czytanie RSS’a spowodowało, że uśmiałem się setnie. Właśnie została opublikowana wersja 3.0.3, więc jutrzejszy ranek spędzę na kolejnej aktualizacji. Mam nadzieję, że tym razem będzie krótka i bezbolesna.

Dla pamięci ustawienia dodane do mysql’a (/etc/mysql/my.conf)

[mysqld]
character-set-server = utf8
character-set-client = utf8
character-sets-dir = /usr/share/mysql/charsets/
default-character-set = utf8
init_connect='SET collation_connection = utf8_general_ci'
init_connect='SET NAMES utf8'
collation-server = utf8_general_ci
[mysql]
default-character-set = utf8
[client]
default-character-set=utf8