Дневник разработчицы
407 subscribers
111 photos
9 videos
14 files
145 links
Откровенные истории о проектах, работе, учебе и открытиях в мире программирования. Присоединяйтесь и вдохновляйтесь вместе со мной!
Download Telegram
Немного про инфру. Один мой друг посоветовал для отслеживания проблем очень интересный инструмент. Давно советовал, но я трусила вникать в новый инструмент. Все новое пугает, это ж надо разбираться.
https://newrelic.com/

Что я сделала, чтобы запустить его в итоге:
1. Зарегистрировалась. Дошла до документации по установке https://docs.newrelic.com/docs/agents/python-agent/installation/standard-python-agent-install, где при добавлении нового приложения, мне сервис выгрузил конфиг newrelic.ini (кнопка add python data)
2. Внесла в список установок newrelic, загрузила в проект файл из пункта 1. В докер файле все свои запуски обернула вот так
NEW_RELIC_CONFIG_FILE=newrelic.ini newrelic-admin run-program hypercorn monitoring_admin.asgi:application --bind 0.0.0.0:8000 --workers 3 

3. Пересобрала контейнер, запустила локально. Кликнула "See you data" и следующие кнопки на странице "Add your Python application data"
4. Бинго, получила данные.
5. Потом еще настроила окружения. В файле конфига можно найти настройки для разных окружений:

monitor_mode = false

[newrelic:test]
monitor_mode = false

[newrelic:staging]
app_name = noctua (Staging)
monitor_mode = true

[newrelic:production]
monitor_mode = true


Чтобы они заработали, нужно добавить переменную окружения NEW_RELIC_ENVIRONMENT с указанием как раз названия типа окружения из конфига. Локально я отключила передачу данных, а вот на сервере установила переменной значение production.

6. Еще я попробовала обернуть asgi по примеру из документации https://docs.newrelic.com/docs/agents/python-agent/python-agent-api/asgiapplication-python-agent-api, но неудачно.
Добавила в asgi.py следующую строку

application = newrelic.agent.ASGIApplicationWrapper(application)

На что получила ошибку:


Traceback (most recent call last):
File "/usr/local/lib/python3.8/site-packages/hypercorn/asyncio/context.py", line 28, in _handle
await invoke_asgi(app, scope, receive, send)
File "/usr/local/lib/python3.8/site-packages/hypercorn/utils.py", line 214, in invoke_asgi
asgi_instance = app(scope)
File "/usr/local/lib/python3.8/site-packages/newrelic/api/asgi_application.py", line 329, in nr_asgi_wrapper
return nr_async_asgi(*_bind_receive_send(*args, **kwargs))
TypeError: _bind_receive_send() missing 2 required positional arguments: 'receive' and 'send'


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


Кстати, https://t.me/lovely_it_hell канальчик друга, который мне посоветовал данный инструмент.

А ну, и результаты. С помощью инструмента заметила, что одна из rss-лент уж очень быстро отдает ответ. Посмотрела в ленту - свежие материалы в ней есть, а у меня их нет. Поняла, что зря я не передаю user-agent при запросе лент. Добавила, подтянулись новые материалы из нескольких лент. Посмотрим, что будет дальше, но теперь запросы отправляю с разными юзер-агентами. #Tools
Нельзя просто так взять и запустить! Короче, я не смогла нормально подружить asgi с new relic, как я ни крутила, ничего нормально не завелось. В итоге, я плюнула и решила попробовать middleware. Про то что это и как этим пользоваться хорошо рассказано в видео https://youtu.be/Qe8YI5wXJng
Я просто обернула функции:


newrelic.agent.initialize('newrelic.ini')


class FirstMiddleware:
@newrelic.agent.web_transaction()
def __init__(self, get_response):
self._get_response = get_response

@newrelic.agent.web_transaction()
def __call__(self, request):
response = self._get_response(request)
return response

Название не придумала, поэтому как есть. Оно работает!
Обнаружила свою проблему с долгой загрузкой админки. Выяснилось, что проблема в запросе count(*) для всех объектов (по 8 секунд). Теперь придумываю как ее решать. #Tools
Инфра, продолжение...

Заходишь в логи базы данных, а там каждую секунду неуспешные попытки авторизации. И это точно не ты:

2020-09-21 22:30:00 UTC:59.108.92.240(43526):postgres@postgres:[17605]:FATAL: password authentication failed for user "postgres"
2020-09-21 22:30:00 UTC:59.108.92.240(43527):postgres@postgres:[17649]:DETAIL: Password does not match for user "postgres".
Connection matched pg_hba.conf line 13: "host all all 0.0.0.0/0 md5"


У меня на этом инстансе висело 3 базы данных. Одну из которых использовала lambda. В общем, нельзя так просто взять и закрыть доступ от нехороших людей к бд, при этом оставив его для своей функции в лямбде. В этом весь AWS. Пришлось утягивать эту базу на отдельный инстанс на отдельном аккаунте (чтобы бесплатно, да). После чего я уже смогла закрыть лавочку для тех кул-хацкеров, что хотят меня ломануть. Подлецы! Зато aws классно подтягивает локальный ip, когда настраиваешь security group. Удобно.
Ну, и на всякий случай дефолтного пользователя для нового инстанса назвала сразу не postgres)

А и еще. Гуглила вчера весь вечер как решать проблему с count(), вот нет хорошего решения( Все костыли. Зато потыкала переопределение queryset и manager в джанге. В общем, тут описан вариант решения. Я его немного подкорректировала, потому что для постгрес там были явные ошибки. Не уверена, что у меня получилось на 100% верно. Выглядит как очень фу, на самом деле:

class ApproxCountPgQuerySet(models.query.QuerySet):
"""approximate unconstrained count(*) with reltuples from pg_class"""

def count(self):
if self._result_cache is not None and not self._iter:
return len(self._result_cache)

if hasattr(connections[self.db].client.connection, 'pg_version'):
query = self.query
if query.high_mark is None and query.low_mark == 0 and not any([
query.where,
query.select,
query.group_by,
hasattr(query, "having"),
query.distinct
]):
# If query has no constraints, we would be simply doing
# "SELECT COUNT(*) FROM foo". Monkey patch so the we get an approximation instead.
parts = [p.strip('"') for p in self.model._meta.db_table.split('.')]
cursor = connections[self.db].cursor()
if len(parts) == 1:
cursor.execute("select reltuples::bigint FROM pg_class WHERE relname = %s", parts)
else:
cursor.execute(
"select reltuples::bigint FROM pg_class c JOIN pg_namespace n on (c.relnamespace = n.oid) WHERE n.nspname = %s AND c.relname = %s",
parts)
return cursor.fetchall()[0][0]
return self.query.get_count(using=self.db)


Но пока это не критичная проблема, поэтому отложу ее на потом. #Django
Интерфейс... Все мои надежды на просто решение через тележку не сбылись. Я подступила к этой задаче, но осознала, что слишком много уже функционала и настроек, чтобы можно было легко и непринужденно сделать все через телеграмм.
В итоге я сегодня сделала еще одну маленькую фичу, а завтра пойду начинать проходить отложенный курс по JS, дабы сделать красивую и удобную админку.

Про кнопки в телеграмме.
Я пользуюсь api телеграмма без дополнительных библиотек. Сегодня добралась до реализации своей кнопки к отправленным сообщениям, чтобы удалять лишнее и помечать удаленное как ошибку.
Для этого в данные, отправляемые методом sendMessage я добавила:

[{"text": "Мимо!", "callback_data": f"mistake={material.id}"}]
]})

json.dumps(), чтобы получился json-объект. Каждый ряд кнопок - отдельный список [] внутри основного списка для inline_keyboard.

callback_data - данные, которые телеграмм отправляет в систему при клике на кнопку в виде callback_query. На такой запрос можно "ответить" (ограничено время на ответ) с помощью метода answerCallbackQuery. callback_query приходит со всеми данными о сообщении, для которого была нажата кнопка. Поэтому после получения запроса с кнопки я выполняю следующие действия:
1.
Отправляю ответ "принято" с помощью метода answerCallbackQuery
2. Устанавливаю статус для данного сообщения в своей базе, что оно ошибочно.
3. С помощью метода deleteMessage и данных из callback_query удаляю ошибочное сообщение из чата.

Мои функции для работы с этими методами:


url = bot_url + "/answerCallbackQuery"
data = {
"callback_query_id": callback_query_id,
"text": text,
"show_alert": alert
}
response = post_request(url, data)
return response

def delete_message(chat_id, message_id):
url = bot_url + "/deleteMessage"
data = {
"chat_id": chat_id,
"message_id": message_id
}
response = post_request(url, data)
return response

Задача готова и зарелижена. Ни дня без релиза) #Python, #Работа
Выглядит вот так (кнопка "Мимо!")
Инфра. Подключение SSL-сертификата. AWS Elastic Beanstalk

AWS продолжает меня заставлять изучать нюансы инфраструктуры. 2 дня я пыталась решить нехитрую задачку изменения конфигов для nginx для работы сертификата на моем домене.

Открываю документацию, делаю как написано - не работает. Перерываю кучу статей в интернете... Пробую... Не работает. Вручную через ssh запускаем - работает! Ну ладно...
Все варианты в документации и в статьях сводятся к команде files, которая в нужном месте создает нужный файл.

Пытаюсь через команды докера положить туда, куда надо файлы конфигов. Не работает.

Во всех случаях в нужном месте после деплоя файла просто нет. В логах никак не могу найти информацию почему.

Пытаюсь еще 100500 раз по-всякому сделать как написано в документации - не работает...

Нахожу НЕРАБОЧЕЕ решение на стаковерфлоу - положить конфиги в директорию .ebextensions/nginx. Конечно, он не работает. Но! внезапно, aws в ЛОГАХ подсказывает как можно:
 Instance deployment: Elastic Beanstalk ignored your '.ebextensions/nginx' configuration directory. To include these configuration files, move them to '.platform/nginx'.

Пробую... Работает!
Я нигде в документации не видела такого варианта.
Сами конфиги для nginx легко гуглятся, если что.
Блин, безумие и отвага. #AWS
🔥1
Двойная проверка на дубли в последовательном выполнении кода внезапно не срабатывает. Система создает 2, а иногда даже 3 одинаковых объекта. В логах ошибок нет. Раньше проблема не наблюдалась.

Что изменилось? Обновилась минорная версия postgresql (с 11.6 до 11.8)
Да и все, пожалуй, из того, что может быть связано.

Переставила порядок проверок, все равно давно хотела это сделать. Сначала с помощью метода django get_or_create() создается уникальная связка тема + материал, потом идет проверка на наличие похожих материалов с помощью триграмм:
    similar_messages = TopicMessage.objects.annotate(
similarity=TrigramSimilarity('channel_message__text', text)
).filter(similarity__gt=0.5, message__date__gte=day_ago, topic=topic)


Слежу теперь поможет ли. #Django
Так. Итого. Опять инфра.

Если вдруг начинается какая-то дичь, подкинь дровишек. Нашла истоки проблемы со своими дублями. Да, был косяк (или не косяк) в логике, из-за которого одна и та же тема обрабатывалась двумя воркерами одновременно. Но самая большая проблема шла из-за того, что select, который раньше отрабатывал быстрее, чем за секунду, вдруг стал залипать на 40 секунд.

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

Я поменяла машину для бд с бесплатной t2micro (1-1) на платную t2small (1-2).

Теперь все вернулось на круги своя. Мда. Растем. #AWS
Если честно, иногда хочется сложить лапки.
Когда вот вижу проблему, но хз как ее решать. Особенно нагнетает то, что ведь этим пользуются люди, они видят косяк системы. Поэтому хочется как можно быстрее его исправить, а не получается. Особенно, когда такая проблема не одна, а их много, и они падают одна за другой на мою голову.
Самое еще неприятное то, что спросить то в общем, не у кого. Вернее, конечно, есть у кого, но никто не знает систему и все ее нюансы так, как я, никто не сможет понять проблему "удаленно". Если уж я зашла в тупик, то человеку со стороны еще сложнее понять, в чем моя боль.
Пока я со всем справляюсь.

Кстати, логи спасают. И да, new relic тоже сильно помог в выявлении проблемы.
Одна из функций, которую я использую для логирования для подсчета времени на выполнение функции (подсмотрела на одном из курсов):


import time

def measure_time(func):
@wraps(func)
def wrap(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
add_log(f"{func.__name__} work time", f'{elapsed:0.2f} sec')
return result

return wrap
#Python
Сегодня радостно писала новый функционал.
Вжух-вжух - там модельку дописать, там вывод доработать, там обработку данных на входе. Кайф.
Проверила, отдебажила. Все было прекрасно.
Деплою, полетело. Но тут хоба, часть сообщений приходит в новом формате, а часть в старом.
Данные работают, сохраняются, но почему-то много чего идет не так.
Ну, здравствуйте. Смотрю в логах, а там проблемных данных нет. Вижу корректные отправки, а проблемных нет. Какой-то корабль-призрак мне их присылает, чтобы позлить меня.
Пробовала перекатывать несколько раз, чистить очередь, но ничего не помогло.
Пришлось использовать тяжелую артиллерию и переключать тип деплоя на вариант с переходом на новый инстанс, чтобы все точно заглохло.
Проблема скорее всего в кастомной настройке nginx. Потому что раньше такого не наблюдалось.
Надо разбираться как это чинить. Опять ковырять инфру. Как же надоело.
Мне уже понакидали вариантов решения, надо разбираться, но уже не сегодня.

Инфра - зло.

Обсудила с пользователями новый функционал. Оказалось, что он недостаточно хорош. Надо еще дорабатывать логику источников данных. Давно думала переделать парсинг, видимо, пришла пора. Но сначала, надо разобраться с инфрой. Так катать нельзя. А потом еще закончить с интерфейсами. Хехей, нет конца и края задачкам. #AWS, #Работа
Плохо быть джуном и не ведать то, что делаешь. Особенно касательно инфраструктуры. Но хорошо, когда у тебя есть друзья, интуиция, память и логика.
Вчера после общения с товарищами про мою проблему с инфрой, у меня заела одна мысль - лишний запущенный nginx. Когда я ковыряла инстанс по ssh, у меня уже возникли вопросы на тему того, а какой из файлов конфигураций nginx надо запускать то вообще, потому что их несколько.
Короче, вывод то какой? Я через ssh по ходу запустила не тот nginx. AWS о нем не знал и не тушил при перекате, и он миленько работал на старой версии. А чего бы и нет?
Переходом на новый инстанс проблема была по факту решена.
Теперь можно как и раньше катать спокойно. Фух. Можно дальше творить) #AWS, #Работа
На неделю выпала из процесса. Пришлось взять некоторую паузу.

Интерфейсы. Чтобы не тошнило от любого соприкосновения с js, пошла подтягивать базу по этому вопросу. Второй день прохожу базовый курс на udemy https://www.udemy.com/course/javascript_full/
Очень тяжело идут лекции подряд, не потому что сложно, а потому что рутинно и томно. Нашла выход - смотрю лекцию, потом какой-нибудь короткий видосик, снова лекцию. Такое межевание помогает продвигаться.
В итоге в моменты прокрастинации наткнулась на курс по AWS, который также думала пройти https://www.youtube.com/playlist?list=PLg5SS_4L6LYsxrZ_4xE_U95AtGsIB96k9
Хехей! Вот оно гениальное решение: чередую теперь лекция по AWS, лекция по JS.

Ну что же. Чай, xmind, продолжаю... 9 лекция AWS, 31 лекция по JS.

В голове начинает проясняться. #AWS, #JS, #Учеба, #Курсы
Я привязала комментарии к каналу. Попробуем?)
Я узнал, что у меня...

Я продолжаю свое чередование AWS и JS.

Про AWS. Банальная такая штука, но нифига неочевидная. Инстансы в AWS разделены на семейства. С ходу ведь так и не знаешь, в чем же суть этих семейств, какие-то буковки-циферки. Да, понятно, что там написано CPU и RAM, по ним и ориентировалась. Думала, что чем выше циферки, тем круче и дороже. Но оказывается, что цифра после буквы семейства означает версию типа инстанса, чем она больше, тем новее оборудование, да и тем круче, конечно, НО! и тем дешевле инстанс Оо. То есть банально, переключим с t2 на современные t3 я буду платить меньше. Жаль только elastic beanstalk не поддерживает новые дешевые a1 и даже t4g.

https://aws.amazon.com/ru/ec2/instance-types/ тут можно почитать про разные типы.

Также есть нюансы резервирования инстансов. И если резервировать на год или на 3, то цена становится еще ниже.

Надо еще попробовать поиграть со спотами, которые вообще очень-очень дешевые, но AWS их может отбирать, когда кто-то делает резерв. Тут интересно потыкать eb, так как он позволяет заводить Instances именно на спотах.

Про JS. Странное. В JS много странного.
1. NodeList не Array. Почему? Потому:
myArray --> Array.prototype --> Object.prototype --> null
myNodeList --> NodeList.prototype --> Object.prototype --> null
https://developer.mozilla.org/ru/docs/Web/API/NodeList
А я всего лишь хотела метод indexOf() для NodeList. Блин, где логика?

2. +'Infinity' это число Infinity (бесконечность), а +'infinity' это NaN (not a number). Вот такие дела. Вроде все логично, но почему так больно?

40 лекция по js из 124. 18 лекция по aws из 88. Поехала дальше. Интересно, смогу ли я закончить до конца недели? #AWS, #JS, #Учеба, #Курсы
Паутина разрастается. Закончу - закину сюда #Учеба
Новый классный функционал от aws. Теперь EB работает с docker-compose. Вот только почему-то работа с обычным dockerfile встала. Твою мойву... Опять 25... Пришлось откатываться на предыдущую версию. Т_Т я не хочу снова страдать, но по ходу придется разбираться.
В логах выглядит вот так:

2020/10/07 11:57:14.114946 [INFO] Running command /bin/sh -c docker pull python:3.8
2020/10/07 11:57:30.855165 [INFO] Starting...
2020/10/07 11:57:30.855211 [INFO] Starting EBPlatform-PlatformEngine
2020/10/07 11:57:30.855228 [INFO] reading event message file
2020/10/07 11:57:30.855325 [INFO] no eb envtier info file found, skip loading env tier info.
2020/10/07 11:57:30.855398 [INFO] Engine received EB command cfn-hup-exec
Дорогая, я не смог тебе достать образ питона. Все, чао-какао. #AWS
Чувствую себя кул-хацкером) Подключилась к Instance по ssh, нашла там название запущенного докер контейнера, выполнила на контейнере exec, запустив django shell, а потом запустила скриптик, который мне выгрузил нужную статистику.
Вроде мелочь, но путь тернистый. До этого я только sql-запросы к продовой базе тыкала.

Команды:

чтобы получить список контейнеров
docker exec --interactive CONTAINER_ID django-admin shell

Ну, а дальше в интерактивном режиме импорт и запуск нужного скрипта.

Кстати, раньше я не понимала нафига вообще нужен этот shell, зачем все так упорото все прогоняют через shell. Теперь это мой чуть ли не основной инструмент для проверки своих идей в процессе написания кода. Почти каждую функцию проверяю в shell. Ну, или если не джанга, то аналогично запуск python в консоли отличный вариант для тестирования скриптов. #Docker
Еще хочу поделиться. Не знала как отправлять файлы с помощью Python.
Так как я не хотела заморачиваться со скачиванием файла или подключением s3 для сохранения, решила воспользоваться методом отправки файла в telegram api для ботов (sendDocument). Вот, пришлось разобраться как крепить файлы к post запросу. Получилась такая функция:

url = f"https://api.telegram.org/bot{settings.TOKEN}/sendDocument"
caption = "описание файла"
data = {"chat_id": chat_id, "caption": caption}
with open(self.file_name, "rb") as file:
files = {"document": file}
response = requests.post(url, data, files=files)
add_log(self.name, response.text)
return response

Как-то так. #Python
После JS в скрипте на python пыталась на каждой строчке ; ставить и тело функции чуть не внесла в {}.

Прохожу AJAX.

Перелопатила кучу всего, чтобы придумать с помощью какой приблуды запустить свой js, в итоге оказалось, что того как запускает шаблоны pycharm вполне достаточно, чтобы отправлять post-запросы. Лектор предлагал openserver и еще что-то подобное, мне не понравилось.
Дальше лектор кладет в папочку php-скрипт, чтобы обрабатывать запросы, но у меня то Pycharm, я не могу его осквернять.
Полезла запускать flask. Конечно, я не через template на flask запустила html (хотя что мне мешало).
Короче, ошибка, не тот домен. corsы-фигорсы (защита от кросдоменных запросов), стаковерфлоу, библиотека flask_cors, чтобы добавить разрешение для запросов с другого порта localhost. Это я еще почти молчу, что сначала я пыталась фласк поднять в докере, но что-то пошло не так, поэтому психанула и запустила просто в venv прямо в винде. Ненуачо.
Тяжела и неказиста жизнь простого программиста.
Полдня в топку на то, чтобы просто выпендриться и сделать по-своему условную инфру для прохождения курса, еще и довольно криво. Зато, блин, опыт. Но все равно чет в докере не вышло запустить. Странно. Очень странно.

Тем временем в лекциях по AWS (да я их не бросила) vpc (сети, подсети). Понравилось выражение лектора - "Приступим, помолясь". Это, блин, слоган для работы с инфрой, особенно на AWS. Когда буду настраивать деплой на новую платформу, тоже буду приступать, помолясь, поставлю рядом шаманский бубен и заряженную воду, вдруг поможет. Нет, AWS хорош, но СЛИШКОМ СЛОЖНА!

Мой прогресс: js 54/124; aws 31/88. Кажется, до конца недели не успею. #AWS, #JS, #Учеба, #Курсы
Пришел очередной отчет из wakatime. Забавно, что больше 70% приходится на браузер. И это не только во время прохождения курса, но и при реализации проектов примерно такая же доля. Не потому что прокрастинация и вот это все, а потому что чтение документации, поиск решений и тд и тп приходится именно на долю браузера.

Если что, я не разбиралась как там точно проводятся замеры, но плагин wakatime у меня стоит в Pycharm и в браузере, а также подключен github.

К слову, если не подключать плагин к браузеру, то всю активность будет считать как программирование. Статистика будет нечестная, зато красивая) #Учеба, #Tools, #Работа