1. Первый пользователь посылает сообщение. Запускается копия search_vk() и выполняет синхронный запрос:
user_data = vk_parser.get_user_data(user_id)
Пока запрос выполняется, бот стоит, так как код синхронный.
2. get_user_data() деляет глупости вида
with open("FIO.txt", "w") as file: #открываем один и тот же файл для любого запроса
или
global bio # используем одну и ту же переменную для любого запроса
Ну и прочие медиа-файлы тоже одни и те же для разных запросов.
3. Обработчик сообщения первого пользователя доходит до строки
loading = await message.answer('<b>Идёт поиск</b>')
Так как это await-вызов, планируется вызов корутины, и асинхронная программа переходит к следующей доступной операции, пока выполняется отправка сообщения. Собранные данные лежат в файлах.
4. Второй пользователь посылает сообщение. Запускается ещё одна копия search_vk() и тоже выполняет синхронный запрос get_user_data(). Второй вызов get_user_data() перезаписывает данные в файлах.
5. Обработчик сообщения второго пользователя доходит до строки
loading = await message.answer('<b>Идёт поиск</b>')
и засыпает, пока сообщение отправляется.
6. Тем временем сообщение первого пользователя отправилось. Обработчик получает управление, и отправляет содержимое файлов - которое было перезаписано, заодно закрывая их.
7. Сообщение второго пользователя отправилось. Его копия search_vk() пытается отправить файлы, но первый обработчик их уже закрыл.
Дело ещё осложняется сетевым лагом, так что трудно сказать, кто в итоге будет первым.
Если по прочтению ты ещё не понял, в чём дело, скажу прямо:
не используй глобальные объекты, будь то файлы или переменные, в конкурентной среде! Убедись, что каждая копия обработчика запроса имеет свои хранилища для данных! Локальные переменные безопасны, они создаются каждый раз заново. Если не можешь обойтись без файлов на диске, либо используй модуль tempfile, либо привяжи их имена к ID отправителя сообщения, чтобы хотя бы разные пользователи не сталкивались лбами.