Jaką bazę wybrać do cache'owania?

Jaką bazę wybrać do cache'owania?
straznik-tagu
  • Rejestracja:ponad 3 lata
  • Ostatnio:prawie 3 lata
  • Postów:74
0

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

  1. Zapis danych o takiej strukturze:
Kopiuj
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?

edytowany 3x, ostatnio: straznik-tagu
cerrato
Moderator Kariera
  • Rejestracja:około 7 lat
  • Ostatnio:około godziny
  • Lokalizacja:Poznań
  • Postów:8759
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ć.

edytowany 1x, ostatnio: cerrato
Patryk27
Moderator
  • Rejestracja:ponad 17 lat
  • Ostatnio:ponad rok
  • Lokalizacja:Wrocław
  • Postów:13042
3

Postgresql + garstka indeksów? :-)


Manna5
  • Rejestracja:prawie 6 lat
  • Ostatnio:około 5 godzin
  • Lokalizacja:Kraków
  • Postów:636
3

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


UglyMan
  • Rejestracja:około 6 lat
  • Ostatnio:około 3 lata
  • Postów:2206
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.

KamilAdam
  • Rejestracja:ponad 6 lat
  • Ostatnio:4 dni
  • Lokalizacja:Silesia/Marki
  • Postów:5505
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

Mama called me disappointment, Papa called me fat
Każdego eksperta można zastąpić backendowcem który ma się douczyć po godzinach. Tak zostałem ekspertem AI, Neo4j i Nest.js . Przez mianowanie
edytowany 1x, ostatnio: KamilAdam
UglyMan
Zgadzam się zwłaszcza z punktem drugim.
UglyMan
  • Rejestracja:około 6 lat
  • Ostatnio:około 3 lata
  • Postów:2206
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ą.

KamilAdam
  • Rejestracja:ponad 6 lat
  • Ostatnio:4 dni
  • Lokalizacja:Silesia/Marki
  • Postów:5505
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ć


Mama called me disappointment, Papa called me fat
Każdego eksperta można zastąpić backendowcem który ma się douczyć po godzinach. Tak zostałem ekspertem AI, Neo4j i Nest.js . Przez mianowanie
Inclouds
Redis był jednowątkowy. Od wersji 6.0 możesz mu włączyć wielowątkowość, co jeszcze bardziej zwiększa jego performance. Spotkałeś się kiedyś żeby redis był bottleneckiem? Bo dla mnie, jeżeli nie piszesz systemu pod miliony userów, to mało prawdziwy argument.
KamilAdam
Spotkałeś się kiedyś żeby redis był bottleneckiem? tak, ale to była wina architektów którzy chcieli współdzielić redisy między mikroserwisami. A system był dla 200k urządzeń nadających cały czas statystyki
straznik-tagu
  • Rejestracja:ponad 3 lata
  • Ostatnio:prawie 3 lata
  • Postów:74
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.

Robert Karpiński
  • Rejestracja:ponad 3 lata
  • Ostatnio:ponad rok
  • Postów:133
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.

straznik-tagu
  • Rejestracja:ponad 3 lata
  • Ostatnio:prawie 3 lata
  • Postów:74
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ć.

edytowany 2x, ostatnio: straznik-tagu
Robert Karpiński
  • Rejestracja:ponad 3 lata
  • Ostatnio:ponad rok
  • Postów:133
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

KamilAdam
Bardzo dobra odpowiedź, jedyne do czego bym się przyczepił że nie ma czegoś takiego jak najbardziej optymalna bo optymalna to już jest najlepsza z najlepszych :P
Robert Karpiński
:) Oczywista oczywistość !!!
cerrato
Moderator Kariera
  • Rejestracja:około 7 lat
  • Ostatnio:około godziny
  • Lokalizacja:Poznań
  • Postów:8759
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.


Patryk27
Moderator
  • Rejestracja:ponad 17 lat
  • Ostatnio:ponad rok
  • Lokalizacja:Wrocław
  • Postów:13042
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


edytowany 2x, ostatnio: Patryk27
straznik-tagu
60ms percentyl 99. Zaraz wrzucę ten kod może, tylko nie wyzywajcie od bootcampowiczów xD
Robert Karpiński
Jak już wchodzimy w te percentyle, to jak ten czas przetwarzania wygląda dla innych wartości ? Może jest wszystko OK, a czasami występuje taka anomalia. Za jaki okres czasowy jest ten pomiar ?
JM
  • Rejestracja:ponad 3 lata
  • Ostatnio:prawie 3 lata
  • Postów:93
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.
edytowany 2x, ostatnio: cerrato
UglyMan
  • Rejestracja:około 6 lat
  • Ostatnio:około 3 lata
  • Postów:2206
2
J.Muzykant napisał(a):
  1. Na mój sklerotyczny łeb bazy nie są do cachowania.

Chyba źle podchodzisz do pojęcia bazy danych

edytowany 1x, ostatnio: cerrato
JM
jest i taka możliwość, ale nie ja w tym wątku wymyśliłem SQLLite, czyli relacyjne z interpreterem SQL
JM
Jak się ma zupę do zjedzenia, to się poszukuje łyżki a nie zoptymalizowanego widelca.
straznik-tagu
  • Rejestracja:ponad 3 lata
  • Ostatnio:prawie 3 lata
  • Postów:74
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

edytowany 1x, ostatnio: straznik-tagu
straznik-tagu
  • Rejestracja:ponad 3 lata
  • Ostatnio:prawie 3 lata
  • Postów:74
0

Wrzucam kod mojego cache:

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

Kopiuj
@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);
        }
    }
}

Kopiuj
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:

Kopiuj
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());
    }

...
edytowany 2x, ostatnio: straznik-tagu
straznik-tagu
  • Rejestracja:ponad 3 lata
  • Ostatnio:prawie 3 lata
  • Postów:74
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.

edytowany 2x, ostatnio: straznik-tagu
Robert Karpiński
No to gratuluję !!!
straznik-tagu
no dzieki, tylko co z tym zrobić, musze wiecej instancji chyba dowalić... no to muszę wystawić to cache do osobnego serwisu ech
straznik-tagu
  • Rejestracja:ponad 3 lata
  • Ostatnio:prawie 3 lata
  • Postów:74
0

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

TR
  • Rejestracja:ponad 7 lat
  • Ostatnio:29 dni
  • Lokalizacja:700m n.p.m.
  • Postów:677
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/


DRY > SOLID (nie bierz tego zbyt poważnie)
edytowany 3x, ostatnio: TomRZ
JM
Sądzę, ze żadna SQL nie będzie szybsza od key-value w porównywalnej technologii.
TR
To najpierw sprawdź.
JM
ps. wydajna instalacja MySQL nie jest banalna
TR
Instalacja tylko na potrzeby silnika MEMORY jest banalna, nie potrzebujesz InnoDB i innych rzeczy. Tu się liczy tylko jedna zmienna konfiguracyjna o której pisze w dokumencie do którego dałem URL.
Robert Karpiński
Ale po co ? Założenia tego projektu były proste. Mało danych, krótki czas oczekiwania na odpowiedź. Zostało to zrealizowane z sukcesem przy pomocy Map. Zmienią się wymagania, to może przyjdzie czas na inne fajne klocki.
TR
W MariaDB na MEMORY będziesz miał opóźnienia typu od poniżej 1 ms do 3 ms, a nie 60 ms albo 25, największe opóźnienie będzie robić Java i JDBC, ale ile to już nie mam pojęcia.
Robert Karpiński
Dokładnie o to chodzi ! MariaDB może odpowiadać po nawet 0ms, ale co z tego jak narzut czasowy na komunikację z MariaDB będzie wynosiło ok 25ms !
TR
To trzeba jeszcze sprawdzić, być może nie jest tak źle.
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)