
Полная версия платной статьи, публикуется с разрешения автора. Первая часть здесь.
В этой части поговорим о сложностях, с которыми столкнулась команда при лавинообразном росте нагрузки, как разваливался бекенд, а архитекторы из AWS только пожимали плечами.
5. Инженерные вызовы
Рост нагрузки и его влияние на выбор технологий
Тип технологических решений, которые принимает команда, диктуется в первую очередь паттернами чтения и записи (Cursor всю дорогу синхронизирует струкутру проектов от миллионов пользователей без перерыва, об этом говорили в первой части. Прим. пер.):
Работа с кодом: транзакции с низкой задержкой. Для фичей, связанных с кодом (дополнения по tab'у, индексация, пересчет хеш-деревьев), рабочая нагрузка представляет собой череду чтений и записи. Задержка для этих операций должна быть как можно ниже. Изначально Cursor использовал Pinecone для семантической индексации и поиска, но затем перешел на Turbopuffer.
Также важно иметь серверы поближе к пользователю. Рабочие нагрузки распределены по нескольким регионам, таким как западное и восточное побережье США, Великобритания, Европа, Япония. Для сокращения задержки обычно выбирается физически ближайший к пользователю бэкенд.
Аналитика: допустима высокая задержка. Такие вещи, как хранение метаданных о каждом запросе агента, логи и т.д. можно писать в долгую. Здесь задержка не так важна и допустима конечная согласованность (eventual consistency). Команда использует Warpstream для потоковой передачи событий и логов.
Проблемы масштабирования
Главная трудность на проекте, связаная со стремительным ростом пользовательской базы, — постоянное машстабирование. Использование часто удваивалось из месяца в месяц. Все показатели по использованию сервиса сейчас в 100 раз выше, чем 12 месяцев назад.
Подходы, работающие в меньшем масштабе, часто начинают ломаться на большом. Год назад Cursor индексировал несколько миллионов файлов в день, и эмбеддинги умещались в одной БД без шардинга. По мере роста появились сотни миллионов, а теперь и миллиарды новых эмбеддингов для ежедневного хранения. Масштаб сейчас настолько велик, что Cursor является одним из крупнейших клиентов для используемых им моделей. Например, для эмбеддингов от OpenAI. (Более конкретно про масштабирование читайте дальше. Прим. пер.)
Проблема холодного старта
Тут проблема заключается в том, что стартануть огромный сервис после падения может быть очень проблематично. Суалех (сооснователь компании):
«Представьте, что вы обрабатываете 100k запросов в секунду, и внезапно все ваши узлы умирают. При перезапуске системы ваши узлы обычно поднимаются один за другим. Но, если вам удалось перезапустить 10 узлов из 1000, и вы не запретите людям делать запросы, эти 10 узлов будут завалены входящими запросами. Прежде чем эти 10 узлов смогут функционировать в нормальном режиме, они опять умрут!
Это уже много раз в нас стреляло. Всякий раз, когда у вас случается серьезный инцидент, требующий холодного старта, нужно придумать, как сделать это корректно.
Многие из крупных провайдеров, которых вы, вероятно, используете, имеют различные трюки, чтобы прекратить трафик, пока они выполняют холодный старт. Мы в итоге сделали настройку, при которой мы либо полностью останавливаем трафик до завершения старта, либо приоритизируем небольшое подмножество наших пользователей, пока сервис не вернется к нормальному состоянию».
Геморрой с шардингом
Поначалу Cursor использовал Pinecone как векторную БД для хранения эмбеддингов. Слоган компании гласил — «Ваша векторная БД для масштабирования в продакшене».
Pinecone работает с концепцией «подов». Под — это единица оборудования, в которой хранятся индексы; думайте о нем как о ВМ или микро-ВМ под капотом. Поды имеют определенный размер хранилища, а количество эмбеддингов, которые они могут хранить, зависит от размерности вектора.
Для сценариев с низкой задержкой, где векторный поиск должен возвращать результат менее чем за 100 мс, Pinecone предлагает использовать поды p1, где каждый под p1 имеет емкость примерно для 1 млн эмбеддингов размерности 768. Индексы могут быть распределены по нескольким подам и Pinecone не использует шардинг данных для случаев, когда эмбеддингов меньше 100 млн. Из их документации:
«Обратите внимание, что шардинг данных подходит только для индексов с сотнями миллионов векторов».
Большинство реальных приложений и не вырастают до сотен миллионов векторов, но Cursor смог. Поэтому им пришлось внедрять шардинг данных. Суалех:
«Нам пришлось вручную шардировать индексы для пользователей в базы данных Pinecone. Однако шардинг привел к проблемам, таким как необходимость решать, когда перемещать пользователей между шардами, по мере увеличения шарда.
Недоиспользование: мы обнаружили, что работаем с низкой утилизацией хранилища, ограничивая утилизацию на уровне 70% от пространства базы данных. 30% хранилища мы оплачиваем, но не используем.
Сложные миграции: когда пространство БД в шарде начинает достигать 80-90%, нам нужно решить, какого пользователя мигрировать из этого шарда. Но у нас могут быть пользователи, записывающие данные прямо сейчас и теперь нам нужно убедиться, что эти записи проходят. Но идут ли они в старый шард, из которого мы мигрируем, или в новый, который еще не закончил перемещение? Как нам убедиться, что нет потери данных и нет повреждения данных?»
Постоянное перешардирование очень напрягало и разочаровывало команду в этой БД. Суалех сказал мне, что самый большой урок, который извлекла команда, — это по возможности избегать шардинга в будущем. Поэтому они искали векторную БД, которая могла бы поддерживать многопользовательскую архитектуру без необходимости ручного шардинга.
Turbopuffer был стартапом, который это обещал, поэтому команда решила его попробовать и мигрировать значительную часть своих данных туда. Суалех:
«Самое замечательное в то, что теперь каждый пользователь Cursor является отдельным тенантом в их системе. Это означает, что нам больше не нужно иметь дело со сложными миграциями между шардами.
Нам также нравится, что Turbopuffer построен на базе S3. Хотя это прыжок веры — доверять так много молодому стартапу, но знание того, что они строят на самом стабильном хранилище в отрасли (S3), было в их пользу».
Труднообнаружимые сбои
Однажды эмбеддинги для больших файлов (60 000 строк и более) перестали доезжать до БД из-за ошибки в инфраструктуре. Ошибка была неизвестна команде, а тестирование её пропустило. Поскольку такие эмбеддинги не сохранялись, процесс снова и снова пытался их синхронизировать.
Внезапно нагрузка на БД начала расти, в то время как количество ошибок оставалось прежним. Команда заподозрила, что что-то не так, но не могла точно определить, что именно! Началась отладка:
Кто-то заметил, что отклонилось среднее попадание в кеш (cache hit)): подсказка!
Добавили больше мониторинга вокруг кэширования.
Поняли, что что-то не так с большими файлами.
Команда заподозрила, что что дело может быть в гонках (Типичный случай для высоконагруженных систем, когда множество процессов может изменять состояние. Прим. пер.). После оченьь долгого поиска и отладки проблему удалось устранить.
Это лишь один из многих примеров сбоев, с которыми сталкивается команда.
6. Миграции БД
Непрерывный рост означает постоянное масштабирование, что часто означает смену технологий. Например, переход с Pinecone на Turbopuffer для хранения эмбеддингов. Требуется проводить миграции, которых у команды уже было несколько.
От Yugabyte до Postgres в AWS RDS
На самом раннем этапе Cursor хранил структуры данных (хеш-деревьев проекта) в Yugabyte, распределенной БД, совместимой с PostgreSQL. Yugabyte был выбран из-за обещания, что база данных может масштабироваться бесконечно. Суалех делится:
«Когда я был в MIT, я был большим любителем баз данных. В мире БД есть пара удивительных продуктов, которые многие считают решением всех своих проблем:
Google Spanner: БД, известная своей способностью работать в масштабах Google.
FoundationDB: распределенная и высокомасштабируемая БД.
KV хранилища, такие как Redis, которые также известны хорошей масштабируемостью.
Расхожее мнение заключается в том, что если вы используете эти три широко известные БД, то вы можете заставить работать что угодно, добавив, пожалуй, еще одну специализированную БД для аналитики.
Я решил выбрать Yugabyte, потому что он был построен на тех же принципах, что и Google Spanner, и предлагал аналогичные возможности масштабирования. Я думал, что нам нужно решение, которое обеспечивает бесконечную масштабируемость, зная, что нам нужно будет делать это быстро».
Однако команда быстро столкнулась с проблемой: Yugabyte не работал, как ожидалось.
«Мы просто не могли заставить Yugabyte работать. Мы тратили на неё много денег, пытаясь уменьшить хранилище до как можно меньшего количества узлов, но оно просто не работало. Мы нагружали БД и она и зависала. Это происходило потому, что у нас было много очень длинных транзакций, а поскольку Yugabyte глобально распределенная БД, ей требовалось много времени для достижения консенсуса.
Оказалось, что в то время нам нужна была не глобально распределенная база данных, а что-то гораздо проще. Поэтому мы перешли на Postgres, размещенный на Amazon Relational Database Service (RDS). После перенос той же рабочей нагрузки все внезапно пошло как по маслу.
Урок жизни для нас тут в том, что при выборе БД лучше избегать сложных решений, по крайней мере, на начальном этапе. Начните с Postgres и он вас очень далеко заведет. По крайней мере, для нас это сработало».
С Postgres на Turbopuffer
Postgres очень хорошо работал почти год, пока внезапно не перестал. Однажды в 2024 году система сошла с ума: выросли кэш-промахи, эмбеддинги перестали писаться, все пошло не так. Алерты БД сыпались налево и направо, сигнализируя о достижении лимитов хранилища. Суалех вспоминает:
«У нас было 22 ТБ данных, хранящихся в AWS RDS, а у RDS был лимит в 64 ТБ. Я предполагал, что у нас есть пара месяцев, пока наша база данных не станет достаточно большой, чтобы потребовалась миграция. 20 из 60 — это ведь не так уж плохо, да?
Ну, оказалось, что хранилище для Postgres устроено иначе. Когда пользователь удаляет или изменяет файл в своем рабочем пространстве, мы обновляем запись, хранящую дерево в Postgres.
Наша основная рабочая нагрузка — это разработчки, которые постоянно что-то пишут, поэтому мы видим огромное количество обновлений в БД. Как следствие — огромное количество удалений, добавлений и обновлений записей!
В Postgres операция удаления просто помечает запись как удаленную: она говорит индексу игнорировать ее. Но она не удаляет запись и не освобождает память. В Postgres есть процесс VACUUM, который очищает все эти удаленные записи и освобождает блоки. Но мы наблюдали, что VACUUM тоже сходит с ума, а транзакции все время застревают.
Когда БД испытывает трудности, это почти как машина, которая с трудом заводится. Мы прилагаем кучу усилий, чтобы уменьшить трафик на БД, запустить VACUUM, очистить все накопившиеся транзакции, затем медленно снова пустить трафик… и БУМ, она снова останавливается. Это как машина, которая дергается, когда вы пытаетесь ее завести».
Чем дольше команда пыталась починить БД, тем хуже становилось. В какой-то момент она перестала загружаться. Через несколько часов после инцидента начали сыпаться электронные письма от недовольных клиентов, потому что Cursor сильно деградировал. Команды обратилась в службу поддержки AWS и смогла связаться с архитекторами из AWS, которые RDS и сделали. Суалех:
«Итак, мы разговариваем с архитекторами, которые создали RDS и они говорят нам, что мы являемся самым большим инстансом в их серсисе на на AWS. Говорят, что-то типа: «К сожалению, ребята, мы не можем вам ничего сказать, кроме того, что у вас большие проблемы». (Лол. Прим. пер.)
Поэтому мы начали делать несколько вещей параллельно, чтобы оживить систему:
Один инженер изучает возможность удаления всех FK в БД, так как они могут начать удалять связанные записи в других таблицах, а это создает большую нагрузку на БД. Решили, что можем избавиться от них, чтобы БД снова заработала.
Другой инженер ищет способ как-то удалить самую большую таблицу. Из 22 ТБ одна таблица занимает 20 ТБ (Что? Воскл. пер.). Один из наших соучредителей, Арвид, засучил рукава и начал изучать возможность перемещения этой таблицы из Postgres в объектное хранилище, вместо того чтобы хранить ее в основной БД.
Остальные разрабы пытаются снизить нагрузку на БД. Сидят с консолью Postgres, вручную удаляют транзакции и пробуют разные странные вещи, чтобы воскресить БД.
Затем происходит нечто неожиданное: Арвид справляется с задачей быстрее остальных и перемещает 20 ТБ нагрузки из Postgres в Turbopuffer. К этому моменту мы уже 10 часов находимся в состоянии критического инцидента и новое решение, как будто бы работает, в то время как наша существующая БД все еще лежит. Арвид говорит, что написанный им код надежен и будет работать. (Хорош. Прим. пер.)
Мы принимаем решение сделать перезапись по живому и начинаем перемещать все эмбеддинги в блочное хранилище, работающее на Turbopuffer. И это работает!
Чуть позже мы полностью переехали с Postgres на Turbopuffer.
Лучший способ масштабировать БД — это не иметь БД вовсе — по крайней мере, когда вы работаете в таком масштабе, как мы.»
7. Инженерная культура и процессы
Новый релиз каждые 2-4 недели
Cursor необычен среди стартапов, потому что они выпускают новый релиз каждые 2-4 недели (По-моему, Cursor постоянно говорит, что вышло новое обновление, так ведь? Прим. пер.), в то время как большинство из них выпускают релизы несколько раз в день. Объясняется это тем, что это не веб-приложение, а толстый клиент. Суалех называет то, что они создают, «локальным ПО» (Local software. Прим. пер.):
«Локальное ПО, подобное нашему, кажется медленно умирающим искусством. Большинство крупных компаний, как правило, управляют сервисами с тяжелым бэкендом, где они часто добавляют новые функции. Даже для настольных приложений большинство из них полагаются на веб-клиенты, вместо того чтобы быть полностью самодостаточными. (Я лично не догнал до конца, о чем тут речь. Курсор ведь это клон VS Code, т.е. не то, чтобы с нуля написанное самодостаточное приложение. Прим. пер.)
Выпуск локального ПО сопряжен с дополнительными сложностями: нам нужно выбрать коммит для релиза, а затем тщательно протестировать, что мы не внесли регрессий». (В целом-то, бекенд тоже надо тестировать. Прим. пер.)
Использование флагов
Команда использует флаги функций при разработке, но иначе, чем большинство стартапов. Реализация флагов функций была вдохновлена препроцессором Pragma в C. Флаги, разбросанные по их коду, обычно предназначены только для внутреннего использования и сборок. Например, вот флаг функции из кодовой базы Cursor, который оборачивает несколько строк кода:

Флаг внутри кодовой базы Cursor в файле abstractFileDIalogService.fs
По умолчанию Cursor удаляет весь код, огороженный флагами из списка. Флаги перечисленны в конфигурационном файле product-prod-todesktop.jsonc
.

По умолчанию весь код, обернутый флагами функций, добавленными в код, удаляется из прод. сборки
Суалех говорит, что они выбрали такой подход (Удалять все. Прим. пер.), чтобы избежать случайной отправки сломанных функций клиентам, не беспокоиться беспокоиться о недостатках «обычных» флагов функций и о раскрытии невыпущенной функциональности перед хитрыми разработчиками. Например, программист Джейн Мачун Вонг регулярно обнаруживает предрелизные продукты, типа Figma Sites.
Выделенная команда инфраструктуры
Учитывая огромную нагрузку на инфраструктуру, более 1 млн запросов в секунду в БД, и использование десятков тысяч GPU в нескольких облаках, я задался вопросом, есть ли у компании выделенная команда инфраструктуры. Вот что мне сказал Суалех:
В команде инфраструктуры сейчас 15 инженеров. Команда существует с самого начала компании. Их основные обязанности:
Поддержка БД
Потоковая передача данных
Планирование компьюта
Масштабирование обучения и инференса
Обеспечение надежность
Cursor также нанял несколько мощных инженеров с глубокими знаниями и интересом к инфраструктуре. Например, Аман Канани был вице-президентом по инфраструктуре в GitHub, прежде чем присоединиться к команде в прошлом сентябре в качестве главного инженера.
Облако — это отличная инфраструктура для начала, но у него есть свои сложности. Cursor использует облачные решения и стартапы, такие как AWS, Azure, Turbopuffer и другие, которые берут на себя большую часть тяжелой работы по инфре. Тем не менее, даже так остается много работы, связанной с инфрой:
Планирование: выбор поставщиков/решений, их оценка
Мониторинг, алеты при деградации систем, работа с инцидентами и сбоями
Обслуживание: обновления и миграции
Через некоторое время для качественного выполнения этой работы начинают требуются выделенные инженеры.
Культура экспериментов
Суалех:
«Мы призываем всех придумывать и пробовать новые вещи, пробовать их внутри компании. Мы сначала все тестируем на себе, поэтому не страшно, если эксперимент окажется полным провалом. В этом случае мы просто не отправляем его нашим пользователям.
Основная ценность заключается в том, что инженеры должны быть креативней. Мы заботимся об «инженерном гении». Ваша кодовая база не должна тормозить ваше развитие. Это было нашим фокусом и это так и остается».
Следующая крупная задача
Я спросил Суалеха, какая следующая часть работы волнует его больше всего и он ответил, что это масштабирование стека обучения для проведения полномасштабного RL.
Обучение с подкреплением (RL) — это не просто обычный forward pass по данным, как на претрейне и SFT, а гораздо сложнее. Тут есть среда и другие компоненты типа reward-моделей и других. Этим команда планирует заниматься в ближайшее время.
Выводы
Большое спасибо Суалеху за то, что он поделился всеми этими подробностями о том, как работает Cursor. Какие выводы мы можем из этого извлечь:
Небольшие, но опытные команды могут выполнять задачи исключительно хорошо. Команда Cursor крошечная по сравнению с популярностью их продукта и доходом, и это может объяснить, почему они так быстро движутся, опережая гораздо более крупные организации, такие как GitHub Copilot.
Используйте облачных провайдеров при быстром масштабировании. Одна из причин, по которой Cursor может поддерживать небольшую команду инженеров, заключается в том, что они «аутсорсят» многие инфраструктурные проблемы облачным провайдерам. Инженерной команде не нужно заниматься шардингом данных, потому что платформы, которые они используют, делают это за них.
Будьте осторожны с использованием небольших стартапов. Суалех сказал мне, что одним из главных уроков было то, что использование стартапов для инфраструктуры — это огромный риск при быстром росте. Команда несколько раз на этом обжигалась.
Суалех упомянул, что у них было два приятных исключения из этого правила с Warpstream и Turbopuffer; оба — небольшие стартапы, у которых Cursor является одним из крупнейших клиентов.
Гораздо проще создавать продукт, когда вы создаете его для себя. Пока что у команды нет нужды в менеджерах по продукту или бизнес-стратегах, потому что их основные клиенты — это те же люди, которые его создают: такие же разработчики.

Перевел Сергей Аверкиев, заходите ко мне на канал Градиент обреченный. Всем спасибо за чтение!