Ceph — od „na kolanie” do „production”

Wybór CEPH. Część 1

Mieliśmy pięć szaf, dziesięć przełączników optycznych, skonfigurowany BGP, kilkanaście SSD oraz mnóstwo dysków SAS w różnych kolorach i rozmiarach, a także proxmox i chęć umieszczenia całej statyki w własnym magazynie S3. To nie tak, że wszystko to było potrzebne do wirtualizacji, ale skoro zacząłem używać opensource — to idź ze swoją pasją aż do końca. Jedyna rzecz, która mnie niepokoiła — to BGP. Na świecie nie ma nikogo bardziej bezsilnego, nieodpowiedzialnego i niemoralnego niż wewnętrzne routowanie przez BGP. I wiedziałem, że wkrótce się w to zanurzymy.

Ceph — od „na kolanie” do „production”

Zadanie było proste — mieliśmy CEPH, który nie działał zbyt dobrze. Trzeba było zrobić to „dobrze”.
Klaster, który mi przypadł, był heterogeniczny, skonfigurowany w pośpiechu i praktycznie nie dostrojony. Składał się z dwóch grup różnych węzłów, z jedną wspólną siecią pełniącą rolę zarówno dla sieci klastrowej, jak i publicznej. Węzły były wyposażone w cztery typy dysków — dwa typy SSD, zebrane w dwa oddzielne zasady umieszczania, i dwa typy HDD o różnych rozmiarach, zebrane w trzecią grupę. Problem z różnymi rozmiarami został rozwiązany poprzez różne wagi OSD.

Samą konfigurację podzielono na dwie części — dostrajanie systemu operacyjnego i dostrajanie samego CEPH i jego ustawień.

Optymalizacja OS

Sieć

Wysokie opóźnienie miało wpływ zarówno podczas zapisu, jak i podczas balansowania. Przy zapisie — ponieważ klient nie otrzyma odpowiedzi o pomyślnym zapisie, dopóki repliki danych w innych grupach umieszczania nie potwierdzą sukcesu. Ponieważ zasady dystrybucji replik w mapie CRUSH miały jedną replikę na host, sieć była zawsze wykorzystywana.

Dlatego pierwszym krokiem postanowiłem nieco skonfigurować obecną sieć, jednocześnie próbując przekonać do przejścia na oddzielne sieci.

Na początek zmieniłem ustawienia kart sieciowych. Zacząłem od konfiguracji kolejek:

co było:

ethtool -l ens1f1

root@ceph01:~# ethtool -l ens1f1
Parametry kanałów dla ens1f1:
Maksima wstępne:
RX:     0
TX:     0
Inne:      1
Połączone:   63
Aktualne ustawienia sprzętowe:
RX:     0
TX:     0
Inne:      1
Połączone:   1
root@ceph01:~# ethtool -g ens1f1
Parametry pierścienia dla ens1f1:
Maksima wstępne:
RX:     4096
RX Mini:    0
RX Jumbo:   0
TX:     4096
Aktualne ustawienia sprzętowe:
RX:     256
RX Mini:    0
RX Jumbo:   0
TX:     256
root@ceph01:~# ethtool -l ens1f1
Parametry kanałów dla ens1f1:
Maksima wstępne:
RX:     0
TX:     0
Inne:      1
Połączone:   63
Aktualne ustawienia sprzętowe:
RX:     0
TX:     0
Inne:      1
Połączone:   1

Widać, że aktualne parametry są dalekie od maksimum. Zwiększyłem:

root@ceph01:~#ethtool -G ens1f0 rx 4096
root@ceph01:~#ethtool -G ens1f0 tx 4096
root@ceph01:~#ethtool -L ens1f0 combined 63

Kierując się doskonałym artykułem

https://blog.packagecloud.io/eng/2017/02/06/monitoring-tuning-linux-networking-stack-sending-data/

zwiększyłem długość kolejki wysyłania txqueuelen od 1000 do 10 000

root@ceph01:~#ip link set ens1f0 txqueuelen 10000

I zgodnie z dokumentacją samego ceph

https://ceph.com/geen-categorie/ceph-loves-jumbo-frames/

zwiększyłem MTU do 9000.

root@ceph01:~#ip link set dev ens1f0 mtu 9000

Dodałem do /etc/network/interfaces, aby wszystko powyższe ładowało się przy starcie

cat /etc/network/interfaces

root@ceph01:~# cat /etc/network/interfaces
auto lo
iface lo inet loopback

auto ens1f0
iface ens1f0 inet manual
post-up /sbin/ethtool -G ens1f0 rx 4096
post-up /sbin/ethtool -G ens1f0 tx 4096
post-up /sbin/ethtool -L ens1f0 combined 63
post-up /sbin/ip link set ens1f0 txqueuelen 10000
mtu 9000

auto ens1f1
iface ens1f1 inet manual
post-up /sbin/ethtool -G ens1f1 rx 4096
post-up /sbin/ethtool -G ens1f1 tx 4096
post-up /sbin/ethtool -L ens1f1 combined 63
post-up /sbin/ip link set ens1f1 txqueuelen 10000
mtu 9000

Po czym, zgodnie z tym samym artykułem, zacząłem starannie dostosowywać parametry jądra 4.15. Biorąc pod uwagę, że w węzłach jest 128G RAM, powstał pewien plik konfiguracyjny dla sysctl

cat /etc/sysctl.d/50-ceph.conf

net.core.rmem_max = 56623104  
# Maksymalny rozmiar bufora odbioru danych dla wszystkich połączeń 54M

net.core.wmem_max = 56623104
# Maksymalny rozmiar bufora przesyłania danych dla wszystkich połączeń 54M

net.core.rmem_default = 56623104
# Rozmiar bufora odbioru danych domyślnie dla wszystkich połączeń. 54M

net.core.wmem_default = 56623104
# Rozmiar bufora przesyłania danych domyślnie dla wszystkich połączeń 54M  
# dla każdego gniazda

net.ipv4.tcp_rmem = 4096 87380 56623104
# Wektor (minimum, domyślnie, maksimum) zmiennej w pliku tcp_rmem
# zawiera 3 liczby całkowite, które definiują rozmiar bufora odbioru gniazd TCP.
# Minimum: każde gniazdo TCP ma prawo do użycia tej pamięci już 
# w momencie jego utworzenia. Możliwość użycia takiego bufora 
# jest gwarantowana nawet przy osiągnięciu progu ograniczeń (umiarkowane ciśnienie pamięci).
# Rozmiar minimalnego bufora domyślnego wynosi 8 Kb (8192).
# Wartość domyślna: ilość pamięci dozwolona dla bufora 
# przesyłania gniazda TCP domyślnie. Ta wartość jest stosowana w miejsce
# parametru /proc/sys/net/core/rmem_default, używanego przez inne protokoły.
# Wartość używanego bufora domyślnego zazwyczaj (domyślnie) 
# wynosi 87830 bajtów. Określa to rozmiar okna 65535 z 
# domyślną wartością tcp_adv_win_scale i tcp_app_win = 0, 
# nieco mniejszy, niż określa przyjęta domyślnie wartość tcp_app_win.
# Maksimum: maksymalny rozmiar bufora, który może być automatycznie
# przydzielony dla odbioru gniazdu TCP. Ta wartość nie unieważnia maksymalnego, 
# określonego w pliku /proc/sys/net/core/rmem_max. Przy „statycznym”
# przydzielaniu pamięci za pomocą SO_RCVBUF ten parametr nie ma znaczenia.

net.ipv4.tcp_wmem = 4096 65536 56623104
net.core.somaxconn = 5000    
# Maksymalna liczba otwartych gniazd oczekujących na połączenia.

net.ipv4.tcp_timestamps=1
# Umożliwia używanie znaczników czasowych (timestamps) zgodnie z RFC 1323.

net.ipv4.tcp_sack=1
# Umożliwi wybiórcze potwierdzenia protokołu TCP

net.core.netdev_max_backlog=5000 (domyślnie 1000)
# maksymalna liczba pakietów w kolejce do przetworzenia, jeśli 
# interfejs odbiera pakiety szybciej, niż jądro może je przetworzyć.

net.ipv4.tcp_max_tw_buckets=262144
# Maksymalna liczba gniazd w stanie TIME-WAIT jednocześnie.
# Po przekroczeniu tego progu – „nadmiarowe” gniazdo jest niszczone i zapisywane
# komunikat w dzienniku systemowym.

net.ipv4.tcp_tw_reuse=1
# Umożliwiamy ponowne użycie gniazd TIME-WAIT w przypadkach,
# gdy protokół uznaje to za bezpieczne.

net.core.optmem_max=4194304
# Zwiększyć maksymalny całkowity bufor przestrzenny ALLOCATABLE
# mierzone w jednostkach stron (4096 bajtów)

net.ipv4.tcp_low_latency=1
# Umożliwia stosowi TCP/IP preferencje niskich czasów oczekiwania
# przed wyższą przepustowością.

net.ipv4.tcp_adv_win_scale=1
# Ta zmienna wpływa na obliczanie objętości pamięci w buforze gniazda,
# przydzielanej pod rozmiar okna TCP i bufor aplikacji.
# Jeśli wartość tcp_adv_win_scale jest ujemna, to do obliczenia rozmiaru
# używa się następującego wyrażenia:
# Bytes - bytes2 do potęgi -tcp_adv_win_scale
# Gdzie bytes – to rozmiar okna w bajtach. Jeśli wartość tcp_adv_win_scale
# jest dodatnia, to dla określenia rozmiaru używa się następującego wyrażenia:
# Bytes - bytes2 do potęgi tcp_adv_win_scale
# Zmienna przyjmuje wartość całkowitą. Wartość domyślna – 2, 
# czyli pod bufor aplikacji przydzielana jest ¼ część objętości definiowanej przez zmienną
# tcp_rmem.

net.ipv4.tcp_slow_start_after_idle=0
# mechanizm ponownego uruchamiania wolnego startu, który resetuje wartość okna 
# przeciążenia, jeśli połączenie nie było używane przez określony czas.
# Lepiej wyłączyć SSR na serwerze, aby poprawić wydajność 
# długoterminowych połączeń.

net.ipv4.tcp_no_metrics_save=1
# Nie zapisuj wyników pomiarów połączenia TCP w pamięci podręcznej po jego zamknięciu.

net.ipv4.tcp_syncookies=0
# Wyłącz mechanizm wysyłania syncookie

net.ipv4.tcp_ecn=0
# Wyraźne Powiadomienie o Przeciążeniu (Explicit Congestion Notification) w 
# połączeniach TCP. Używane do powiadamiania o wystąpieniu „zatoru” 
# na trasie do określonego hosta lub sieci. Może być używane do powiadamiania
# hosta-nadawcy o konieczności zmniejszenia prędkości przesyłania pakietów przez
# konkretnego routera lub zapory.

net.ipv4.conf.all.send_redirects=0
# wyłącza wysyłanie ICMP Redirect … innym hostom. Ta opcja musi być 
# zawsze włączona, jeśli host działa jako router jakiegokolwiek rodzaju.
# Nie mamy routingu.

net.ipv4.ip_forward=0
# Odpowiednio wyłączenie forwardingu. Nie jesteśmy bramą, docker na maszynach nie jest uruchomiony,
# nie potrzebujemy tego.

net.ipv4.icmp_echo_ignore_broadcasts=1
# Nie odpowiadamy na zapytania ICMP ECHO wysyłane jako pakiety rozgłoszeniowe

net.ipv4.tcp_fin_timeout=10
# określa czas przechowywania gniazda w stanie FIN-WAIT-2 po jego
# zamknięciu przez lokalną stronę. Domyślnie 60

net.core.netdev_budget=600 # (domyślnie 300)
# Jeśli wykonanie przerwań programowych nie trwa wystarczająco długo,
# tempo wzrostu danych przychodzących może przekroczyć możliwość jądra 
# opróżnienia bufora. W rezultacie bufory NIC przepełnią się, a ruch zostanie utracony.
# Czasami trzeba zwiększyć czas pracy SoftIRQs
# (przerwania programowe) z CPU. Za to odpowiada netdev_budget. 
# Wartość domyślna 300. Parametr sprawi, że proces SoftIRQ przetworzy
# 300 pakietów od NIC przed uwolnieniem CPU

net.ipv4.tcp_fastopen=3
# TFO TCP Fast Open
# jeśli zarówno klient, jak i serwer obsługują TFO, o czym informują za pomocą
# specjalnego flagi w pakiecie TCP. W naszym przypadku jest to placebo, po prostu
# wygląda ładnie)

Zsieć luster była przypisana do oddzielnych interfejsów sieciowych 10Gbps w osobnej sieci płaskiej. Każda maszyna miała zamontowane karty sieciowe z dwoma portami mellanox 10/25 Gbps, wpięte w dwa oddzielne switche 10Gbps. Agregacja odbywała się z wykorzystaniem OSPF, ponieważ bonding z lacp z jakiegoś powodu wykazywał łączną przepustowość maksymalnie 16 Gbps, podczas gdy ospf skutecznie wykorzystał w pełni obie dziesiątki na każdej maszynie. W dalszych planach było skorzystanie z ROCE na tych mellanoxach, aby zmniejszyć opóźnienia. Jak skonfigurowano tę część sieci:

  1. Ponieważ same maszyny mają zewnętrzne adresy IP na BGP, potrzebujemy odpowiedniego oprogramowania — ( a ściślej rzecz biorąc w momencie pisania artykułu był to frr=6.0-1 ) już zainstalowane.
  2. Na maszynach znajdowały się dwie karty sieciowe, każda z dwoma interfejsami — w sumie 4 porty. Jedna karta sieciowa z dwoma portami była podłączona do fabryki, na niej był skonfigurowany BGP, druga — z dwoma portami podłączona do dwóch różnych switchy, na nią został wpięty OSPF.

Szczegóły dotyczące konfiguracji OSPF: Główne zadanie — agregacja dwóch łączy i zapewnienie odporności na awarie.
dwa interfejsy sieciowe skonfigurowano w dwie proste płaskie sieci — 10.10.10.0/24 i 10.10.20.0/24

1: ens1f0:  mtu 9000 qdisc mq state UP group default qlen 1000
    inet 10.10.10.2/24 brd 10.10.10.255 scope global ens1f0

2: ens1f1:  mtu 9000 qdisc mq state UP group default qlen 1000
    inet 10.10.20.2/24 brd 10.10.20.255 scope global ens1f1

po których maszyny widzą się nawzajem.

DYSK

Kolejnym krokiem postanowiłem zoptymalizować pracę dysków. Dla SSD zmieniłem harmonogram na noop, dla HDD — deadline. Mówiąc ogólnie — NOOP działa na zasadzie "kto pierwszy wstał, ten lepszy", co po angielsku brzmi "FIFO (First In, First Out)". Żądania ustawia się w kolejce w miarę ich napływu. DEADLINE bardziej nadaje się do odczytu, a dodatkowo proces z kolejki ma praktycznie monopolowy dostęp do dysku w momencie operacji. Dla naszego systemu jest to idealne — bo tylko jeden proces, OSD daemon, pracuje z każdym dyskiem.
(Zainteresowani zgłębieniem tematu harmonogramu wejścia-wyjścia mogą przeczytać o nim tutaj:
http://www.admin-magazine.com/HPC/Articles/Linux-I-O-Schedulers

Ci, którzy wolą czytać po rosyjsku: https://www.opennet.ru/base/sys/linux_shedulers.txt.html)

W zaleceniach dotyczących tuningu Linuksa również sugeruje się zwiększenie nr_request

nr_requests
Wartość nr_requests określa liczbę żądań I/O, które są buforowane przed tym, jak harmonogram I/O wysyła/odbiera dane do urządzenia blokowego. Jeśli używasz karty RAID lub urządzenia blokowego, które może obsługiwać większą kolejkę, niż została ustawiona w harmonogramie I/O, zwiększenie wartości nr_requests może pomóc poprawić wydajność i zmniejszyć obciążenie serwera, gdy na serwerze występuje duża liczba operacji I/O. Jeśli używasz Deadline lub CFQ jako harmonogramu, zaleca się ustawienie wartości nr_requests na 2 razy wartość głębokości kolejki.

Ale! Sami twórcy systemu CEPH przekonują nas, że ich system priorytetów działa lepiej.

Ceph — od „na kolanie” do „production”

WBThrottle i/lub nr_requests

WBThrottle i/lub nr_requests
W magazynie plików operacje zapisu wykorzystują buforowane operacje wejścia/wyjścia; przynosi to szereg korzyści, jeśli dziennik magazynu plików znajduje się na szybszym nośniku. Klienci otrzymują powiadomienia, gdy dane są zapisane w dzienniku, a następnie są przekazywane na sam dysk danych w późniejszym czasie, korzystając ze standardowych funkcji systemu Linux. Umożliwia to dyskom OSD uzyskanie opóźnienia zapisu podobnego do SSD przy zapisie małych pakietów. Takie opóźnione zapisywanie również pozwala samemu rdzeniowi na reorganizację żądań operacji wejścia/wyjścia do dysku w nadziei na ich scalenie lub pozwolenie głowicom dysku wybrać bardziej optymalną ścieżkę na swoich talerzach. Ostateczny efekt polega na tym, że można uzyskać nieco więcej operacji wejścia/wyjścia z każdego dysku, niż byłoby to możliwe w przypadku bezpośrednich lub synchronicznych operacji wejścia/wyjścia.

Jednakże pojawia się pewien problem, jeśli liczba przychodzących zapisów do danego klastra Ceph wyprzedza możliwości leżących u podstaw dysków. W takim scenariuszu całkowita liczba operacji wejścia/wyjścia oczekujących na zapis na dysku może niekontrolowanie rosnąć, prowadząc do pełnych kolejek operacji wejścia/wyjścia oraz kolejek Ceph. Żądania odczytu szczególnie źle wpływają na sytuację, ponieważ utkną między żądaniami zapisu, które mogą wymagać kilku sekund na zapis na głównym dysku.

Aby pokonać ten problem, Ceph ma wbudowany w mechanizm przechowywania plików system dławienia zapisu opóźnionego (writeback) o nazwie WBThrottle. Został zaprojektowany w celu ograniczenia całkowitej liczby operacji wejścia/wyjścia zapisu opóźnionego, które mogą być kolejkowane i zaczynać proces spłaty wcześniej niż naturalnie by to nastąpiło dzięki aktywacji przez samo jądro. Niestety testy pokazują, że domyślne wartości nadal nie mogą ograniczyć istniejącego zachowania do poziomu, który mógłby zmniejszać wpływ na latencję operacji odczytu. Dostosowanie może zmienić to zachowanie i zmniejszyć ogólne długości kolejek zapisu, co może zminimalizować negatywne skutki. Istnieje jednak pewien kompromis: zmniejszając ogólną maksymalną liczbę dozwolonych zapisów w kolejce, można ograniczyć możliwość jądra do maksymalizacji efektywności porządkowania przychodzących żądań. Warto się zastanowić, co jest bardziej potrzebne w waszym konkretnym przypadku użycia, obciążeniach roboczych i dostosować do tych wymagań.

Aby zarządzać głębokością takiej kolejki zapisu opóźnionego, można albo zmniejszyć całkowitą maksymalną liczbę niewykonanych operacji wejścia/wyjścia, stosując ustawienia WBThrottle, albo zmniejszyć maksymalną wartość dla niewykonanych operacji na poziomie bloku w samym jądrze. Oba podejścia mogą skutecznie zarządzać tym samym zachowaniem, a to, co wybierzesz, będzie podstawą wprowadzenia tej konfiguracji.
Należy również zauważyć, że istniejąca w Ceph system priorytetów operacji jest bardziej efektywna dla krótszych żądań na poziomie dysku. Przy skracaniu ogólnej kolejki do tego dysku główna lokalizacja w kolejce przesuwa się do Ceph, który ma większą kontrolę nad tym, jaki priorytet ma operacja wejścia/wyjścia. Rozważmy następujący przykład:

echo 8 > /sys/block/sda/queue/nr_requests

http://onreader.mdl.ru/MasteringCeph/content/Ch09.html#030202

COMMON

I jeszcze kilka ustawień jądra, które mogą sprawić, że twoja maszyna będzie działać płynniej i wydobyć jeszcze trochę wydajności z sprzętu.

cat /etc/sysctl.d/60-ceph2.conf

 kernel.pid_max = 4194303
# W każdej maszynie jest po 25 dysków, więc zakładaliśmy, że będzie wiele procesów
kernel.threads-max=2097152
# Wątków, naturalnie, również.
vm.max_map_count=524288
# Zwiększyliśmy ilość sekcji mapy pamięci procesu.
# Jak wynika z dokumentacji na temat zmiennych jądra
# Sekcje mapy pamięci są używane jako efekty uboczne wywołania
# malloc, bezpośrednio za pomocą mmap, mprotect i madvise, a także podczas ładowania
# bibliotek wspólnych.
fs.aio-max-nr=50000000
# Dostosowujemy parametry wejścia-wyjścia
# Jądro Linuxa zapewnia funkcję asynchronicznego, nieblokującego wejścia-wyjścia (AIO),
# która pozwala procesowi inicjować wiele operacji wejścia-wyjścia
# jednocześnie, nie czekając na zakończenie którejkolwiek z nich.
# To pomaga zwiększyć wydajność aplikacji,
# które mogą nakładać przetwarzanie na wejście-wyjście.
# Parametr aio-max-nr określa maksymalną liczbę dozwolonych
# równoczesnych zapytań.
vm.min_free_kbytes=1048576
# Minimalny rozmiar wolnej pamięci, który należy utrzymywać.
# Ustawiony na 1 Gb, co jest wystarczające do działania systemu operacyjnego,
# i pozwala uniknąć OOM Killer dla procesów OSD. Mimo że pamięci jest sporo,
# zapas nie zaszkodzi.
vm.swappiness=10
# Mówimy, aby używać swap, jeśli pozostało 10% pamięci.
# Na maszynach z 128 GB RAM, 10% to 12 GB. Więcej niż wystarczające do pracy.
# Domyślny parametr 60% powodował spowolnienie systemu, wchodząc w swap,
# gdy jeszcze było sporo wolnej pamięci.
vm.vfs_cache_pressure=1000
# Zwiększamy z domyślnych 100. Zmuszamy jądro do aktywniejszego usuwania
# nieużywanych stron pamięci z cache.
vm.zone_reclaim_mode=0
# Umożliwia ustawienie mniej lub bardziej agresywnych podejść do
# odzyskiwania pamięci, gdy w strefie kończy się pamięć.
# Jeśli jest ustawione na zero, nie następuje odzyskiwanie strefy.
# Dla serwerów plikowych lub obciążeń roboczych
# korzystne jest, jeśli dane są cache'owane, więc zone_reclaim_mode
# należy pozostawić wyłączonym, ponieważ efekt cache'owania,
# prawdopodobnie będzie ważniejszy niż lokalizacja danych.
vm.dirty_ratio=20
# Procent pamięci operacyjnej, który można przydzielić "brudnym" stronom.
# Obliczaliśmy to na podstawie szacunków:
# W systemie jest 128 GB pamięci.
# Przykład około 20 dysków SSD, dla których w ustawieniach CEPH
# ustalono przydział 3 GB RAM na cache.
# Przykład około 40 dysków HDD, dla których ten parametr wynosi 1 GB.
# 20% z 128 to 25,6 GB. W sumie, w przypadku maksymalnego wykorzystania pamięci,
# dla systemu pozostanie 2,4 GB pamięci. Powinno wystarczyć, aby przetrwać i czekać
# na przybycie kawalerii DevOps, czyli osoby, która to wszystko naprawi.
vm.dirty_background_ratio=3
# Procent pamięci systemowej, którą można zapełnić brudnymi stronami, zanim
# procesy w tle pdflush/flush/kdmflush zapiszą je na dysku.
fs.file-max=524288
# Cóż, otwartych plików prawdopodobnie będziemy mieć znacznie więcej niż
# przewiduje domyślnie. 

Zanurzenie w CEPH

Ustawienia, na których warto się skupić:

cat /etc/ceph/ceph.conf

osd:
    journal_aio: true               # Trzy parametry, które włączają
    journal_block_align: true       # bezpośrednie i/o
    journal_dio: true               # do dziennika
    journal_max_write_bytes: 1073714824 # Nieco zwiększamy maksymalny rozmiar
                                        # jednorazowej operacji zapisu do dziennika
    journal_max_write_entries: 10000    # Ilość jednoczesnych wpisów
    journal_queue_max_bytes: 10485760000 
    journal_queue_max_ops: 50000
    rocksdb_separate_wal_dir: true      # Postanowiono utworzyć oddzielny wal
                                        # Nawet staraliśmy się zrealizować to zadanie
                                        # NVMe
    bluestore_block_db_create: true     # Oddzielne urządzenie dla dziennika
    bluestore_block_db_size: '5368709120 #5G'
    bluestore_block_wal_create: true
    bluestore_block_wal_size: '1073741824   #1G' 
    bluestore_cache_size_hdd: '3221225472   # 3G' 
                                            # Duża ilość pamięci RAM pozwala
                                            # na przechowywanie wystarczająco dużych objętości
    bluestore_cache_size_ssd: '9663676416   # 9G' 

    keyring: /var/lib/ceph/osd/ceph-$id/keyring
    osd_client_message_size_cap: '1073741824 #1G'
    osd_disk_thread_ioprio_class: idle
    osd_disk_thread_ioprio_priority: 7
    osd_disk_threads: 2 # liczba wątków demona na jeden dysk
    osd_failsafe_full_ratio: 0.95
    osd_heartbeat_grace: 5
    osd_heartbeat_interval: 3
    osd_map_dedup: true
    osd_max_backfills: 2 # liczba jednoczesnych operacji wypełniania na jeden OSD.
    osd_max_write_size: 256
    osd_mon_heartbeat_interval: 5
    osd_op_threads: 16
    osd_op_num_threads_per_shard: 1
    osd_op_num_threads_per_shard_hdd: 2
    osd_op_num_threads_per_shard_ssd: 2
    osd_pool_default_min_size: 1     # Cechy ciążenia. Bardzo szybko zabrakło nam
    osd_pool_default_size: 2         # miejsca, ponieważ tymczasowe
                                     # rozwiązanie polegało na zmniejszeniu liczby
                                     # replik danych
    osd_recovery_delay_start: 10.000000
    osd_recovery_max_active: 2
    osd_recovery_max_chunk: 1048576
    osd_recovery_max_single_start: 3
    osd_recovery_op_priority: 1
    osd_recovery_priority: 1            # parametry regulujemy w razie potrzeby
    osd_recovery_sleep: 2
    osd_scrub_chunk_max: 4

Niektóre parametry, które testowano w QA w wersji 12.2.12, są nieobecne w wersji ceph 12.2.2, na przykład osd_recovery_threads. Dlatego planowano aktualizację na produkcji do 12.2.12. Praktyka pokazała kompatybilność wersji 12.2.2 i 12.2.12 w jednym klastrze, co umożliwia przeprowadzenie aktualizacji z ciągłym działaniem.

Klastr testowy

Naturalnie, aby przeprowadzić testy, konieczne było posiadanie tej samej wersji, co na produkcji, lecz w momencie, gdy rozpocząłem pracę z klastrem, w repozytorium była tylko nowsza wersja. Zauważywszy, że różnice w wersji pomiędzy minorami nie są znaczne,1393 przeciwko 1436 (w nowej wersji) zdecydowaliśmy się zainicjować testy nowej (i tak czy tak trzeba będzie zaktualizować, po co jechać na starym złomie)

Jedyna rzecz, którą postaraliśmy się zachować ze starej wersji — to pakiet ceph-deploy, ponieważ część narzędzi (i część pracowników) była dostosowana do jego składni. Nowa wersja różniła się znacznie, ale nie wpływała na działanie samego klastra, więc pozostawiono wersję 1.5.39

Ponieważ zespół ceph-disk wyraźnie mówi, że jest przestarzały i zaleca korzystanie z zespołu ceph-volume — zaczęliśmy tworzyć OSD właśnie tą komendą, nie tracąc czasu na przestarzałe.

Plan był taki — stworzyć lustrzane odbicie z dwóch dysków SSD, na których umieścimy dzienniki OSD, które z kolei są umieszczone na dyskach SAS. W ten sposób zabezpieczymy się przed problemami z danymi w przypadku awarii dysku z dziennikiem.

Zaczęliśmy tworzyć klaster zgodnie z dokumentacją

cat /etc/ceph/ceph.conf

root@ceph01-qa:~# cat /etc/ceph/ceph.conf # Umieściliśmy wcześniej przygotowany plik konfiguracyjny
[client]
rbd_cache = true
rbd_cache_max_dirty = 50331648
rbd_cache_max_dirty_age = 2
rbd_cache_size = 67108864
rbd_cache_target_dirty = 33554432
rbd_cache_writethrough_until_flush = true
rbd_concurrent_management_ops = 10
rbd_default_format = 2
[global]
auth_client_required = cephx
auth_cluster_required = cephx
auth_service_required = cephx
cluster network = 10.10.10.0/24
debug_asok = 0/0
debug_auth = 0/0
debug_buffer = 0/0
debug_client = 0/0
debug_context = 0/0
debug_crush = 0/0
debug_filer = 0/0
debug_filestore = 0/0
debug_finisher = 0/0
debug_heartbeatmap = 0/0
debug_journal = 0/0
debug_journaler = 0/0
debug_lockdep = 0/0
debug_mon = 0/0
debug_monc = 0/0
debug_ms = 0/0
debug_objclass = 0/0
debug_objectcatcher = 0/0
debug_objecter = 0/0
debug_optracker = 0/0
debug_osd = 0/0
debug_paxos = 0/0
debug_perfcounter = 0/0
debug_rados = 0/0
debug_rbd = 0/0
debug_rgw = 0/0
debug_throttle = 0/0
debug_timer = 0/0
debug_tp = 0/0
fsid = d0000000d-4000-4b00-b00b-0123qwe123qwf9
mon_host = ceph01-q, ceph02-q, ceph03-q
mon_initial_members = ceph01-q, ceph02-q, ceph03-q
public network = 8.8.8.8/28 # Adres zmieniony, oczywiście ))
rgw_dns_name = s3-qa.mycompany.ru # I ten adres został zmieniony
rgw_host = s3-qa.mycompany.ru # I ten również
[mon]
mon allow pool delete = true
mon_max_pg_per_osd = 300 # Więcej niż trzysta grup umieszczonych
                          # na dysku to ryzyko
                     # choć parametr, oczywiście, zależy od liczby puli,
                     # ich rozmiarów i liczby OSD. Mówi się, że lepiej mieć mniej, ale zdrowych PG
                        # to również nie jest najlepszy wybór - cierpi na tym dokładność równoważenia
mon_osd_backfillfull_ratio = 0.9
mon_osd_down_out_interval = 5
mon_osd_full_ratio = 0.95 # Na SSD pamięci lokalnej dla ich
                          # dziennika jest tym samym urządzeniem co dla OSD
                          # postanowiliśmy, że 5% z dysku (który sam ma 1,2TB)
                          # powinno spokojnie wystarczyć, i koreluje z parametrem
                          # bluestore_block_db_size plus wariacje na dużych
                          # grupach umieszczonych
mon_osd_nearfull_ratio = 0.9
mon_pg_warn_max_per_osd = 520
[osd]
bluestore_block_db_create = true
bluestore_block_db_size = 5368709120 #5G
bluestore_block_wal_create = true
bluestore_block_wal_size = 1073741824 #1G
bluestore_cache_size_hdd = 3221225472 # 3G
bluestore_cache_size_ssd = 9663676416 # 9G
journal_aio = true
journal_block_align = true
journal_dio = true
journal_max_write_bytes = 1073714824
journal_max_write_entries = 10000
journal_queue_max_bytes = 10485760000
journal_queue_max_ops = 50000
keyring = /var/lib/ceph/osd/ceph-$id/keyring
osd_client_message_size_cap = 1073741824 #1G
osd_disk_thread_ioprio_class = idle
osd_disk_thread_ioprio_priority = 7
osd_disk_threads = 2
osd_failsafe_full_ratio = 0.95
osd_heartbeat_grace = 5
osd_heartbeat_interval = 3
osd_map_dedup = true
osd_max_backfills = 4
osd_max_write_size = 256
osd_mon_heartbeat_interval = 5
osd_op_num_threads_per_shard = 1
osd_op_num_threads_per_shard_hdd = 2
osd_op_num_threads_per_shard_ssd = 2
osd_op_threads = 16
osd_pool_default_min_size = 1
osd_pool_default_size = 2
osd_recovery_delay_start = 10.0
osd_recovery_max_active = 1
osd_recovery_max_chunk = 1048576
osd_recovery_max_single_start = 3
osd_recovery_op_priority = 1
osd_recovery_priority = 1
osd_recovery_sleep = 2
osd_scrub_chunk_max = 4
osd_scrub_chunk_min = 2
osd_scrub_sleep = 0.1
rocksdb_separate_wal_dir = true

# создаем мониторы
root@ceph01-qa:~#ceph-deploy mon create ceph01-q
# генерируем ключи для аутентификации нод в кластере
root@ceph01-qa:~#ceph-deploy gatherkeys ceph01-q
# Это если поштучно. Если у нас несколько машин доступны - те, которые описаны в конфиге в секции 
# mon_initial_members = ceph01-q, ceph02-q, ceph03-q
# можно запустить эти две команды в виде одной
root@ceph01-qa:~#ceph-deploy mon create-initial
# Положим ключи в указанные в конфиге места
root@ceph01-qa:~#cat ceph.bootstrap-osd.keyring > /var/lib/ceph/bootstrap-osd/ceph.keyring 
root@ceph01-qa:~#cat ceph.bootstrap-mgr.keyring > /var/lib/ceph/bootstrap-mgr/ceph.keyring 
root@ceph01-qa:~#cat ceph.bootstrap-rgw.keyring > /var/lib/ceph/bootstrap-rgw/ceph.keyring
# создадим ключ для управления кластером
root@ceph01-qa:~#ceph-deploy admin ceph01-q
# и менеджер, плагинами управлять
root@ceph01-qa:~#ceph-deploy mgr create ceph01-q

Pierwsza rzecz, na którą natknąłem się w pracy tej wersji ceph-deploy z klastrem wersji 12.2.12 — to błąd podczas próby utworzenia OSD z db na macierzy softwarowej —

root@ceph01-qa:~#ceph-volume lvm create --bluestore --data /dev/sde --block.db /dev/md0
blkid nie mógł wykryć PARTUUID dla urządzenia: /dev/md1

Rzeczywiście, blkid nie pokazuje PARTUUID, musiałem ręcznie tworzyć partycje:

root@ceph01-qa:~#parted /dev/md0 mklabel GPT 
# będzie wiele partycji, 
# bez GPT nie da się ich stworzyć
# rozmiar partycji podaliśmy w konfiguracji wyżej = bluestore_block_db_size: '5368709120 #5G'
# Mam 20 dysków pod OSD, nie chce mi się ręcznie tworzyć partycji
# dlatego zbudowałem pętlę
root@ceph01-qa:~#for i in {1..20}; do echo -e "nnnn+5Gnw" | fdisk /dev/md0; done

Wygląda na to, że wszystko jest gotowe, próbujemy ponownie utworzyć OSD i otrzymujemy następujący błąd (który, nawiasem mówiąc, nie występował w produkcji)

podczas tworzenia OSD typu bluestore bez wskazania ścieżki do WAL, ale ze wskazaniem db

root@ceph01-qa:~#ceph-volume lvm create --bluestore --data /dev/sde --block.db /dev/md0
 stderr: 2019-04-12 10:39:27.211242 7eff461b6e00 -1 bluestore(/var/lib/ceph/osd/ceph-0/) _read_fsid parsowalny uuid
 stderr: 2019-04-12 10:39:27.213185 7eff461b6e00 -1 bdev(0x55824c273680 /var/lib/ceph/osd/ceph-0//block.wal) otwarte otwarte otrzymano: (22) Nieprawidłowy argument
 stderr: 2019-04-12 10:39:27.213201 7eff461b6e00 -1 bluestore(/var/lib/ceph/osd/ceph-0/) _open_db dodaj urządzenie blokowe(/var/lib/ceph/osd/ceph-0//block.wal) zwróciło: (22) Nieprawidłowy argument
 stderr: 2019-04-12 10:39:27.999039 7eff461b6e00 -1 bluestore(/var/lib/ceph/osd/ceph-0/) mkfs nie powiodło się, (22) Nieprawidłowy argument
 stderr: 2019-04-12 10:39:27.999057 7eff461b6e00 -1 OSD::mkfs: ObjectStore::mkfs nie powiodło się z błędem (22) Nieprawidłowy argument
 stderr: 2019-04-12 10:39:27.999141 7eff461b6e00 -1 ** BŁĄD: błąd tworzenia pustego magazynu obiektów w /var/lib/ceph/osd/ceph-0/: (22) Nieprawidłowy argument

Jednak jeżeli na tym samym lustrze (lub w innym miejscu, według wyboru) utworzyć kolejna partycję pod WAL i wskazać ją przy tworzeniu OSD — wszystko pójdzie gładko (z wyjątkiem pojawienia się oddzielnego WAL, którego być może nie chcieliście).

Ale, ponieważ planowano w przyszłości przenieść WAL na NVMe, więc praktyka okazała się niezbędna.

root@ceph01-qa:~#ceph-volume lvm create --bluestore --data /dev/sdf --block.wal /dev/md0p2 --block.db /dev/md1p2

Utworzyliśmy monitory, menedżerów i OSD. Teraz chcemy je pogrupować różnie, ponieważ planujemy mieć dyski różnych typów — szybkie pule na SSD i duże, ale wolne na tradycyjnych dyskach SAS.

Załóżmy, że na serwerach jest po 20 dysków, pierwsza dziesiątka to jeden typ, druga — inny.
Początkowa, domyślna mapa wygląda tak:

ceph osd tree

root@ceph01-q:~# ceph osd tree
ID KLASA WAGA TYP NAZWA STATUS REWAGENT
-1 14.54799 korzeń domyślny
-3 9.09200 host ceph01-q
0 ssd 1.00000 osd.0 w górze 1.00000 1.00000
1 ssd 1.00000 osd.1 w górze 1.00000 1.00000
2 ssd 1.00000 osd.2 w górze 1.00000 1.00000
3 ssd 1.00000 osd.3 w górze 1.00000 1.00000
4 hdd 1.00000 osd.4 w górze 1.00000 1.00000
5 hdd 0.27299 osd.5 w górze 1.00000 1.00000
6 hdd 0.27299 osd.6 w górze 1.00000 1.00000
7 hdd 0.27299 osd.7 w górze 1.00000 1.00000
8 hdd 0.27299 osd.8 up 1.00000 1.00000
9 hdd 0.27299 osd.9 up 1.00000 1.00000
10 hdd 0.27299 osd.10 up 1.00000 1.00000
11 hdd 0.27299 osd.11 up 1.00000 1.00000
12 hdd 0.27299 osd.12 up 1.00000 1.00000
13 hdd 0.27299 osd.13 up 1.00000 1.00000
14 hdd 0.27299 osd.14 up 1.00000 1.00000
15 hdd 0.27299 osd.15 up 1.00000 1.00000
16 hdd 0.27299 osd.16 up 1.00000 1.00000
17 hdd 0.27299 osd.17 up 1.00000 1.00000
18 hdd 0.27299 osd.18 up 1.00000 1.00000
19 hdd 0.27299 osd.19 up 1.00000 1.00000
-5 5.45599 host ceph02-q
20 ssd 0.27299 osd.20 up 1.00000 1.00000
21 ssd 0.27299 osd.21 up 1.00000 1.00000
22 ssd 0.27299 osd.22 up 1.00000 1.00000
23 ssd 0.27299 osd.23 up 1.00000 1.00000
24 hdd 0.27299 osd.24 up 1.00000 1.00000
25 hdd 0.27299 osd.25 up 1.00000 1.00000
26 hdd 0.27299 osd.26 up 1.00000 1.00000
27 hdd 0.27299 osd.27 up 1.00000 1.00000
28 hdd 0.27299 osd.28 up 1.00000 1.00000
29 hdd 0.27299 osd.29 up 1.00000 1.00000
30 hdd 0.27299 osd.30 up 1.00000 1.00000
31 hdd 0.27299 osd.31 up 1.00000 1.00000
32 hdd 0.27299 osd.32 up 1.00000 1.00000
33 hdd 0.27299 osd.33 up 1.00000 1.00000
34 hdd 0.27299 osd.34 up 1.00000 1.00000
35 hdd 0.27299 osd.35 up 1.00000 1.00000
36 hdd 0.27299 osd.36 up 1.00000 1.00000
37 hdd 0.27299 osd.37 up 1.00000 1.00000
38 hdd 0.27299 osd.38 up 1.00000 1.00000
39 hdd 0.27299 osd.39 up 1.00000 1.00000
-7 6.08690 host ceph03-q
40 ssd 0.27299 osd.40 up 1.00000 1.00000
41 ssd 0.27299 osd.41 up 1.00000 1.00000
42 ssd 0.27299 osd.42 up 1.00000 1.00000
43 ssd 0.27299 osd.43 up 1.00000 1.00000
44 hdd 0.27299 osd.44 up 1.00000 1.00000
45 hdd 0.27299 osd.45 up 1.00000 1.00000
46 hdd 0.27299 osd.46 up 1.00000 1.00000
47 hdd 0.27299 osd.47 up 1.00000 1.00000
48 hdd 0.27299 osd.48 up 1.00000 1.00000
49 hdd 0.27299 osd.49 up 1.00000 1.00000
50 hdd 0.27299 osd.50 up 1.00000 1.00000
51 hdd 0.27299 osd.51 up 1.00000 1.00000
52 hdd 0.27299 osd.52 up 1.00000 1.00000
53 hdd 0.27299 osd.53 up 1.00000 1.00000
54 hdd 0.27299 osd.54 up 1.00000 1.00000
55 hdd 0.27299 osd.55 up 1.00000 1.00000
56 hdd 0.27299 osd.56 up 1.00000 1.00000
57 hdd 0.27299 osd.57 up 1.00000 1.00000
58 hdd 0.27299 osd.58 up 1.00000 1.00000
59 hdd 0.89999 osd.59 up 1.00000 1.00000

Stwórzmy własne wirtualne stojaki i serwery z blackjackiem i innymi:

root@ceph01-q:~#ceph osd crush add-bucket rack01 root #stworzono nowy root
root@ceph01-q:~#ceph osd crush add-bucket ceph01-q host #stworzono nowy host
root@ceph01-q:~#ceph osd crush move ceph01-q root=rack01 #przemieszczono serwer do innego stojaka
root@ceph01-q:~#osd crush add 28 1.0 host=ceph02-q # Dodano OSD do serwera

# Jeśli stworzyliśmy źle, można usunąć
root@ceph01-q:~# ceph osd crush remove osd.4
root@ceph01-q:~# ceph osd crush remove rack01

Problemy, z którymi się spotkaliśmy w bojowym klastrze, przy próbie stworzenia nowych hostów i przeniesienia ich do istniejącego stojaka — polecenie ceph osd crush move ceph01-host root=rack01 zamroziło się, a monitory zaczynały padać jeden po drugim. Przerwanie polecenia za pomocą CTRL+C przywracało klaster do stanu aktywnego.

Szukając problemu, natrafiono na taką sytuację: https://tracker.ceph.com/issues/23386

Rozwiązaniem było zrzucenie crushmap i usunięcie sekcji rule replicated_ruleset

root@ceph01-prod:~#ceph osd getcrushmap -o crushmap.row #Zrzucamy mapę w surowym formacie
root@ceph01-prod:~#crushtool -d crushmap.row -o crushmap.txt #przekładamy na czytelny
root@ceph01-prod:~#vim crushmap.txt #edytujemy, usuwając rule replicated_ruleset
root@ceph01-prod:~#crushtool -c crushmap.txt -o new_crushmap.row #kompilujemy z powrotem
root@ceph01-prod:~#ceph osd setcrushmap -i new_crushmap.row #ładujemy do klastra

Uwaga: ta operacja może spowodować rebalance grupy placement między OSD. U nas to wystąpiło, ale w bardzo małym stopniu.

Dziwnym zjawiskiem, które napotkaliśmy w testowym klastrze, było to, że po ponownym uruchomieniu serwera OSD zapominały, że zostały przeniesione do nowych serwerów i szafek, i wracały do root default.
W rezultacie, stworzywszy ostateczny schemat, w którym stworzyliśmy osobno root dla dysków SSD i osobno dla talerzowych, rozdzieliliśmy wszystkie OSD po szafkach i po prostu usunęliśmy default root. Po ponownym uruchomieniu OSD pozostały na swoich miejscach.
Później, przeszukując dokumentację, znaleźliśmy parametr, który odpowiada za to zachowanie. Jest on omówiony w drugiej części.

Jak tworzyliśmy różne grupy według typów dysków.

Na początek stworzyliśmy dwa rooty — dla SSD i dla HDD.

root@ceph01-q:~#ceph osd crush add-bucket ssd-root root
root@ceph01-q:~#ceph osd crush add-bucket hdd-root root

Ponieważ fizycznie serwery stoją w różnych szafkach — dla wygody stworzyliśmy szafki, a w nich już serwery.

# Стойки:
root@ceph01-q:~#ceph osd crush add-bucket ssd-rack01 rack
root@ceph01-q:~#ceph osd crush add-bucket ssd-rack02 rack
root@ceph01-q:~#ceph osd crush add-bucket ssd-rack03 rack

root@ceph01-q:~#ceph osd crush add-bucket hdd-rack01 rack
root@ceph01-q:~#ceph osd crush add-bucket hdd-rack01 rack
root@ceph01-q:~#ceph osd crush add-bucket hdd-rack01 rack

# Сервера
root@ceph01-q:~#ceph osd crush add-bucket ssd-ceph01-q host
root@ceph01-q:~#ceph osd crush add-bucket ssd-ceph02-q host
root@ceph01-q:~#ceph osd crush add-bucket ssd-ceph03-q host

root@ceph01-q:~#ceph osd crush add-bucket hdd-ceph01-q host
root@ceph01-q:~#ceph osd crush add-bucket hdd-ceph02-q host
root@ceph01-q:~#ceph osd crush add-bucket hdd-ceph02-q host

i porozrzucali dyski według ich typów po różnych serwerach.

root@ceph01-q:~# Dyski od 0 do 3 to SSD, mieszczą się w ceph01-q, umieszczamy je w serwerze 
root@ceph01-q:~# ssd-ceph01-q
root@ceph01-q:~#ceph osd crush add 0 1 host=ssd-ceph01-q
root@ceph01-q:~#ceph osd crush add 1 1 host=ssd-ceph01-q
root@ceph01-q:~#ceph osd crush add 2 1 host=ssd-ceph01-q
root@ceph01-q:~#ceph osd crush add 3 1 host=ssd-ceph01-q
root-ceph01-q:~# podobnie z innymi serwerami.

Rozrzucając dyski po rootach ssd-root i hdd-root, pozostawiliśmy root-default pusty, więc możemy go usunąć.

root-ceph01-q:~#ceph osd crush remove default

Następnie trzeba stworzyć zasady rozmieszczania, które będziemy przypisywać do tworzonych pul — w zasadach wskażemy, do których rootów można wkładać dane naszej puli oraz poziom unikalności repliki — na przykład repliki powinny być koniecznie na różnych serwerach lub w różnych szafkach (można nawet w różnych rootach, jeśli mamy taką konfigurację).

Przed wyborem typu warto przeczytać dokumentację:
http://docs.ceph.com/docs/jewel/rados/operations/crush-map/#crushmaprules

root-ceph01-q:~#ceph osd crush rule create-simple rule-ssd ssd-root host firstn
root-ceph01-q:~#ceph osd crush rule create-simple rule-hdd hdd-root host firstn
root-ceph01-q:~# Wskazaliśmy dwie zasady, w których dane są replikowane 
root-ceph01-q:~# między hostami - to znaczy replika powinna być na innym hoście,
root-ceph01-q:~# nawet jeśli są w tej samej szafce.
root-ceph01-q:~# W produkcji, jeśli to możliwe, lepiej rozdzielić hosty
root-ceph01-q:~# po szafkach i zdefiniować rozmieszczanie replik po szafkach:
root-ceph01-q:~# ##ceph osd crush rule create-simple rule-ssd ssd-root rack firstn

Tworzymy pule, w których w przyszłości będziemy przechowywać obrazy dysków naszej wirtualizacji — PROXMOX:

    root-ceph01-q:~# #ceph osd pool create {NAME} {pg_num}  {pgp_num}
    root-ceph01-q:~# ceph osd pool create ssd_pool 1024 1024 
    root-ceph01-q:~# ceph osd pool create hdd_pool 1024 1024

I wskazujemy tym pulom, jakie zasady umieszczania mają stosować

 root-ceph01-q:~#ceph osd crush rule ls # sprawdzamy listę zasad
    root-ceph01-q:~#ceph osd crush rule dump rule-ssd | grep rule_id #wybieramy potrzebny ID
    root-ceph01-q:~#ceph osd pool set ssd_pool crush_rule 2

Do wyboru liczby grup umieszczających należy podchodzić z wcześniejszym wyobrażeniem o swoim klastrze — ile mniej więcej OSD tam będzie, jaki procent danych (od całkowitej objętości) znajdzie się w pule, jaką łączną ilość danych przewidujemy.

W sumie nie powinno być więcej niż 300 grup umieszczających na dysk, a łatwiej będzie zbalansować mniejsze grupy umieszczające — to znaczy, że jeśli cały twój pul zajmuje 10 Tb i ma 10 PG — to balansowanie przez przenoszenie tero- bajtowych cegieł (pg) będzie problematyczne — łatwiej i równo przesypywać piasek o małej wielkości ziaren w wiadrach.

Jednak trzeba pamiętać, że im większa liczba PG — tym więcej zasobów zużywa się na obliczanie ich rozmieszczenia — zaczyna wykorzystywać się pamięć i CPU.

Przybliżone zrozumienie może dać kalkulator, udostępniony przez twórców dokumentacji CEPH.

Lista materiałów:

https://blog.packagecloud.io/eng/2017/02/06/monitoring-tuning-linux-networking-stack-sending-data
http://www.admin-magazine.com/HPC/Articles/Linux-I-O-Schedulers
http://onreader.mdl.ru/MasteringCeph/content/Ch09.html#030202
https://tracker.ceph.com/issues/23386
https://ceph.com/pgcalc/

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster