Ресурсы функции
То, что функция в один момент времени обрабатывает один запрос, а после выполнения инстанс функции подчищает все ресурсы, позволяет не задумываться о корректном закрытии таких ресурсов. Например, можно спокойно не закрыть открытый на чтение временный файл. И в этом действительно не будет никаких проблем, до тех пор пока в сервис не придет нагрузка.
В этом случае инстансы начнут переиспользоваться: после завершения обработки одного запроса в тот же инстанс будет передаваться на выполнение следующий запрос. С одной стороны это хорошо — сервису Cloud Functions не нужно пересоздавать инстансы, а значит пользователю не нужно будет ждать лишнее время «холодного старта». С другой стороны, именно в этот момент могут всплыть проблемы связанные с некорректной работой с ресурсами.
Кеширование данных в /tmp
Вы можете, и это будет верно, кешировать какие-то общие данные, которые не зависят от пользователя или запроса в
директории /tmp
. Но вот чего не стоит делать, так это хранить там пользовательские данные по одному путь.
Например, в функцию приходит запрос от user1
вы получаете его из базы и складываете эту информацию в файл /tmp/user
,
в надежде, что в следующий раз сможете сэкономить в этом месте время. Но вот в следующий раз приходит запрос от user2
,
и если вы не проверили, что в кеше у вас данные другого пользователя, то вы вернете пользователю user2
данные user1
.
Поэтому если вы и кешируете данные в /tmp
, то не забывайте добавлять к ключу какой-то идентификатор пользователя.
Также не стоит забывать, что в /tmp
есть ограничение на размер файлов по умолчанию в 512 МБ.
Открытие файлов
Лимит на количество открытых файлов в инстансе функции — 4096. Если вы открываете файлы в цикле, то вам нужно быть уверенным, что вы закрываете их после использования.
Убедиться в размере лимита на количество открытых файлов можно проверив значение ulimit -n
внутри функции.
А чтобы убедиться, что лимит не может быть превышен, можно воспользоваться следующим кодом:
import os
import subprocess
def handler(event, context):
stdout = check_ulimit()
print(stdout)
for i in range(100000):
os.open(f"/tmp/f{i}", os.O_CREAT | os.O_WRONLY)
def check_ulimit():
command = ['sh', '-c', 'ulimit -n']
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
stdout, _ = process.communicate()
return stdout.decode()
В ответе на запрос вы увидите значение 4096
, что означает, что лимит на количество открытых файлов в инстансе
функции равен 4096. А также функция вернет ошибку:
{
"errorMessage": "[Errno 24] Too many open files: '/tmp/f4091'",
"errorType": "OSError",
"stackTrace": [
" File \"/function/runtime/runtime.py\", line 231, in handle_event\n",
" File \"/function/code/index.py\", line 10, in handler\n"
]
}
Такое же поведение будет и в Go:
{
"errorMessage": "open /tmp/f4088: too many open files",
"errorType": "UserCodeError"
}
Видно, что у нас получилось что в примере Python откры ть 4091 файл, а вот 4092 файл уже вызвал ошибку. Разница в количестве файлов между Python и Go несущественна и объясняется различиями в реализации рантаймов.
Корректным решением работы с файлами в Python будет использование менеджера контекста with
:
import os
def handler(event, context):
for i in range(100000):
with open(f"/tmp/f{i}", "w") as f:
f.write("Hello, World!")