Zużycie pamięci przez aplikacje Django - przypadki

Wycieki pamięci przy wyszukiwania za pomocą Whoosh, duże zużycie pamięci przy generowanie map Sitemap przez Djangowski framework

Iterowanie po dużych zbiorach danych

Należy unikać pobierania dużej ilości pełnych rekordów, szczególnie tych o dużym rozmiarze (np. artykuły), gdy chcemy operować na niektórych kolumnach. Należy użyć wtedy metod takich jak .values() i .distinct() by nie pobierać zbędnych danych do pamięci RAM.

Whoosh

Odpalając JobMastera zastosowałem parę nowych rozwiązań, w tym Whoosha zgodnie z przykładem zaprezentowanym na blogu arnebrodowski.de. Najważniejszy jest widok wyszukiwania, gdyż zaprezentowane tam rozwiązanie powoduje wycieki pamięci (hosting Nginx/FastCGI), a dokładnie: searcher = ix.searcher(). Każde wyszukiwanie na stronie zwiększało ilość zużywanej pamięci (memstat -v). Rozwiązanie to nie tworzyć tego obiektu co żądanie, a schować jeden w settingsach aplikacji (gdzie dla pewności wrzuciłem resztę obiektów, które można ponownie wykorzystać):
WHOOSH_SCHEMA = fields.Schema(title=fields.TEXT(stored=True),
				content=fields.TEXT,
				slug=fields.ID(stored=True, unique=True))

storage = store.FileStorage(WHOOSH_INDEX)
ix = index.Index(storage, schema=WHOOSH_SCHEMA)
PARSER = QueryParser("content", schema=ix.schema)
SEARCHER = ix.searcher()
Rozwiązanie to można sprawdzić na prostych serwerach (za pomocą których wypatrzyłem problem). whoosh-client.py:
# -*- coding: utf-8 -*-
from socket import *
for i in range(0, 100):
	s = socket(AF_INET, SOCK_STREAM) #utworzenie gniazda
	s.connect(('localhost', 8889)) # nawiazanie polaczenia
	s.send('popularna fraza tutaj')
	res = s.recv(102400)
	s.close()
	print i
	print res
	print '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
whoosh-server-bad.py:
# -*- coding: utf-8 -*-
from socket import *

import simplejson as json

from whoosh import index, store, fields
from whoosh.qparser import QueryParser
from whoosh import store, fields, index

# dane indeksu whoosh
WHOOSH_INDEX = '/ściezka/do/indeksu/whoosh/'
WHOOSH_SCHEMA = fields.Schema(title=fields.TEXT(stored=True),
				content=fields.TEXT,
				slug=fields.ID(stored=True, unique=True))

# konfiguracja whoosh
storage = store.FileStorage(WHOOSH_INDEX)
ix = index.Index(storage, schema=WHOOSH_SCHEMA)
parser = QueryParser("content", schema=ix.schema)


# konfiguracja serwera
s = socket(AF_INET, SOCK_STREAM) #utworzenie gniazda
s.bind(('', 8889)) 
s.listen(5)

while 1:
	client,addr = s.accept() # odebranie polaczenia
	print 'Polaczenie z ', addr
	while 1:
		# odebranie frazy do wyszukania
		query = client.recv(1024)
		if not query: break
		
		# przygotowanie jej do wyszukiwania
		query = query.replace('+', ' AND ').replace(' -', ' NOT ').replace(', ', ' AND ')
		hits = []
		try:
			
			qry = parser.parse(query)
		except:
			qry = None
		if qry is not None:
			# TO CIEKNIE
			searcher = ix.searcher()
			hits = searcher.search(qry)
		
		# przygotowanie wyników do wysłania
		if len(hits) > 0:
			res = []
			for i in hits:
				res.append({'slug': i['slug'], 'title': i['title']})
			hits =  json.dumps(res)
		else:
			hits = '||'
		client.send(hits) # wyslanie danych do klienta
	client.close()
Oraz wersja "poprawna", whoosh-server.py różniąca się umieszczeniem "ix.searcher()":
# -*- coding: utf-8 -*-
from socket import *

import simplejson as json

from whoosh import index, store, fields
from whoosh.qparser import QueryParser
from whoosh import store, fields, index

# dane indeksu whoosh
WHOOSH_INDEX = '/ściezka/do/indeksu/whoosh/'
WHOOSH_SCHEMA = fields.Schema(title=fields.TEXT(stored=True),
				content=fields.TEXT,
				slug=fields.ID(stored=True, unique=True))

# konfiguracja whoosh
storage = store.FileStorage(WHOOSH_INDEX)
ix = index.Index(storage, schema=WHOOSH_SCHEMA)
searcher = ix.searcher()
parser = QueryParser("content", schema=ix.schema)

# konfiguracja serwera
s = socket(AF_INET, SOCK_STREAM) #utworzenie gniazda
s.bind(('', 8889))
s.listen(5)

while 1:
	client,addr = s.accept() # odebranie polaczenia
	print 'Polaczenie z ', addr
	while 1:
		# odebranie frazy do wyszukania
		query = client.recv(1024)
		if not query: break
		
		# przygotowanie jej do wyszukiwania
		query = query.replace('+', ' AND ').replace(' -', ' NOT ').replace(', ', ' AND ')
		hits = []
		try:
			qry = parser.parse(query)
		except:
			qry = None
		if qry is not None:
			hits = searcher.search(qry)
		
		# przygotowanie wyników do wysłania
		if len(hits) > 0:
			res = []
			for i in hits:
				res.append({'slug': i['slug'], 'title': i['title']})
			hits =  json.dumps(res)
		else:
			hits = '||'
		client.send(hits) # wyslanie danych do klienta
	client.close()
Wystarczy w terminalu odpalić zły serwer i przez np. "ps -aux" obserwować z innego terminala zużycie pamięci. W kolejnym terminalu odpalamy klienta i jak skończy patrzymy na pamięć. Kolejne fale wyszukiwań będą powodowały wzrost zużycia pamięci. W przypadku drugiej, "dobrej" wersji zużycie pamięci nie będzie przyrastać.

Sitemaps

Okazuje się także że generowane przez Djangowski framework Sitemaps mapy sitemap są w jakiś sposób "trzymane" w RAMie - jeżeli mamy dużą mapę, to jej wygenerowanie zajmie sporą ilość pamięci RAM. Dla przykładu moja mapa miała około 9000 elementów, co dawało plik sitemap.xml o rozmiarze 1,7MB. Jej wygenerowanie spowodowało wzrost zużycia pamięci przez aplikację z około 6 do 105MB. Kolejne żądania sitemapy nie podnosiły zużycia RAM. Rozwiązanie: używać statycznych map.
blog comments powered by Disqus

Kategorie

Strony