Wpis z mikrobloga

#programowanie #php #pliki #bazydanych

Postanowiłem że zacznę wykop troszkę traktować jak priv bloga ( ͡° ͜ʖ ͡°) Ostatnio zacząłem rozważać pewien dosyć typowy problem - pod tytułem jak w webie umożliwić użytkownikowi śledzenie i zarządzanie plikami. Dla kontekstu, mam tu dosyć nowoczesną aplikację, która wystawia RestAPI schowane za oAuth.

Problem, użytkownik frontendu potrzebuje możliwości dodawania i przeglądania plików jak na dysku komputera. Tzn potrzebuje możliwości tworzenia folderów i grupowania w nich plików / innych folderów. Drzewo nie ma limitu głębokości.
Jedyny search jaki implementuje frontend to szukanie po nazwie pliku, nie jest istotna pełna ścieżka przy ewentualnym pokazywaniu wyników wyszukiwania.
Aplikacja posiada 2 bazy danych, redis - służący za długoterminowy cache (od 6 do 24h) oraz mysql w którym trzymane jest w zasadzie wszystko inne, chociaż same pliki mają lądować na AWS S3.
Na AWS każdy user ma swój folder, wynikający z jego uuid, w którym płasko są trzymane pliki, dla zachowania unikalności nazw, jako nazwy wykorzystywane są ich uuidy i reprezentatywne nazwy po myślniku (np. e8ede6c8-d679-43a3-a15d-ff5f92de8e05-logo.png), bowiem foldery nie są w żaden sposób istotne z pkt widzenia aplikacji. Istotne są za to rozmiar i typ pliku. Pytanie: Jak zatem reprezentować drzewiastą strukturę dysku w bazie danych na potrzeby użytkownika?

Istnieją 4 popularne rozwiązania.

1. Dorzucamy pole parent_id i tyle. Insert oraz update jest tani, ale koszt przeszukiwania po drzewie jest wysoki.
2. Materialized Path - w bazie trzymamy ścieżkę do danego elementu pod postacią idków parentów np. 3/14/7/28 dla id reprezentowanych poprzez wartości integer. Umożliwia łatwe przeszukiwanie po drzewie, ale kosztem update'a. Zmiana parenta jednego folderu może wiązać się z koniecznością aktualizacji całej masy rekordów.
3. Nested Set - Kiedyś już pracowałem z tym rozwiązaniem. Sprowadza się do reprezentowania całego drzewa w bazie danych, z lewym, prawym elementem oraz parentem. Super łatwe wyszukiwanie róznych grup elementów, świetna sprawa. Ale jakiekolwiek operacje aktualizacji to "pain in the ass".
4. Closure Table - rozwiniecie podejścia nr 1, gdzie informacje o strukturze trzymamy w osobnej tabeli, dorzucając informacje o id dziecka oraz zagłębieniu. Znacznie ułatwia przeszukiwanie, nie jest tak skomplikowane, aktualizacja rekordów nie jest ogromnym bólem zadka.

Co wybrać...
Przyjrzyjmy się dokładnie naszemu przypadkowi. Użytkownik nie ma potrzeby przechowywać ogromnej ilości plików, czyli struktury będą małe, wyszukiwanie po drzewie delikatnie to ujmując zupełnie nas wali - wystarczy dobry index w ES po nazwach plików i uuid userów. Niesamowicie istotne jest za to szybkie reagowanie na widzialne przez użytkownika akcje drag&drop w tym przerzucanie plików między folderami. Oznacza to automatyczną eliminację rozwiązań nr 3 oraz 2. Ponieważ użytkownikowi pokazujemy stan jego 'dysku' zawsze wychodząc od root folderu, możemy mieć też wywalone w historię. Frontend zawsze ładuje zawartość jednego folderu na raz, kolejne zagłębienia to kolejne strzały do API. Closure Table zatem nic nam nie daje, nie mamy poruszania się po drzewie od dołu w górę.

Wychodzi na to że w tej sytuacji zwykły parentid to wszystko czego trzeba.
No ale właśnie... to zależy bardzo mocno od kontekstu... Jeśli istotne byłoby przeszukiwanie po drzewie, a mógłbym sobie pozwolić na większe obciążenie przy aktualizacji - rozwiązanie padłoby na Materialized Path...

Macie robaczki jakieś własne doświadczenia? ( ͡° ͜ʖ ͡°) I przyznać się, kogo w implementacji wkurzał już Nested Set
  • 4
@micke: jako że mój juniorski mózg nie ogarnia, mógłbyś rozwinąć dlaczego Closure Table jest w tym przypadku złym wyborem? Myślałem, że jest to zawsze najlepszy wzorzec do odwzorowania hierarchii, praktycznie bez wad (poza puchnącą rozmiarem tablelą pivota :P)
@nowiutki: W tym konkretnym wypadku nie jest złym wyborem, a jedynie mniej optymalnym. Wymaga większego nakładu przy utrzymaniu od zwykłego pilnowania id parenta, a nie daje żadnego dodatkowego zysku w zamian. Nie mamy w tej aplikacji sytuacji w której istnieje potrzeba przeszukiwania drzewa względem jakiegoś elementu (nie ma w frontendzie opcji szukania w konkretnych subfolderach, ani nie ma żadnej wizualizacji ścieżki, nie ma też konieczności szukania w górę, ponieważ reprezentacja odbywa
@micke pamiętaj że sporo zależy tu jeszcze od bazy danych. Prawdziwe bazy mają rekursywnie cte i ind ksy na arrayach/jsonie więc baza zachowuje się trochę inaczej