Фабрика картинок — как оно работает? Часть 2

Веб-разработка /
Наконец собрался написать вторую часть как и обещал в первой. В этой части хочется рассказать о клиентской стороне проекта.

Что используется:
Как говорил раньше проект полностью написан на Python (со вставками Cython'а). Вся информация о изображениях, пользователях, статистики — хранится в БД MySQL.

Для поиска (основного) и фильтра используется Sphinx-сервер. Клиент написанный для twisted txsphinx.

Для «лайков», кол-во просмотров изображения и кол-во скачиваний используется Redis. Так-же в Redis-е хранится топ-изображений (главная страница) и «похожие изображения» (страница самого изображения). Для twisted клиент txredis, найденный на просторах и немного доработанный под себя (пока не в паблике).

Веб: TwistedWeb с шаблонизатором Jinja2, рисуется все Bootsrap'ом и Jquery. Конец цепочки это Nginx.

Интересная часть:
Первое (и самое интересное) это было сделать фильтр изображений. Для начала был составлен список полей для поиска:
  1. Категории
  2. Минимальное разрешение изображения
  3. Ключевые слова
  4. Цвета
Фильтр решено было сделать с помощью Sphinx'а. Индексирование происходит через xmlpipe. Определение в sphinx очень простое:

source images {
	type					= xmlpipe2
	xmlpipe_command				= bin/sphinx.py --indexer=images
}

index images {
	source					= images
	path					= /var/lib/sphinx/data/images
	morphology         			= stem_enru
	charset_type				= utf-8

	min_word_len        			= 2
	min_infix_len 				= 3
	enable_star				= 1

	docinfo 				= extern

	html_strip = 1
	index_exact_words			= 1
	expand_keywords				= 0

	wordforms = images_wordforms.txt
}

Категории: MVA-атрибут, список ID. Так-же текстовый атрибут список названий категорий (для правильного поиска, прибавления веса результатам).

Минимальное разрешение изображения: Два атрибута width и height. Тут так-же все просто, поиск по диапазону каждого атрибута, от заданного пользователем до максимального (магическое число 10000).

Ключевые слова: Три текстовых атрибута title tags keywords. Title — заголовок изображения, результатам дается максимальный вес при попадании. Tags — список тегов изображения, средний вес. Keywords — набор ключевых слов (пользователь их не видит), взяты на странице изображения, могут содержать мусор. Маленький вес.

Цвета: Было самое сложное, расскажу подробней. Была создана палитра цветов {ID => RGB}. При добавлении изображения в базу, получаем список доминирующих цветов и приравниваем их к нашей палитре. Цвета изображения хранятся в БД с двумя значениями: ID цвета и процент занимаемый на изображении. В индексе десять MVA-атрибутов «c_X» где X — это номер от 0 до 9. В c_0 попадают все цвета изображения, в с_1 цвета с процентом >= 10, в c_2 цвета с процентом >= 20 и т.д.

Фильтр по цветам: При поиске изображений по цвету берутся все изображения у которых цвет находится в индексе c_1, далее считается вес цвета. При поиске по цвету с ID 2 (псевдокод):

setSelect('(IN(c_1,2)*1) + (IN(c_2,2)*1) + (IN(c_3,2)*1) + (IN(c_4,2)*1) + \
    (IN(c_5,2)*1) + (IN(c_6,2)*1) + (IN(c_7,2)*1) + (IN(c_8,2)*1) + (IN(c_9,2)*1) AS colors_weight')
setOrder('colors_weight DESC')

Возможно поиск по цветам сделан не самым оптимальным способом, но это самое удачное из того что я придумал.

Итог:
Скорость фильтра меня радует, сейчас это примерно 50-80 миллисекунд при 70 000 изображений. Если что-то еще интересно по проекту, пожалуйста спрашивайте, я буду рад рассказать. Опять же сам проект: http://picsfab.com

27 комментариев

webman
И снова отличный пост.
Cython, честно первый раз слышу, очень интересно узнать подробности.

PS сделай API =)
p0is0n
Cython — это транслятор питон кода в Си, подробней cython.org/ на основе Pyrex.

А что хотелось бы видеть в API?
p0is0n
Вот пример кода из фабрики

@cython.nonecheck(False)
def get_image_resize(unsigned int x, unsigned int y, unsigned int width, unsigned int height):
	cdef unsigned int a = x
	cdef unsigned int b = y

	if x > width:
		y = max(y * width / x, 1)
		x = width

	if y > height:
		x = max(x * height / y, 1)
		y = height

	# print a, b, x, y, width, height

	# Check
	if not (x == a and y == b):
		return (x, y)
webman
Основной плюс это видимо производительность?
API для получения картинок
mkw
Насчет API я за )))

Меня интересует хранение картинок в целом. Какие технологии использованы для этого? Радует скорость их загрузки, у меня интернет магазин и скорость загрузки как раз таки картинок у меня храмает, может смогу вылечить твоим советом.
p0is0n
Ну в хранении картинок все обычно, разбито на три вложения, progressive JPEG, для маленьких плохое качество. Отдача — правильно настроенный nginx. И все.
majesty
Хранить картинки на FS — плохая идея. Плохо всё — и много файлов в одной директории, и много вложенных директорий. Грубо говоря, каждый файл — это 1 объект. Каждый уровень вложенности возводит количество объектов в степень n+1. Со временем (когда картинок будет не 70К, а 700М) время на поиск файла и чтение его мета-информации (имя, права доступа, туда-сюда) будет значительно превышать время на чтение данных. nginx с кешем здорово помогает, но до поры до времени. Аплоад и первое чтение с диска всё равно будут тупить. Facebook для решения этой проблемы написал Haystack — хранилище файлов в BLOB'ах.
p0is0n
Думаю когда будет такой обьем проще взять cdn. Сейчас все хорошо летает и летать будет еще долго:)
p0is0n
А вообще нужно смотреть что именно хромает, диск, сеть или может что-то еще?
jandosul
Можете поверх нгинкса натянуть varnish cache
yrnt
Варниш поверх нжинкса оО
Что имелось ввиду?
jandosul
Varnish на 80-м, а нгинкс на любом другом порту например 8080. Фишка варниша в том, что всё что кэшируется будет хранится в памяти, а в случае нгинкса даже чтобы отдать статику будет сискол на дисковое чтение.
yrnt
Интересная мысль, правда что-то мне подсказывает, что это не самое лучшее решение. Я бы использовал варниш только как умный кэш для отдачи динамы.
jandosul
Что-то вам что-то плохо подсказывает ) Про варниш я узнал в туториале как разогнать вордпресс до 10 млн. хитов в день тыц
yrnt
Ммм… я его использую. Если есть esi, то замечал разницу в пользу varnish-а как реверс прокси. А так по опыту вижу, что nginx+php-fpm в принципе ни чуть не хуже. У нас одновременно скачивается 10 000 файлов по 1,5 мб.
p0is0n
То-есть вы предлагаете хранить кэш картинок в оперативной памяти?
jandosul
Статику, т.к. у вас сервис картинок не резонно хранить в памяти все картинки. Хотя в случае если будет собственная cdn, то можно попробовать раскидать для начала thumbnail-ы на шарды.
p0is0n
«Нормальная» ОС и так хранит горячие файлы в кэше (по мере свободности ОП).
jandosul
Кэш на файловой системе и кэш в памяти это разные вещи. Для чтения первого произойдёт syscall, а для второго нет. Варниш использует второе.
bsl_zcs
А это далеко не факт.

Я не полезу смотреть конкретную реализацию по исходникам нгинкса и варниша – не настолько оно интересно, но теоретически количество системных вызовов не должно отличаться вообще.

Если у нгинкса настроен достаточно большой пул файловых дескрипторов (open_file_cache) и настроена отдача статики через sendfile, то, в случае удачного попадания в кэш, вся разница между ними будет в том, что у одного цепочка действий с сокетом (упрощённо) «accept соединения / read запроса из сокета / write в сокет из кэша / close», а у второго вместо «write в сокет» будет «sendfile в сокет из открытого в прошлые разы файла».

Не говоря уже о том, что на каждый запрос происходит приличное количество сисколлов в любом случае. Сетевой стек в ядре, значит как минимум каждое из действий в цепочке выше – сисколл, а там наверняка кроме этого куча всего происходит. Не думаю, что там их меньше десятка-двух даже при самом идеальном попадании в кэш. Перед тем как разница в один вызов начнёт хоть на что-то заметно влиять, система в целом упрётся во что-нибудь другое, и гораздо раньше.
jandosul
Никто же не сравнивает нгинкс с варниш, варниш всего лишь кеш в памяти. Сетевой стек будет будь варниш на морде или нет. Выигрыш варниша не в паре тройке сэкономленных системных вызовах, это же очевидно. К тому же, варниш не панацея, хотите используйте хотите нет. Я лишь предложил использовать его на проекте, так как меня подкупила его архитектура с хранением в виртуальной памяти.
p0is0n
Как это разные вещи? +- 1 syscall это не так страшно.
jandosul
Я в исходники варниша не коммитил, но думаю выигрыш в 1 syscall это наверно не единственная оптимизация. А на вопрос скажу ещё проще, что быстрее чтение из памяти или чтение с диска?
bsl_zcs
А надо ставить вопрос так: «что быстрее, чтение из памяти приложения или из свободной памяти, в которой операционка кэширует файлы?».
mkw
Интересует реализация фильтра по цвету. Индексация картинок по цвету. Расскажите, если можно с примерами.
majesty
В посте есть это — для каждого цвета считаем процент его на изображении, в индексе ставим 10 полей от 0 до 10 с десятками процентов. Просто и изящно!
mkw
Опубликован клиент на Android для данного сайта
goo.gl/75JpO9
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.