Постоянный объект Python в памяти для сервера nginx/uwsgi

Я сомневаюсь, что это даже возможно, но вот проблема и предлагаемое решение (осуществимость предлагаемого решения является предметом этого вопроса):


У меня есть некоторые «глобальные данные», которые должен быть доступен для всех запросов. Я сохраняю эти данные в Riak и использую Redis в качестве уровня кэширования для скорости доступа (пока...). Данные разбиты примерно на 30 логических фрагментов, каждый размером около 8 КБ.

Каждый запрос требуется для чтения 4 из этих блоков по 8 КБ, в результате чего из Redis или Riak считываются 32 КБ данных. Это ДОПОЛНЕНИЕ к любым данным, специфичным для запроса, которые также необходимо прочитать (что довольно много).

Предполагая даже 3000 запросов в секунду (это не работающий сервер, поэтому у меня нет реальных цифр, но 3000ps — разумное предположение, может быть больше), это означает 96 КБ/с передачи от Redis или Riak в ДОПОЛНЕНИЕ к уже не -незначительные другие вызовы из логики приложения. Кроме того, Python анализирует JSON этих 8-килобайтных объектов 3000 раз в секунду.


Все это, особенно то, что Python постоянно выполняет десериализацию данных, похоже на полная трата, и совершенно элегантным решением было бы просто кэшировать десериализованные данные в нативном объекте в памяти в Python, который я могу периодически обновлять по мере того, как все эти «статические» данные становятся несвежий. Раз в несколько минут (или часов), вместо 3000 раз в секунду.

Но я не знаю, возможно ли это вообще. Вам действительно понадобится «всегда работающее» приложение, чтобы кэшировать любые данные в своей памяти. И я знаю, что это не относится к комбинации nginx+uwsgi+python (по сравнению с чем-то вроде узла) - данные в памяти python НЕ будут сохраняться во всех запросах, насколько мне известно, если только я не ужасно ошибся.

К сожалению, это система, которую я «унаследовал» и поэтому не могу вносить слишком много изменений с точки зрения базовой технологии, и при этом я недостаточно осведомлен о том, как работает комбинация nginx+uwsgi+python с точки зрения запуска процессов Python и их сохранения. Данные в памяти Python - это означает, что я МОГУ ужасно ошибаться в своем предположении выше!


Итак, прямой совет о том, будет ли работать это решение + ссылки на материалы, которые могли бы помочь мне понять, как будет работать nginx+uwsgi+python с точки зрения запуск новых процессов и выделение памяти очень помогло бы.

P.S:

  1. Просмотрел некоторую документацию по nginx, uwsgi и т. д., но еще не полностью понял последствия для моего варианта использования. Надеюсь добиться некоторого прогресса в этом направлении сейчас

  2. Если бы дело с памятью МОГЛО сработать, я бы отказался от Redis, поскольку я кэширую в нем ТОЛЬКО статические данные, о которых я упоминал выше. Это делает внутрипроцессный постоянный кеш Python в памяти еще более привлекательным для меня, сокращая одну движущуюся часть в системе и по крайней мере ЧЕТЫРЕ сетевых цикла туда и обратно на запрос.


person Dev Kanchen    schedule 15.03.2013    source источник
comment
Как выглядят эти данные? Насколько жесткая конструкция? Как часто он меняется?   -  person abarnert    schedule 16.03.2013
comment
Огромный набор пар ключ-значение с некоторой вложенностью. Пример структуры (в формате JSON): {ключ1: значение1, ключ2:{подключ1: [значение1, значение2, значение3], подраздел2: значение2}, ключ3: [значение1, значение2, значение3]}. Таким образом, разумное разнообразие данных, но, как я уже сказал, они довольно статичны, и я могу обновлять их только каждые несколько минут или даже каждый час.   -  person Dev Kanchen    schedule 16.03.2013


Ответы (4)


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

Однако есть несколько способов обойти это.

Часто достаточно одного уровня хранилища ключей и значений. А иногда наличие буферов фиксированного размера для значений (которые вы можете использовать непосредственно как объекты str/bytes/bytearray; все остальное, что вам нужно struct там или иным образом сериализовать) — это все, что вам нужно. В этом случае встроенная система кэширования uWSGI позаботится обо всем. тебе нужно.

Если вам нужен более точный контроль, вы можете посмотреть, как кеш реализован поверх SharedArea и настройте что-нибудь. Однако я бы не рекомендовал этого. По сути, это дает вам тот же API, который вы получаете с файлом, и единственные реальные преимущества по сравнению с простым использованием файла заключаются в том, что сервер будет управлять временем жизни файла; он работает на всех языках, поддерживаемых uWSGI, даже на тех, которые не поддерживают файлы; и это упрощает миграцию вашего пользовательского кеша в распределенный (многокомпьютерный) кеш, если вам это понадобится позже. Я не думаю, что что-то из этого относится к вам.

Еще один способ получить плоское хранилище значений ключа, но без буферов фиксированного размера, — это Python stdlib anydbm. Поиск ключ-значение настолько питонический, насколько это вообще возможно: он выглядит точно так же, как dict, за исключением того, что его резервная копия хранится в находящейся на диске базе данных BDB (или аналогичной), кэшируется соответствующим образом в памяти, а не хранится во внутренней памяти. хеш-таблица памяти.

Если вам нужно обрабатывать несколько других простых типов — все, что невероятно быстро распаковывается/собирается, например ints, — вы можете рассмотреть shelve.

Если ваша структура достаточно жесткая, вы можете использовать базу данных ключ-значение для верхнего уровня, но получить доступ к значениям через ctypes.Structure или де/сериализовать с помощью struct. Но обычно, если вы можете это сделать, вы также можете удалить верхний уровень, после чего все ваше дело будет просто одним большим Structure или Array.

В этот момент вы можете просто использовать обычный файл для хранения — либо mmap (для ctypes), либо просто open и read (для struct).

Или используйте общие ctypes объекты multiprocessing для прямого доступа к Structure из общей области памяти.

Между тем, если вам на самом деле не нужны все данные кеша все время, а только фрагменты время от времени, то это именно то, для чего нужны базы данных. Опять же, anydbm и т. д. могут быть всем, что вам нужно, но если у вас сложная структура, нарисуйте ER-диаграмму, превратите ее в набор таблиц и используйте что-то вроде MySQL.

person abarnert    schedule 15.03.2013
comment
Со смешанными данными (с плавающей запятой и строками) некоторые общие поиски тестов показали, что simplejson работает быстрее, чем cpickle. Вот почему я говорил о примере с JSON, а также о полке и т. д., которые на данный момент кажутся выходящими за рамки. И действительно ли чтение из файла будет быстрее, чем обращение к Redis, скажем, на локальном хосте? Конечно, учитывая, что мы говорим примерно только о 31 КБ статических данных, похоже, что они будут обращаться к дисковому кешу снова и снова, а не к шпинделю вообще. Кэширование uwsgi выглядит ОЧЕНЬ интересно и почти идеально подходит, если только я не столкнусь с некоторыми проблемами. - person Dev Kanchen; 16.03.2013
comment
Я предполагаю, что кеш uwsgi будет доступен внутри процесса из Python и, естественно, будет в несколько раз быстрее, чем Redis. Это кажется правильным, но я просто подтверждаю, что мое понимание основных правил здесь правильное. - person Dev Kanchen; 16.03.2013
comment
При 32 КБ разница между общей памятью, mmaped файлом или reading файлом с диска (как вы говорите, действительно дисковым кешем ОС) и т. д., вероятно, не будет иметь большого значения. Отправка его через TCP-сокет localhost также не должна многого добавить. (Но, очевидно, проверьте.) Что касается проблемы JSON-vs.-pickle, если у вас есть только один уровень (словарь, полный чисел с плавающей запятой и строк), вы не будете хранить смешанные данные; каждое значение представляет собой просто число с плавающей запятой или строку. Но вы этого не делаете; у вас есть несколько уровней, так что ... полки нет. - person abarnert; 16.03.2013
comment
Между тем, с несколькими уровнями иерархии кэш wUSGI также не является автоматическим выигрышем. Если у вас есть, скажем, 8 ключей верхнего уровня, каждый из которых содержит от 2 до 8 КБ данных под ними, он справится с этим довольно хорошо (хотя это означает, что вы будете использовать 64 КБ кэша, а не 32 КБ), но проблема в том, что вам все еще нужно сериализоваться в эти буферы размером 8 КБ. Если вы можете напрямую выполнить преобразование из буфера в ctypes.Structure, это максимально быстро, но это часто означает, например, использование массива пар вместо словаря, что означает линейный доступ к значениям (хотя с довольно маленькое N, похоже). - person abarnert; 16.03.2013
comment
О, еще одно: если у вас есть, скажем, 8 ключей верхнего уровня, каждый с 2-8 ключами второго уровня, … вы можете создать 8 кешей, каждый с 8 сегментами по 1 КБ (или, может быть, они все еще должны быть 4 КБ или каков бы ни был размер вашей страницы, я не уверен…), что означает, что вам нужно сериализовать только уровень 3 и ниже. В этот момент у вас может быть так мало структуры, что даже JSON подойдет? - person abarnert; 16.03.2013
comment
На этом этапе мне действительно нужно протестировать и посмотреть, какая комбинация параметров является наилучшей, что приведет к наиболее эффективному решению для меня (с учетом варианта использования). Я отмечу это как принятый ответ и добавлю дополнительные комментарии, когда узнаю больше. Спасибо! - person Dev Kanchen; 18.03.2013

Вы ничего не сказали о записи этих данных обратно, они статичны? В этом случае решение очень простое, и я понятия не имею, что происходит со всеми ответами «это невозможно».

Рабочие процессы Uwsgi являются постоянно работающими приложениями. Таким образом, данные абсолютно сохраняются между запросами. Все, что вам нужно сделать, это сохранить данные в глобальной переменной, вот и все. И помните, что это для каждого рабочего, а рабочие время от времени перезапускаются, поэтому вам нужны правильные стратегии загрузки/аннулирования.

Если данные обновляются очень редко (достаточно редко, чтобы при этом перезапустить сервер), вы можете сэкономить еще больше. Просто создайте объекты во время создания приложения. Таким образом, они будут созданы ровно один раз, а затем все рабочие будут ответвляться от мастера и повторно использовать одни и те же данные. Конечно, это копирование при записи, поэтому, если вы обновите его, вы потеряете преимущества памяти (то же самое произойдет, если python решит сжать свою память во время запуска gc, так что это не супер предсказуемо).

person letitbee    schedule 29.07.2017
comment
Ого, некропост. Не заметил дату раньше. Интересно, почему ТАК решил показать мне этот пост? - person letitbee; 31.07.2017

Я никогда не пробовал это сам, но не могли бы вы использовать SharedArea uWSGI? чтобы выполнить то, что вы после?

person Demian Brecht    schedule 15.03.2013
comment
Вы должны были бы построить много поверх этого, чтобы сделать его пригодным для использования. По сути, это дает вам тот же API, что и файловый объект; это оно. Как сказано в предупреждении в верхней части страницы, на которую вы ссылаетесь: SharedArea — это механизм очень низкого уровня. Чтобы найти более простую в использовании альтернативу, см. фреймворки Caching и Queue. - person abarnert; 16.03.2013

«Насколько мне известно, данные Python в памяти НЕ будут сохраняться во всех запросах, если только я не ошибаюсь».

вы ошибаетесь.

весь смысл использования uwsgi поверх, скажем, механизма CGI заключается в том, чтобы сохранять данные между потоками и экономить накладные расходы на инициализацию для каждого вызова. вы должны установить processes = 1 в файле .ini, иначе, в зависимости от настроек uwsgi, он может запускать более 1 рабочего процесса от вашего имени. зарегистрируйте env и найдите 'wsgi.multiprocess': False и 'wsgi.multithread': True, и все потоки uwsgi.core для одного рабочего должны показывать одни и те же данные.

вы также можете увидеть, сколько рабочих процессов и «основных» потоков под каждым у вас есть, используя встроенную функцию stats-server.

вот почему uwsgi предоставляет функции lock и unlock для управления хранилищами данных несколькими потоками.

вы можете легко проверить это, добавив маршрут /status в свое приложение, которое просто выводит JSON-представление вашего глобального объекта данных и просматривает его время от времени после действий, которые обновляют хранилище.

person jcomeau_ictx    schedule 28.07.2017