Jaką bazę wybrać do cache'owania?

0

Potrzebuję czegoś lekkiego i łatwego w używaniu.
Ma robić następujące rzeczy:

  1. Zapis danych o takiej strukturze:
typ_zgloszenia: ...,
data_waznosci: ...,
szczegoly: ...
  1. Pobieranie najstarszego, aktualnego zgłoszenia czyli takiego z min(data_waznosci) i data_waznosci > now() pogrupowane po typie zgłoszenia

  2. Usuwanie zgłoszeń nieaktualnych, ale tak by został zawsze przynajmniej 1 pogrupowane po typie zgłoszenia

Zrobiłem coś takeigo w javie na Mapach i przy 40 wiadomościach na sekundę mam latency 60 ms :( Chcę zejść do conajmniej 30 ms.

Couchbase? Reddis?

5
  1. A czy już coś próbowałeś (poza wspomnianymi mapami)?
  2. Ja bym zaczął od Redis'a (pisze się przez jedno "D", nie mylić z Redds :P )
  3. Na ile to musi być niezawodne i nieulotne? Bo Redis wprawdzie ma opcje zapisu na dysk, ale (jeśli teraz bluźnię to proszę o sprostowanie) trochę ona niszczy wydajność. Poza tym - dzieje się to co jakiś czas, więc jest ryzyko że pad/restart pomiędzy zapisami część danych może zgubić.
3

Postgresql + garstka indeksów? :-)

3

czegoś lekkiego i łatwego w używaniu Chyba najlepsze będzie SQLite.

3

Redis fajnie działa jak odpytujesz po kluczu. Jak zaczniesz szukać po tym to może już tak fajnie nie działać. Ile masz tych danych? Pomysł @Patryk27 wydaje się sensowny.

3

Jak to są gołe mapy bez zapisu na dysk i jest za wolne to IMHO są dwie opcje:

  • niżej nie zejdziesz bo każdy zewnętrzny komponent oznacza dodatkową serializację (redis) lub serializację i zapis na dysk (postgresql i sqlite)
  • coś skopałeś w implementacji, pogubiłeś się na wielowątkowości itd :p
1
cerrato napisał(a):
  1. Na ile to musi być niezawodne i nieulotne? Bo Redis wprawdzie ma opcje zapisu na dysk, ale (jeśli teraz bluźnię to proszę o sprostowanie) trochę ona niszczy wydajność. Poza tym - dzieje się to co jakiś czas, więc jest ryzyko że pad/restart pomiędzy zapisami część danych może zgubić.

No ale jak to ma być cache to zakładam, że jego odbudowanie nie powinno stanowić problemu. Nie wiem jak jest teraz, ale kiedyś zapis nie powodował jakiś wielkich problemów z wydajnością.

2
UglyMan napisał(a):

Redis fajnie działa jak odpytujesz po kluczu. Jak zaczniesz szukać po tym to może już tak fajnie nie działać. Ile masz tych danych? Pomysł @Patryk27 wydaje się sensowny.

Dodatkowo trzeba pamiętać że redis jest jednowatkowy wiec złe wpięty do systemu wielowątkowego może go spowolnić

0
cerrato napisał(a):
  1. Na ile to musi być niezawodne i nieulotne?

nie może być zawodne, ale może być ulotne. Tzn. nie może baza przestać odpowiadać, ale jak przez 10 sekund nie będzie aktualizować danych to się nic nie stanie.

1
KamilAdam napisał(a):

Dodatkowo trzeba pamiętać że redis jest jednowatkowy wiec złe wpięty do systemu wielowątkowego może go spowolnić

Jak zawsze, jest alternatywa np. https://keydb.dev/downloads/ :)
Osobiście wybrałbym jednak redis. Sprawdzony, działający produkt.

0
cerrato napisał(a):
  1. A czy już coś próbowałeś (poza wspomnianymi mapami)?

nic. Nie mam już czasu, do końca roku muszę oddać tę moją pracę dyplomową.

UglyMan napisał(a):

Redis fajnie działa jak odpytujesz po kluczu. Jak zaczniesz szukać po tym to może już tak fajnie nie działać. Ile masz tych danych?

będę miał 2 klucze tu, danych, 40 wiadomości co sekundę, z tego przechowywać trzeba może z 200.

KamilAdam napisał(a):

Jak to są gołe mapy bez zapisu na dysk i jest za wolne to IMHO są dwie opcje:

  • niżej nie zejdziesz bo każdy zewnętrzny komponent oznacza dodatkową serializację (redis) lub serializację i zapis na dysk (postgresql i sqlite)
  • coś skopałeś w implementacji, pogubiłeś się na wielowątkowości itd :p

no skopane jest, chyba najbardziej tracę czas przez sprawdzanie nuli, iterowanie po tym cachu i usuwanie starych co każde pobranie. Poza tym te mapy powinienem wynieść do osobnej instancji i na zewnątrz, do nowego serwisu, więc de facto sprowadza się to do implementowania własnej "bazy".
a druga, że nie mam już czasu się w tym babrać, muszę do końca roku złożyć pracę na polibudzie.

Zostawiłbym to tak, ale wstyd pokazać 60 ms, jak moje inne microserwisy dają radę w 20 - 30.
Aha to wszystko percentyl 99.

To jest praca o Big Data, więc to ma zapierdalać.

1
straznik-tagu napisał(a):

nie może być zawodne, ale może być ulotne. Tzn. nie może baza przestać odpowiadać, ale jak przez 10 sekund nie będzie aktualizować danych to się nic nie stanie.

Jeżeli ma być szybko, to najszybciej będzie zawsze trzymanie danych w pamięci.
Jeżeli tych danych zaczyna być dużo, no to można je jakoś samemu zapisywać na dysk, znając specyfikę tych danych. Ale to taka zabawa dla twardzieli.
Stosując np. Redis czy inną DB dodajemy narzut czasowy na komunikację. Dodając indeksy w DB, powodujemy, że zapis staje się wolniejszy, ale odczyt szybszy.
Itd, itd...
Generalnie jak się ma świadomość, jak to wszystko pod maską działa to można wybrać najbardziej optymalną drogę.
Fachowcy na takie pytanie ogólne odpowiadają najczęściej " To zależy" :0

3

chyba najbardziej tracę czas przez [...] usuwanie starych co każde pobranie.

Jeśli warunkiem do skasowania ma być po prostu wiek wpisu, to Redis może to ogarnąć za Ciebie.

https://redis.io/commands/expire

Set a timeout on key. After the timeout has expired, the key will automatically be deleted. A key with an associated timeout is often said to be volatile in Redis terminology.

2

będę miał 2 klucze tu, danych, 40 wiadomości co sekundę, z tego przechowywać trzeba może z 200.

40 wiadomości na sekundę, przechowywać trzeba ~200 i z in-memory mapą w Javie masz latency na poziomie 60ms? Coś tu nie pasuje - przecież to nawet w Minecrafcie więcej wiadomości się przetwarza w czasie rzeczywistym :-P

0
  1. Na mój sklerotyczny łeb bazy nie są do cachowania.
  2. Umknęło mi w tym chaosie, w jakim języku projekt powstaje. Podjął bym post @Patryk27, zarówno Java, jak i C# mają bardzo fajne rozwiązania do cachowania in-proces, nie będące bazą danych, ich podstawowe działają na heapie, "wyższe wersje" pracują out-of-heap, ale ciągle w RAM (bez IO), aż po rozproszone na cloud.
2
J.Muzykant napisał(a):
  1. Na mój sklerotyczny łeb bazy nie są do cachowania.

Chyba źle podchodzisz do pojęcia bazy danych

0

@Robert Karpiński:

godzinne pomiary:

p99, p95 i p50
Bez tytułu.png

dla porównania serwis, który tylko mapuje wiadomości i przerzuca dalej:
porownanie.png

pamięć i przepustowosc gc OK na tle innych serwisów:
pamiec.png
przepustowosc.png

0

Wrzucam kod mojego cache:

K1 - typ zgloszenia (jest ich. ok 2)
K2 - nazwa okna czasowego (np. minutowe, godzinne itd. jest ich ok. 8)

@RequiredArgsConstructor
public class OpenedWindowCache<K1, K2, V> implements WindowCache<K1, K2, V> {

    private final ConcurrentMap<K1, ConcurrentMap<K2, OpenedWindowCache.Values<V>>> cache = new ConcurrentHashMap<>();
    private final ToLongFunction<V> timestampExtractor;
    private final Clock clock;

    @Override
    public boolean updateCache(K1 k1, K2 k2, V v) {
        long vTimestamp = timestampExtractor.applyAsLong(v);
        long currentTimestamp = clock.millis();
        if (vTimestamp < currentTimestamp) {
            return false;
        }
        ConcurrentMap<K2, OpenedWindowCache.Values<V>> cachedWindows = cache.getOrDefault(k1, new ConcurrentHashMap<>());
        OpenedWindowCache.Values<V> cachedValues = cachedWindows.computeIfAbsent(k2, key -> new Values<>(timestampExtractor));
        cachedValues.add(v);
        cachedWindows.put(k2, cachedValues);
        cache.put(k1, cachedWindows);
        return true;
    }

    @Override
    public Map<K2, V> getCachedValue(K1 k) {
        long currentTimestamp = clock.millis();
        return cache.get(k).entrySet().stream()
                .collect(Collectors.toMap(key -> key.getKey(),
                        v -> v.getValue().getValue(currentTimestamp),
                        (a, b) -> b));
    }

    @Override
    public List<Tuple<K1, Map<K2, V>>> getAllCachedValues() {
        return cache.keySet().stream()
                .map(key -> Tuple.of(key, getCachedValue(key)))
                .collect(Collectors.toList());
    }

    private static class Values<V> {

        private ToLongFunction<V> timestampExtractor;
        private SortedByTimestampLinkedList<V> values;

        public Values(ToLongFunction<V> timestampExtractor) {
            this.timestampExtractor = timestampExtractor;
            this.values = new SortedByTimestampLinkedList<>(timestampExtractor);
        }

        synchronized V getValue(long currentTimestamp) {
            V result;
            SortedByTimestampLinkedList<V> newValues = values.getFiltered(value -> timestampExtractor.applyAsLong(value) >= currentTimestamp);
            if (newValues.isEmpty()) {
                result = values.get(values.size() - 1);
            } else {
                result = values.get(0);
                this.values = newValues;
            }
            return result;
        }

        synchronized boolean add(V v) {
            return values.add(v);
        }
    }
}

public class SortedByTimestampLinkedList<E> {

    private final List<E> list;
    private final ToLongFunction<E> timestampExtractor;

    public SortedByTimestampLinkedList(ToLongFunction<E> timestampExtractor) {
        this(timestampExtractor, new LinkedList<>());
    }

    public SortedByTimestampLinkedList(ToLongFunction<E> timestampExtractor, List<E> list) {
        this.timestampExtractor = timestampExtractor;
        this.list = list;
    }

    public E get(int i) {
        return list.get(i);
    }

    public SortedByTimestampLinkedList<E> getFiltered(Predicate<E> predicate) {
        List<E> filteredList = list.stream()
                .filter(predicate)
                .collect(Collectors.toList());
        return new SortedByTimestampLinkedList<>(timestampExtractor, filteredList);
    }

    public boolean add(E e) {
        if (list.isEmpty()) {
            list.add(e);
        }
        long vTimestamp = timestampExtractor.applyAsLong(e);
        for (int i = 0; i < list.size(); i++) {
            E idxValue = list.get(i);
            long idxTimestamp = timestampExtractor.applyAsLong(idxValue);
            if (vTimestamp < idxTimestamp) {
                list.add(i, e);
                break;
            } else if (vTimestamp == idxTimestamp) {
                list.set(i, e);
                break;
            } else if (i + 1 == list.size()) {
                list.add(e);
                break;
            }
        }
        return true;
    }

    public int size() {
        return list.size();
    }

    public boolean isEmpty() {
        return list.isEmpty();
    }
}

użycie:

class SummaryMsgHandler {

...

private final WindowCache<String, String, SummaryTrade> windowCache;

...

    @Override
    public void accept(SummaryTrade payload, MessageHeaders headers) {
        windowCache.updateCache(payload.getCode(), payload.getWindowDurationName(), payload);
        notifyUpdated(payload.getCode(),
                headers.getOrDefault(KafkaHeaders.RECEIVED_TIMESTAMP, "").toString());
    }

...
1

prostytutka, poprofilowałem sobie i wychodzi, że prawie całe to latency jest z powodu Kafki, a cache jest za darmo.

To jest tak, że on te 40 wiadomości dostaje w paczkach, czyli jakby w 1 momencie, w przeciwieństwie do innych serwisów, które dostają je strumieniowo, pojedynczo. I nagle musi je opędzlować i odesłać, że opędzlował. Problem, więc mam z troughput.

0

prostytutka, ustawiłem na producerze kafki tego serwisu ack = 0 i z 60 ms p99 zrobiło się 25

2

Jeżeli może być ulotne to polecam MySQL/MariaDB i tabelę na silniku MEMORY - czyli tradycyjna tabela SQL, ale całość siedzi w pamięci. Po restarcie serwera tworzy się ponownie automatycznie. Działa bardzo szybko, sądzę, że szybciej niż mechanizm zrobiony czysto w Javie. Instalacja MariaDB jest banalna, w Javie połączysz się przez JDBC.

https://mariadb.com/kb/en/memory-storage-engine/

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