Дневник разработчицы
Kanalga Telegram’da o‘tish
Откровенные истории о проектах, работе, учебе и открытиях в мире программирования. Присоединяйтесь и вдохновляйтесь вместе со мной!
Ko'proq ko'rsatish341
Obunachilar
+124 soatlar
+27 kunlar
+430 kunlar
Postlar arxiv
На неделю выпала из процесса. Пришлось взять некоторую паузу.
Интерфейсы. Чтобы не тошнило от любого соприкосновения с 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.
В голове начинает проясняться.
Плохо быть джуном и не ведать то, что делаешь. Особенно касательно инфраструктуры. Но хорошо, когда у тебя есть друзья, интуиция, память и логика.
Вчера после общения с товарищами про мою проблему с инфрой, у меня заела одна мысль - лишний запущенный nginx. Когда я ковыряла инстанс по ssh, у меня уже возникли вопросы на тему того, а какой из файлов конфигураций nginx надо запускать то вообще, потому что их несколько.
Короче, вывод то какой? Я через ssh по ходу запустила не тот nginx. AWS о нем не знал и не тушил при перекате, и он миленько работал на старой версии. А чего бы и нет?
Переходом на новый инстанс проблема была по факту решена.
Теперь можно как и раньше катать спокойно. Фух. Можно дальше творить)
Сегодня радостно писала новый функционал.
Вжух-вжух - там модельку дописать, там вывод доработать, там обработку данных на входе. Кайф.
Проверила, отдебажила. Все было прекрасно.
Деплою, полетело. Но тут хоба, часть сообщений приходит в новом формате, а часть в старом.
Данные работают, сохраняются, но почему-то много чего идет не так.
Ну, здравствуйте. Смотрю в логах, а там проблемных данных нет. Вижу корректные отправки, а проблемных нет. Какой-то корабль-призрак мне их присылает, чтобы позлить меня.
Пробовала перекатывать несколько раз, чистить очередь, но ничего не помогло.
Пришлось использовать тяжелую артиллерию и переключать тип деплоя на вариант с переходом на новый инстанс, чтобы все точно заглохло.
Проблема скорее всего в кастомной настройке nginx. Потому что раньше такого не наблюдалось.
Надо разбираться как это чинить. Опять ковырять инфру. Как же надоело.
Мне уже понакидали вариантов решения, надо разбираться, но уже не сегодня.
Инфра - зло.
Обсудила с пользователями новый функционал. Оказалось, что он недостаточно хорош. Надо еще дорабатывать логику источников данных. Давно думала переделать парсинг, видимо, пришла пора. Но сначала, надо разобраться с инфрой. Так катать нельзя. А потом еще закончить с интерфейсами. Хехей, нет конца и края задачкам.
Если честно, иногда хочется сложить лапки.
Когда вот вижу проблему, но хз как ее решать. Особенно нагнетает то, что ведь этим пользуются люди, они видят косяк системы. Поэтому хочется как можно быстрее его исправить, а не получается. Особенно, когда такая проблема не одна, а их много, и они падают одна за другой на мою голову.
Самое еще неприятное то, что спросить то в общем, не у кого. Вернее, конечно, есть у кого, но никто не знает систему и все ее нюансы так, как я, никто не сможет понять проблему "удаленно". Если уж я зашла в тупик, то человеку со стороны еще сложнее понять, в чем моя боль.
Пока я со всем справляюсь.
Кстати, логи спасают. И да, new relic тоже сильно помог в выявлении проблемы.
Одна из функций, которую я использую для логирования для подсчета времени на выполнение функции (подсмотрела на одном из курсов):
from functools import wraps
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Так. Итого. Опять инфра.
Если вдруг начинается какая-то дичь, подкинь дровишек. Нашла истоки проблемы со своими дублями. Да, был косяк (или не косяк) в логике, из-за которого одна и та же тема обрабатывалась двумя воркерами одновременно. Но самая большая проблема шла из-за того, что select, который раньше отрабатывал быстрее, чем за секунду, вдруг стал залипать на 40 секунд.
Отчасти думаю, что мощности базы начало не хватать, а поток задач не уменьшился, поэтому базу тупо начало заваливать задачами, наращивая проблемы на нее как снежный ком.
Я поменяла машину для бд с бесплатной t2micro (1-1) на платную t2small (1-2).
Теперь все вернулось на круги своя. Мда. Растем.
Двойная проверка на дубли в последовательном выполнении кода внезапно не срабатывает. Система создает 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)
Слежу теперь поможет ли.Инфра. Подключение 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 легко гуглятся, если что. Блин, безумие и отвага.
Интерфейс... Все мои надежды на просто решение через тележку не сбылись. Я подступила к этой задаче, но осознала, что слишком много уже функционала и настроек, чтобы можно было легко и непринужденно сделать все через телеграмм.
В итоге я сегодня сделала еще одну маленькую фичу, а завтра пойду начинать проходить отложенный курс по JS, дабы сделать красивую и удобную админку.
Про кнопки в телеграмме.
Я пользуюсь api телеграмма без дополнительных библиотек. Сегодня добралась до реализации своей кнопки к отправленным сообщениям, чтобы удалять лишнее и помечать удаленное как ошибку.
Для этого в данные, отправляемые методом sendMessage я добавила:
data["reply_markup"] = json.dumps({"inline_keyboard": [
[{"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 удаляю ошибочное сообщение из чата.
Мои функции для работы с этими методами:
def answer_callback_query(callback_query_id, text, alert=False):
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
Задача готова и зарелижена. Ни дня без релиза)Инфра, продолжение...
Заходишь в логи базы данных, а там каждую секунду неуспешные попытки авторизации. И это точно не ты:
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)
Но пока это не критичная проблема, поэтому отложу ее на потом.Нельзя просто так взять и запустить! Короче, я не смогла нормально подружить asgi с new relic, как я ни крутила, ничего нормально не завелось. В итоге, я плюнула и решила попробовать middleware. Про то что это и как этим пользоваться хорошо рассказано в видео https://youtu.be/Qe8YI5wXJng
Я просто обернула функции:
import newrelic.agent
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 секунд). Теперь придумываю как ее решать.Немного про инфру. Один мой друг посоветовал для отслеживания проблем очень интересный инструмент. Давно советовал, но я трусила вникать в новый инструмент. Все новое пугает, это ж надо разбираться.
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 33. Пересобрала контейнер, запустила локально. Кликнула "See you data" и следующие кнопки на странице "Add your Python application data" 4. Бинго, получила данные. 5. Потом еще настроила окружения. В файле конфига можно найти настройки для разных окружений:
[newrelic:development] 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)На что получила ошибку:
Error in ASGI Framework
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 при запросе лент. Добавила, подтянулись новые материалы из нескольких лент. Посмотрим, что будет дальше, но теперь запросы отправляю с разными юзер-агентами.Сходила в бд, сделала просто select
select * from data_flow_link order by id desc limit 20;Вернулась на страницу, страница загрузилась за 1 секунду. Попробовала использовать поиск из админки, который работает как like, результат отдавался 15 секунд, что в принципе нормально, так как материалов много, а индексации самого текста нет. На тесте такой поиск отрабатывает за 3 секунды. И что это было?
Вот задачка-незадачка. В админке страница с ссылками на локальной машине загружается меньше, чем за секунду (данных всего в 2 раза меньше, чем на сервере), а на сервере за 16 секунд. С остальными страничками в админке такой проблемы не наблюдается.
Пойду запишу себе баг. Если есть идеи в чем может быть проблема - пишите в личку)
Сортировок нет, данных из других моделей на странице не запрашивается.
Есть подозрение, что проблема в самих данных, где-то что-то пошло не так, из-за этого тормозит. Но надо проверять.
У меня в системе уже больше 0,5 млн разных материалов. Из них около 442 500 собрано за последний месяц. При этом от публикации материала до доставки ее в соответствующую тему сейчас в среднем 73 секунды. Хехей!
ORM - прекрасная штука, Но сегодня я собирала данные о своей системе для презентации и поэтому пришлось немного пописать на сыром sql. Вот такие запросы у меня сегодня были:
А что вы сделали за сегодня? Я вот чутка добавила фильтров в админку, рассказала одному программисту про свой проект и смотрела коня боджека) А ведь хотела посмотреть что-нибудь из курсов. Хехе. Ну, и медитирую на цифры, которые стремительно растут...
