Pracując nad systemem metryk kodu, wpadłem na problem przechowywania dużej ilości plików z raportami PHPMetrics na serwerze. Udało mi się zastosować dość dużą optymalizację takich plików i zaoszczędzić masę miejsca na serwerze. A oto jak przebiegł proces.
Problem
Przechowywanie raportów PHPMetrics z wielu projektów z przestrzeni kilku lat.
Raporty generowane co miesiąc są zbierane w celach statystycznych. Przyjmując założenie, że statystyki projektu są gromadzone przez okres 3 lat, daje to łącznie 36 raportów JSON na każdy projekt. W przypadku kilkudziesięciu projektów liczba tych raportów znacząco wzrasta. Przeciętny raport wygenerowany przez PHPMetrics zajmuje około 7 MB. Po usunięciu zbędnych spacji (czyli pretty print) rozmiar zmniejsza się do około 5 MB. Oznacza to, że 5 MB na raport, razy 36 miesięcy, razy 50 projektów, daje 1800 raportów o łącznej wadze około 9 GB!
Kluczowym czynnikiem przy wyborze rozwiązania problemu przechowywania raportów była ich rzadkość odczytu – średnio raz na miesiąc. Drugim istotnym aspektem było to, że raporty zawsze będą odczytywane w całości, a nie partiami. Ponadto nie przewiduje się potrzeby wyszukiwania danych w tych raportach. Te założenia umożliwiły podjęcie decyzji o przechowywaniu raportów w systemie plików na serwerze, niezależnie od tego, czy lokalnie, czy AWS S3.
Rozwiązanie
Zmniejszyć rozmiar plików maksymalnie, zostawiając tylko te dane, które faktycznie potrzebujemy.
Krok 0: Usunięcie białych znaków
To było najprostsze co można zrobić. Mając JSON wystarczy go sparsować i ponownie przekształcić do JSON.
$result = json_encode(json_decode($source));
Przed: 9,3 MB
Po: 5,8 MB
Zysk: 38%
Krok 1: Usunięcie niepotrzebnych danych
Z racji tego, że raport PHPMetrics zawiera wiele różnych danych, nie wszystkie dane potrzebowałem. Naturalnym było więc pozbycie się tych nadmiarowych danych z raportu.
foreach ($source as $item) {
if ($item['_type'] === 'Hal\\Metric\\ClassMetric') {
$result[$item['name']] = [
'type' => 'class',
'lcom' => $item['lcom'],
// ...
];
}
}
Przed: 5,8 MB
Po: 889 kB
Zysk: 85%
Krok 2: Zmiana kluczy na numeryczne
W przypadku takich raportów bardzo dużo kluczy się powtarza, ponieważ każda klasa posiada te same metryki. Zmieniłem więc klucze na jednoliterowe. Na przykład volume
na v
. Już sama ta zmiana dawała dużą optymalizację w przypadku 20 metryk per klasa, ale dało się zrobić jeszcze. Klucze w postaci ciągu znaków w JSON posiadają dodatkowe znaki w postaci double quote, ale liczby już nie…
foreach ($source as $key => $item) {
$result[$key] = [
0 => $item['type'],
1 => $item['lcom'],
2 => $item['lloc'],
// ...
];
}
Przed: 889 kB
Po: 536 kB
Zysk: 40%
Krok 3: Zamiana formatu pliku na CSV
W przypadku JSON nie udało mi się już nic więcej wymyślić. To był maks tego formatu. Doszedłem do wniosku, że skoro tak, to może warto spróbować poszukać innego formatu? Zaletą tego raportu jest to, że mamy tam praktycznie płaskie dane. Lista klas i interfejsów, oraz 20 metryk dla każdej klasy w postaci liczby. Najbardziej oczywistym formatem wydawał się tutaj CSV. Pierwsza linijka to nazwy wartości – już nawet w pełnej postaci a nie numerycznej – a następnie nazwa klasy i jej metryki po przecinku w kolejnych liniach.
$csv = fopen(__DIR__.'/report.csv', 'wb');
// Headers
fputcsv($csv, [
'classname',
'type',
'lcom',
// ...
]);
// Data
foreach ($source as $classname => $entry) {
if ($entry['type'] === 'class') {
fputcsv($csv, [
$classname,
'c',
$entry['lcom'],
// ...
]);
}
}
fclose($csv);
Przed: 536 kB
Po: 280 kB
Zysk: 48%
Podsumowanie
Zdaję sobie sprawę, że proces optymalizacji mógł zostać przyspieszony poprzez migrację danych bezpośrednio z formatu JSON do CSV. Jednak każda decyzja w tym procesie wynikała z poprzednich założeń oraz bieżących wymagań. Podobnie było z czynnikami wpływającymi na decyzje – gdyby były inne, na przykład gdyby dane nie były dostatecznie płaskie, zatrzymałbym się na wcześniejszym etapie optymalizacji (kroku 2). Niemniej jednak, cały proces nie był czasochłonny i przebiegł sprawnie.
Pomijając krok 0, czyli usunięcie zbędnych spacji w raportach (co jest dość oczywistym rozwiązaniem, by nie marnować przestrzeni dyskowej), wszystkie przeprowadzone transformacje pozwoliły zmniejszyć rozmiar raportów średnio o 95%! To imponujący wynik, zwłaszcza przy tak prostych operacjach. Na początku 1800 raportów zajmowało aż 9 GB przestrzeni dyskowej. Po optymalizacji ich rozmiar wynosi jedynie 450 MB.
W dobie wszechobecnej chmury i tanich usług takich jak AWS S3 można odnieść wrażenie, że kwestie przestrzeni dyskowej nie są już istotne. Jednak przy dużych wolumenach danych nawet pozornie niewielka, 20-procentowa optymalizacja może prowadzić do oszczędności liczonych w dziesiątkach gigabajtów. W przypadku projektu, nad którym pracowałem, 50 projektów stanowiło jedynie etap MVP. Docelowo liczba ta może wzrosnąć do setek. A w tedy z 9GB mielibyśmy dziesiątki, jak nie setki gigabajtów danych, które trzeba utrzymywać na serwerze.
Czy było warto W moim przypadku zajęło to zaledwie godzinę pracy, a efektem była oszczędność na poziomie 95%. Bez wątpienia – warto!