2017-10-19 84 views
11

Uruchamiając Pythona na projekcie Django, który komunikuje się z różnymi usługami sieciowymi, mamy problem, który czasami wymaga około 5 sekund zamiast zwykłego < 100 ms .Python `socket.getaddrinfo` trwa 5 sekund około 0,1% żądań

Zawęziłem to do czasu podjętego w funkcji socket.getaddrinfo - jest to wywoływane przez requests, kiedy łączymy się z usługami zewnętrznymi, ale wydaje się również, że wywołuje ono domyślne połączenie Django z polem bazy danych Postgres w klastrze. Po ponownym uruchomieniu uwsgi po wdrożeniu pierwsze przychodzące żądania będą wymagać 5 sekund na wysłanie odpowiedzi. Uważam również, że nasze zadania związane z selerem trwają 5 sekund, ale nie dodałem jeszcze do nich śledzenia statystyk.

Pisałem jakiś kod, aby odtworzyć problem:

import socket 
import timeit 

def single_dns_lookup(): 
    start = timeit.default_timer() 
    socket.getaddrinfo('stackoverflow.com', 443) 
    end = timeit.default_timer() 
    return int(end - start) 

timings = {} 

for _ in range(0, 10000): 
    time = single_dns_lookup() 
    try: 
     timings[time] += 1 
    except KeyError: 
     timings[time] = 1 

print timings 

Typowe wyniki {0: 9921, 5: 79}

Mój kolega już wskazał na potencjalne problemy wokół czasach IPv6 odnośników i dodał to do /etc/gai.conf:

precedence ::ffff:0:0/96 100 

To zdecydowanie poprawiła wyszukiwań z programów nie Pythona, takich jak curl wag Używamy go, ale nie z samego Pythona. Skrzynki serwerów są wyposażone w Ubuntu 16.04.3 LTS i mogę odtworzyć to na wanilii VM z Pythonem 2.

Co mogę zrobić, aby poprawić wydajność wszystkich wyszukiwań w Pythonie, aby mogły one pobierać < 1s ?

+0

Co powiesz na buforowanie wyników i odnowienie ich przy użyciu selera lub coś w tym stylu? –

+1

brzmi jak twój resolwent dns jest powolny, daj spróbować ncsd? – georgexsh

+0

@YaroslavSurzhikov Co zasugerowałbyś w pamięci podręcznej?A jak zasugerowałbyś, że pamięć podręczna powinna być aktualizowana i utrzymywana w stanie gorącym, aby kod serwera Python * nigdy * nie musiał uruchamiać powolnego żądania, z wyjątkiem aktualizacji pamięci podręcznej? – jamesc

Odpowiedz

8

5s to domyślny czas oczekiwania na wyszukiwanie DNS.

You can lower that.

Twój prawdziwy problem jest prawdopodobnie (cichy) UDP w sieci spadnie chociaż.

Edytuj: Eksperyment z resolution over TCP. Nigdy tego nie robiłem. Może ci pomóc.

2

Są dwie rzeczy, które można zrobić. Jednym z nich jest to, że nie zapytać adres IPv6, można to zrobić przez małpy łatania getaddrinfo

orig_getaddrinfo = socket.getaddrinfo 

def _getaddrinfo(host, port, family=0, type=0, proto=0, flags=0): 
    return orig_getaddrinfo(host, port, socket.AF_INET, type, proto, flags) 

socket.getaddrinfo = _getaddrinfo 

Następny można również użyć cache ttl oparty buforować wynik. Możesz użyć pakietu cachepy dla tego samego.

from cachetools import cached 
import socket 
import timeit 
from cachepy import * 
# or from cachepy import Cache 

cache_with_ttl = Cache(ttl=600) # ttl given in seconds 

orig_getaddrinfo = socket.getaddrinfo 

# @cached(cache={}) 
@cache_with_ttl 
def _getaddrinfo(host, port, family=0, type=0, proto=0, flags=0): 
    return orig_getaddrinfo(host, port, socket.AF_INET, type, proto, flags) 

socket.getaddrinfo = _getaddrinfo 

def single_dns_lookup(): 
    start = timeit.default_timer() 
    socket.getaddrinfo('stackoverflow.com', 443) 
    end = timeit.default_timer() 
    return int(end - start) 

timings = {} 

for _ in range(0, 10000): 
    time = single_dns_lookup() 
    try: 
     timings[time] += 1 
    except KeyError: 
     timings[time] = 1 

print (timings) 
2

chciałbym najpierw spróbować zrozumieć przyczynę powolność przed budowania pamięci podręcznej lub monkeypatching socket.getaddrinfo. Czy twoje serwery nazw są poprawnie skonfigurowane w /etc/resolv.conf? Czy widzisz utratę pakietów w sieci?

W przypadku strat, na które nie masz wpływu, uruchomienie serwera buforującego (nscd) spowoduje maskowanie, ale nie całkowite wyeliminowanie problemu.