Чего следует избегать в моей облачной функции python, чтобы избежать утечки памяти? - PullRequest
0 голосов
/ 21 января 2019

Общий вопрос

My Python Cloud Function генерирует около 0,05 ошибки памяти в секунду - она ​​вызывается примерно 150 раз в секунду. У меня такое ощущение, что моя функция оставляет остатки памяти, что приводит к аварийному завершению ее экземпляров после обработки множества запросов. Что вы должны делать или не делать, чтобы экземпляр функции не потреблял «немного больше выделенной памяти» при каждом вызове? Я получил указание на документы, чтобы узнать, что я должен удалить все временные файлы , так как это записывает в память, но я не думаю, что написал что-либо.

Больше контекста

Код моей функции можно суммировать следующим образом.

  • Глобальный контекст: загрузите файл в Google Cloud Storage, содержащий список известных пользователей-агентов ботов. Создание клиента отчетов об ошибках.
  • Если User-Agent идентифицирует бота, верните код 200. В противном случае проанализируйте аргументы запроса, переименуйте их, отформатируйте их, отметку времени получения запроса.
  • Отправьте полученное сообщение в Pub / Sub в виде строки JSON.
  • Вернуть код 200

Я считаю, что мои экземпляры постепенно потребляют всю доступную память из-за этого графика, который я сделал в Stackdriver:

Memory usage of Cloud Function instances

Это тепловая карта использования памяти между экземплярами моей функции Cloud, красная и желтая, показывающие, что большинство моих экземпляров функции потребляют этот диапазон памяти. Из-за цикла, который, кажется, появляется, я интерпретировал его как постепенное заполнение памяти моих экземпляров, пока они не рухнут и не появятся новые экземпляры. Этот цикл остается, если я увеличиваю память, выделенную для функции, он просто увеличивает верхнюю границу использования памяти, за которой следует цикл.

Редактировать: фрагмент кода и более контекст

Запросы содержат параметры, которые помогают реализовать отслеживание на веб-сайте электронной торговли. Теперь, когда я копирую его, возможно, существует анти-шаблон, в котором я изменяю form['products'] во время итерации по нему, но я не думаю, что это как-то связано с потерей памяти?

from json import dumps
from datetime import datetime
from pytz import timezone

from google.cloud import storage
from google.cloud import pubsub
from google.cloud import error_reporting

from unidecode import unidecode

# this is done in global context because I only want to load the BOTS_LIST at
# cold start
PROJECT_ID = '...'
TOPIC_NAME = '...'
BUCKET_NAME = '...'
BOTS_PATH = '.../bots.txt'
gcs_client = storage.Client()
cf_bucket = gcs_client.bucket(BUCKET_NAME)
bots_blob = cf_bucket.blob(BOTS_PATH)
BOTS_LIST = bots_blob.download_as_string().decode('utf-8').split('\r\n')
del cf_bucket
del gcs_client
del bots_blob

err_client = error_reporting.Client()


def detect_nb_products(parameters):
    '''
    Detects number of products in the fields of the request.
    '''
    # ...


def remove_accents(d):
    '''
    Takes a dictionary and recursively transforms its strings into ASCII
    encodable ones
    '''
    # ...


def safe_float_int(x):
    '''
    Custom converter to float / int
    '''
    # ...


def build_hit_id(d):
    '''concatenate specific parameters from a dictionary'''
    # ...


def cloud_function(request):
    """Actual Cloud Function"""
    try:
        time_received = datetime.now().timestamp()
        # filtering bots
        user_agent = request.headers.get('User-Agent')
        if all([bot not in user_agent for bot in BOTS_LIST]):
            form = request.form.to_dict()
            # setting the products field
            nb_prods = detect_nb_products(form.keys())
            if nb_prods:
                form['products'] = [{'product_name': form['product_name%d' % i],
                                     'product_price': form['product_price%d' % i],
                                     'product_id': form['product_id%d' % i],
                                     'product_quantity': form['product_quantity%d' % i]}
                                    for i in range(1, nb_prods + 1)]

            useful_fields = [] # list of keys I'll keep from the form
            unwanted = set(form.keys()) - set(useful_fields)
            for key in unwanted:
                del form[key]

            # float conversion
            if nb_prods:
                for prod in form['products']:
                    prod['product_price'] = safe_float_int(
                        prod['product_price'])

            # adding timestamp/hour/minute, user agent and date to the hit
            form['time'] = int(time_received)
            form['user_agent'] = user_agent
            dt = datetime.fromtimestamp(time_received)
            form['date'] = dt.strftime('%Y-%m-%d')

            remove_accents(form)

            friendly_names = {} # dict to translate the keys I originally
            # receive to human friendly ones
            new_form = {}
            for key in form.keys():
                if key in friendly_names.keys():
                    new_form[friendly_names[key]] = form[key]
                else:
                    new_form[key] = form[key]
            form = new_form
            del new_form

            # logging
            print(form)

            # setting up Pub/Sub
            publisher = pubsub.PublisherClient()
            topic_path = publisher.topic_path(PROJECT_ID, TOPIC_NAME)
            # sending
            hit_id = build_hit_id(form)
            message_future = publisher.publish(topic_path,
                                               dumps(form).encode('utf-8'),
                                               time=str(int(time_received * 1000)),
                                               hit_id=hit_id)
            print(message_future.result())

            return ('OK',
                    200,
                    {'Access-Control-Allow-Origin': '*'})
        else:
        # do nothing for bots
            return ('OK',
                    200,
                    {'Access-Control-Allow-Origin': '*'})
    except KeyError:
        err_client.report_exception()
        return ('err',
                200,
                {'Access-Control-Allow-Origin': '*'})

1 Ответ

0 голосов
/ 23 июня 2019

Есть несколько вещей, которые вы можете попробовать (теоретический ответ, я еще не играл с CF):

  • явно удаляет временные переменные, которые вы выделяете в пути обработки ботов, которые могут ссылаться друг на друга, что не позволяет сборщику мусора памяти освободить их (см. https://stackoverflow.com/a/33091796/4495081): nb_prods, unwanted, form, new_form, friendly_names, например.

  • , если unwanted всегда одно и то же, сделать его глобальным.

  • удалить form перед переназначением его на new_form (старый объект form остается); также удаление new_form на самом деле не сильно сэкономит, так как на объект по-прежнему ссылается form. То есть изменить:

        form = new_form
        del new_form
    

    в

        del form
        form = new_form
    
  • явно вызывает сборщик мусора в памяти после публикации вашей темы и перед возвратом. Я не уверен, применимо ли это к CF или вызов сразу же вступил в силу или нет (например, в GAE это не так, см. Когда освободится память после выполнения запроса к бэкэнд-инстансам App Engine? ). Это также может быть излишним и может повредить производительности CF, посмотрите, как это работает для вас.

    gc.collect()
    
...