Przez rok nie działało na produkcji 😲!! Czyli @Cacheable i @Cacheput w Springu 🔥🔥

Cześć 🖐. Tytuł nie jest żadnym click baitem. To się wydarzyło🤦‍♂️… Przez rok mieliśmy zaimplementowaną funkcjonalność która nie działała i nikt tego nie zauważył!


W styczniu tego roku (2020) do jednego z systemów jaki rozwijamy mieliśmy dodać nowy słownik który powinien być przechowywany w pamięci cache oraz dodatkowo odświeżać się gdy ktoś wgra jego nowszą wersję. Zadanie wydawało się być proste – tym bardziej, że mieliśmy bardzo podobną implementację w naszym systemie od roku…

I tu zaczyna się cała historia.

Programista, który to implementował zaufał istniejącemu kodowi i postanowił przekopiować rozwiązanie zmieniając tylko poszczególne parametry. Takie podejście pewnie by przeszło ale… na całe szczęście pojawił się tester 🕵🏻, który sprawdził to zadanie i jak się domyślasz po tytule – coś nie zadziałało. Słownik jak najbardziej zapisywał się w cache’u (dlatego programista nie wykrył błędu sam) – problem występował w momencie gdy była wgrywana nowa wersja słownika – cache się nie aktualizował.

<Tu pojawia się rola mojej skromnej osoby – bo programista, który to implementował był niedostępny a zadanie wróciło z testów>

Tak wyglądał kod klasy odpowiedzialnej za zarządzaniem cache’em dla tego słownika

Pierwszą myślą jaka mi przyszła do głowy było sprawdzić jak działają pozostałe słowniki – i znów jak możesz domyślać się z tytułu – odświeżanie innych słowników nie działało. Moje na tamten czas doświadczenie z cache’m w Springu było zerowe więc to od czego zacząłem były jakieś szybkie tutoriale. Po wstępnym zapoznaniu się – jak na prawdziwego developera przystało – opisałem problem w google i przeszukiwałem podobne problemy (stackoverflow :)). Nie umiałem znaleźć odpowiedzi na mój problem – choć jak się później okazało…

I to jeszcze jako najpopularniejszy problem dotyczący Spring cache. Ja jednak ze swoją pełną ignorancją sięgnąłem do dokumentacji. Dokumentacja w Springu jest napisana bardzo dobrze ale i tam odpowiedzi nie potrafiłem znaleźć – choć była – wystarczyło przeczytać na spokojnie ze zrozumieniem.

In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual caching at runtime even if the invoked method is marked with @Cacheable – considering using the aspectj mode in this case.

Po dniu takiego przeszukiwania i eksperymentowania postanowiłem, że zajrzę do kodu zarządzającego cach’em w Springu. Zapiąłem się debugiem w paru miejscach i ogień 🔥🔥. Dodam jeszcze, że słowniki były ładowane przy starcie aplikacji a kod wyglądał mniejwięcej tak:

(I tu była kolejna wksazówka, którą tak bardzo przeoczyłem. Metoda getSlownikToCache() wywoływana jest bezpośrednio z docelowego obiektu)

Po starcie aplikacji słownik ładuje się do cache – wszystko wygląda dobrze, nowe obiekty są w pamięci, gdy się do nich odwołuję przychodzą z cache’a. Wgrywam nową wersję słownika, wciskam aktualizację cache i cisza 🤫.

Po stronie Springa nic się nie dzieje 🤯. Mechanim uaktualnienia cache się nie uruchamia. Po chwili zorientowałem się, że aktualizacja idzie przez metodę pośrednią aktualizujCache(). Na tamten moment nie wiedziałem jeszcze, że to jest przyczyną a próbowałem już wielu innych rzeczy – i prawdę mówiąc byłem nieco poirytowany. Tak czy siak zmieniłem wywołanie na coś takiego

Nie miałem wielkich oczekiwań, że to zadziała – choć bardzo chciałem żeby zadziałało 😄. Uruchamiam aktualizację w debugerze i JEST! Cache się zaktualizował ! Cieszyłem się troche jak dziecko. Po czasie połączyłem wszystkie fakty i nadrobiłem braki w przeglądnietej tylko wcześniej przeze mnie dokumentacji. Teraz wiem, że da się zrobić tak aby aktualizowanie działało przez metodę pośredniczącą. Natomiast dla naszego rozwiązania i prostoty nie było takiej potrzeby. Poprawiłem kod dla pozostałych słowników i przekazałem zadanie testerowi.

Lekcje do wyciągnięcia na przyszłość:

  • czytać uważniej dokumentacje / opis podobnych problemów – gdybym z uwagą przeczytał dokumentację lub wpis na stackoverflow (albo to i to), mógłbym zaoszczędzić sobie dzień pracy.
  • głebiej analizować istniejący kod – tutaj problem polegał na tym, że sugerowałem się jak już to było rozwiązane. Inne słowniki też próbowały się odświeżać przez metodę pośrednią przez co całość nie działała. Gdybym skupił się na tym co działa (ładowanie do cache przy starcie aplikacji) i porównał z tym co mam – znów – mógłbym sobie zaoszczędzić czasu.
  • nie ufać w 100% cudzemu kodu – chyba nie trzeba tego komentować. Ja zaufałem, że ktoś zrobił to dobrze a wcale tak nie było i koniec końców rezultat był taki, że przynajmniej nauczyłem się jak działa cache w Springu – więc dziękuje osobie która to implementowała 🙂
0 0 vote
Article Rating
Subscribe
Powiadom o
guest
2 komentarzy
najnowszy
najstarszy oceniany
Inline Feedbacks
View all comments
Anonim
Anonim
3 miesięcy temu

Mnie to spotkało z @Transactional – nie działał bo metoda z tą adnotacją była wywoływana w ramach jednej klasy. Podobnie jak Ty sięgnąłem do dokumentacji i do internetu, i wbiłem sobie do głowy raz na zawsze: Spring działa w oparciu o proxy, więc wszystkie @Transactional, @Async, @Cacheable, własne aspekty muszą być: – na metodzie publicznej – wywoływane z innej klasy będącej ‚beanem’ – użyte w klasie która jest publiczna (odkryłem to jak własny aspekt był na metodzie publicznej, a sama klasa była chroniona pakietowo – Spring bez problemu załadował taką klasę jako ‚beana’, ale jak już przychodziło do wywołania aspektu… Czytaj więcej »