Poprawne ładowanie danych do cachu

0

Witam mam problem z ładowaniem danych dość poważny jak dla mnie.
Posiadam Klase User, Mapke <String, User>, Klase Team, Mapke<Int, Team> w klasie team posiadam liste<User> tak samo posiadam zmienną Team w klasie user. Teraz pytanie jak poprawnie zapisywać dane i następnie je wczytać.
Uznajmy dla przykładu, że na starcie ładuje do mapki teams wszystkie teamy. No i wypadałoby nie ładować wszystkich userów, teraz mam kilka rozwiązań:

  • Ładowanie userów, którzy posiadają teamy
  • Trzymania zmiennych dla danych obiektów np w klasie user nie trzymać całego obiektu team tylko np integer jako id i w przypadku gdy będę potrzebował dostęp do teamu pobierać go z mapki za pomoca tego id.

Zależy mi bardzo na pamięci userów może być kilka tysięcy a niektórzy userzy mogą być używani raz na kilka dni, więc trzymanie ich w pamięci przez cały czas jest po prostu głupie.

0

No niestety nikt nie odpowie Ci na to pytanie jak nie opowiesz o zapytaniach do tego cache. A może on w ogóle niepotrzebny? :)

1

userów może być kilka tysięcy a niektórzy userzy mogą być używani raz na kilka dni, więc trzymanie ich w pamięci przez cały czas jest po prostu głupie.

Dlaczego jest głupie?
W jaki sposób dokonałeś pomiarów oraz benchmarków?

1
  1. Użyj jakiegoś cache typu Caffeine, a najlepiej trzymaj te dane offheap
  2. Cache daje Ci trade-off szybkość vs pamięć. Jeśli masz dostatecznie dużo pamięci (ale patrz punkt 1), to ładuj wszystko do pamięci i pozamiatane
2
  1. Kilka tysięcy userów a ty się martwisz żeby to długo trzymać? Mój laptop ma 32GB ramu a kupiłem go kilka lat temu...
  2. Caffeine + ustawienie max size cache i expiry time i ładować do niego entry podczas pierwszego dostępu. Kosz to jakieś 5 linijek kodu.
Cache<Key, Data> cache = Caffeine.newBuilder()
            .maximumSize(1000)
            .expireAfterWrite(10, TimeUnit.MINUTES) // albo afterRead
            .build();

plus

public Data getData(Key key){
    return cache.get(key, k -> fetchData(k));
}
public Data fetchData(Key key){
   // jakieś ciężkie calle
}

(pisane z palca ale powinno być mniej więcej ok)

0

Nie znałem Caffeine.
To jest off heap, czy normalnie w heapie?
Rzut oka na dokumentację nie za bardzo mi odpowiedział na pytanie

3

W heapie, ale póki nie robisz ultra low latency albo real-time to nie powinieneś sie takimi rzeczami przejmować ;) Jedna uwaga, ze jeśli chcesz żeby periodycznie wyrzucało elementy z cache samoczynnie, to trzeba ustawić jeszcze parametr scheduler, inaczej usuwa tylko jak ktoś robi operacje na tym cache (niejako przy okazji). Jest to istotne, jeśli masz ustawione eviction handlery które cośtam robią (np. usuwają jakieś pliki tymczasowe, zamykają zasoby etc) i chcesz zeby faktycznie po upływie expiry time zrobił się cleanup, bez czekania na jakieś operacje na cache.

0

Dobra wróciłem. Źle zadałem pytanie (chociaż informacja, że nie zawsze trzeba cachować jest przydatna).
Uznajmy, że posiadam taki kod oczywiście będzie więcej zmiennych w tych klasach na szybko to kleiłem.

public class User {

        private final UUID uuid;
        private String name;
        private String city;
        private String color;
        private int points;
        private Team team;

        public User(UUID uuid){
            this.uuid = uuid;
        }
    }

    public class Team {

        private final String tag;
        private List<User> userList;

        public Team(String tag){
            this.tag = tag;
        }
    }

uuid i name są to dane potrzebne all the time, więc nie mam problemu z załadowaniem ich.
Teraz mój problem polega na tym jak dobrze wrzucać reszta danych, może stworzyć jakiś obiekt OnlineUser i ładować reszte tych danych wtedy kiedy będzie potrzeba na jakiś tam czas.
Trzymanie tych wszystkich danych all the time nie przejdzie ponieważ wnioskuje, że trzymanie 50 intów/floatów i nieokreśloną liczbę jeszcze stringów (większość to będą unikatowe dane gdzie nie da się zastosować ManyToOne) nie będzie dobre.

1

screenshot-20200611003640.png

sto tysięcy userów w pamięci

0

Mam dodatkowe pytanie jak rozwiązać taki problem.
Mam mapke w niej uznajmy te mistyczne 100k userów i chce mieć dostęp również poprzez String name.

Map<UUID, User> userHashMap = new HashMap<>();

Teraz jak to zrobić lecenie mapka -> stream -> filter potrafi zajmować w takim przypadku 40+ ms.

Optional<User> optionalUser = userHashMap.values().stream().filter(user -> user.getName().equalsIgnoreCase(name)).findFirst();

Co lepiej zastosować dodatkową mape String,Uuid czy może zastosować takie rozwiązanie mapa String,User i uuid,user

1

To zależy jak bardzo Ci się spieszy.
Ogólnie druga mapka jest ok, ale zawsze stanowi dodatkowy problem z zarządzaniem - trzeba aktualizować 2 mapy.
Im więcej takich indeksów tym gorzej.
Dodatkowa pamięć potrzebna jest raczej pomijalnym problemem.

Są specjalne biblioteki z kolekcjami do automatyzacji takich wyszukiwań:
https://github.com/npgall/cqengine

Jakkolwiek, na proste rzeczy raczej nie polecam.

EDIT:
Strzelam (totalnie w ciemno), że to wyszukiwanie raczej nie zajmuje 40ms dla 100k userów - tylko Ci się wydaje, że tyle zajmuje.

0

@MarekMareckiPL Jeśli w ogóle to robisz obiekt który to opakowuje, tak zeby wszystko mieć w jednym miejscu i aktualizować te mapy jednocześnie.

6

Dlaczego nie zrobisz sobie teraz interface do pobierania tych danych? Zaimplementuj jedna naiwna implementację która trzyma wszystko w pamięci. Jak zobaczysz że to nie daje rady to po prostu zmienisz implementację na lepszą. Za dużo myślisz o optymalizacji w tej chwili dobry interface pozwoli odłożyć to na później albo w ogóle okaze się że problemu nie bylo

Btw tworząc interface skup się na tym co chcesz uzyskać a nie jak te dane będziesz trzymać.

Przykład:
Jedna metoda która zwraca listę drużyn np n drużyn od i tego miejsca.
I druga metodę która pobiera listę userow dla podanej drużyny.
W ten sposób masz dwie płaskie listy zamiast jakiś map itp.
Tylko implementacja pod spodem wie jak to poskładać. W ten sposób masz całkowitą elastyczność trzymania danych.
Dodaj do tego single source od truth ;)

1

Screen z zajętością pamięci - poprzedni był mało wiarygodny ze względu na re-użycie stringów (string pool i te sprawy)
screenshot-20200613095457.png

1 użytkowników online, w tym zalogowanych: 0, gości: 1