Wpisy z January, 2010

Pobieranie nowych wiadomości przez JavaMail

I tylko nowych.

Stanąłem przed problemem jak efektywnie pobierać maile ze skrzynek POP3 za pomocą JavaMail w przypadku, gdy pobrane wiadomości nie są usuwane z serwera. Oczywiście głównym problemem jest wygenerowanie listy wiadomości, które znajdują się w skrzynce odbiorczej konta POP3, a nie znajdują się jeszcze w lokalnej bazie.

Do tej pory w Funneli stosowaliśmy prosty i, wydawałoby się, efektywny algorytm:

  1. Sprawdź kiedy pobrano ostatnią wiadomość z tego konta
  2. Wyszukaj nowsze wiadomości (uwzględniając kilkugodzinny margines)
  3. Pobierz wiadomości pomijając już pobrane

W Javie wyglądało to mniej więcej tak:

folder.open( Folder.READ_WRITE );
Date d = ... //tu pobranie z bazy daty ostatnio odebranego maila
SentDateTerm onlyRecent = new SentDateTerm( DateTerm.GE, d );
Message[] msgs = folder.search( onlyRecent ); //wyszukanie tylko nowszych, które pobiera pełne nagłówki - tu jest problem
for ( Message m: msgs ){
	com.sun.mail.pop3.POP3Folder pf = (com.sun.mail.pop3.POP3Folder)folder;
	String uid = pf.getUID( m );
	bool exists = ... //sprawdzenie czy uid już jest w bazie
	if ( ! exists ){
		//pobranie maila i zapis do bazy
	}
}

Okazuje się, że JavaMail, aby móc porównać daty, pobiera całe nagłówki wszystkich maili. W sieci znalazłem informację, ze średnio nagłówek zajmuje ok 1kB (nie sprawdzałem). Przy skrzynce z 5000 maili daje to jakieś 5MB do pobrania… co 30 sekund.
Na szczęście istnieje bardziej efektywny sposób – pobrać identyfikatory wszystkich maili za pomocą metody fetch() i nie wykonywać punktu 2, czyli porównywania dat:

folder.open( Folder.READ_WRITE );
Message[] msgs = folder.getMessages();
FetchProfile fp = new FetchProfile();
fp.add( UIDFolder.FetchProfileItem.UID );
folder.fetch( msgs, fp ); //wstępne pobranie identyfikatorów
for ( Message m: msgs ){
	com.sun.mail.pop3.POP3Folder pf = (com.sun.mail.pop3.POP3Folder)folder;
	String uid = pf.getUID( m );
	bool exists = ... //sprawdzenie czy uid już jest w bazie
	if ( ! exists ){
		//pobranie maila i zapis do bazy
	}
}

Na pierwszy rzut oka takie rozwiązanie wydaje się mniej efektywne. W końcu pobieramy pełną listę wiadomości i po kolei, dla każdej pozycji, sprawdzamy czy nie została już pobrana. W praktyce okazuje się, że różnica w ilości przesyłanych danych w przypadku gdy pobieramy całe nagłówki do wyszukiwania po dacie, a sytuacji gdy pobierane są jedynie unikalne identyfikatory, jest tak ogromna, że czas sprawdzania czy wiadomość znajduje się w lokalnej bazie jest praktycznie pomijalny.

W przeprowadzonych testach w skrajnych przypadkach zysk czasu był tysiąckrotny.