🚀 Реальные инженерные вызовы: создание Cursor (часть 1)
Дмитрий Жечков (https://t.me/llm_notes) - перевод интервью с https://www.pragmaticengineer.com/Cursor вырос в 100 раз по нагрузке всего за год, обрабатывает более 1 млн запросов в секунду для своего уровня данных и ежедневно предоставляет миллиарды автодополнений кода. Глубокое погружение в то, как он построен, с соучредителем Суалехом Асифом 💻
Автор: Гергели Орош
10 июня 2025 г.
Cursor — это IDE с поддержкой ИИ, которая кажется самой любимой среди инженеров 👨💻 В опросе, который мы провели в прошлом году, Cursor был самым частым ответом на вопрос "какой ваш любимый редактор с функциями GenAI, которые помогают в работе?".
Anysphere — это стартап, стоящий за Cursor, основанный в 2022 году, с первой версией Cursor, выпущенной два года назад, в марте 2023 года. На прошлой неделе Anysphere объявила о привлечении $900 млн в раунде Series C, что оценивает компанию в $9,9 млрд! 💰 Бизнес превысил $500 млн годовой выручки, что может быть рекордом: никакая другая компания инструментов разработки, которую я знаю, не достигла этого рубежа в течение 2 лет после запуска своего первого продукта.
Также на прошлой неделе компания запустила Cursor 1.0, крупный релиз: заметными дополнениями являются ИИ-обзор кода (с инструментом под названием BugBot), фоновые агенты и поддержка памяти 🧠
Я встретился с соучредителем Cursor, Суалехом Асифом, чтобы узнать, как работает Cursor, и как команда создает инструмент, и он поделился новыми деталями его внутреннего устройства:
🔹 Технический стек. TypeScript и Rust, облачные провайдеры, Turbopuffer, Datadog, PagerDuty и другие
🔹 Как работает автодополнение. Низколатентный движок синхронизации передает зашифрованный контекст на сервер, который выполняет инференс
🔹 Как работает чат без хранения кода на сервере. Умное использование деревьев Меркла для избежания хранения исходного кода на сервере, при этом возможность поиска исходного кода с использованием эмбеддингов
🔹 Anyrun: сервис-оркестратор Cursor. Rust-сервис заботится о запуске агентов в облаке, безопасно и с правильной изоляцией процессов, используя Amazon EC2 и AWS Firecracker
🔹 Инженерные вызовы. Паттерны использования диктуют выбор технологий, проблемы масштабирования, проблема холодного старта, вызовы шардинга и трудно обнаруживаемые сбои
🔹 Миграции баз данных по необходимости. Как и почему Cursor перешел с Yugabyte на PostgresSQL. Также эпические усилия по переходу на Turbopuffer за часы во время крупного сбоя индексирования
🔹 Инженерная культура и процессы. Релизы каждые 2-4 недели, необычно консервативное использование feature flags, выделенная инфраструктурная команда, культура экспериментов
📊 Cursor в цифрах
Прежде чем перейти к техническому стеку, начнем с контекста о Cursor в цифрах:
• 50: количество инженеров, работающих над Cursor • 1 млн транзакций в секунду, и выше на пике • 100x: рост пользователей и нагрузки за 12 месяцев — иногда удваивается месяц за месяцем • 100+ млн: строк корпоративного кода, написанных ежедневно с Cursor корпоративными клиентами, такими как NVIDIA, Uber, Stripe, Instacart, Shopify, Ramp, Datadog и другими • $500+ млн: годовая выручка. Это было $300 млн в начале мая и $100 млн в январе, после того как год назад было ноль • Миллиард: чуть меньше этого количества строк кода пишется с Cursor ежедневно • Сотни терабайт: масштаб индексов, хранящихся в базах данных Cursor
Cursor может догонять GitHub Copilot по генерации выручки: Reuters сообщает, что GitHub Copilot, вероятно, сгенерировал $500 млн выручки в 2024 году. В настоящее время Cursor находится на пути к генерации того же в 2025 году 📈
🛠️ 1. Технический стек
Некоторая статистика о едва трехлетней кодовой базе Cursor:
• 25 000 файлов
• 7 миллионов строк кода
Редактор является форком Visual Studio Code, что означает, что у него тот же технический стек, что и у VS Code:
• TypeScript: большая часть бизнес-логики написана на этом языке
• Electron: фреймворк, используемый Cursor
При создании компании им нужно было решить, строить ли редактор с нуля, как это сделал Zed, или начать с форка. Суалех объясняет решение:
"Нам нужно было владеть нашим редактором и мы не могли 'просто' быть расширением, потому что мы хотели изменить способ программирования людей. Это означало, что нам нужно было либо создать совершенно новую IDE, либо сделать форк существующего редактора.
Мы решили сделать форк, потому что начинать с нуля потребовало бы огромных усилий только для создания стабильного редактора. Наше ценностное предложение заключалось не в создании стабильного редактора, а в изменении того, как разработчики программируют, делая это постепенно."
Бэкенд:
• TypeScript: большая часть бизнес-логики написана на этом языке
• Rust: все критически важные для производительности компоненты используют этот язык
• Node API к Rust: мост для вызова Rust-кода из TypeScript через Node.js
• Монолит: бэкенд-сервис в основном представляет собой большой монолит и развертывается как единое целое
Базы данных:
• Turbopuffer: мультитенантная база данных для хранения зашифрованных файлов и дерева Меркла рабочего пространства
• Pinecone: векторная база данных, хранящая некоторые эмбеддинги для документации
Потоковая передача данных:
• Warpstream: сервис потоковой передачи данных, совместимый с Apache Kafka
Инструменты:
• Datadog: для логирования и мониторинга
• PagerDuty: для управления дежурством
• Slack: внутренние коммуникации и чат
• Sentry: мониторинг ошибок
• Amplitude: аналитика
• Stripe: биллинг и платежи
• WorkOS: аутентификация
• Vercel: платформа для хостинга сайта Cursor.com
• Linear: для управления работой
• Cursor — естественно! Команда использует Cursor для создания Cursor 🔄
Обучение моделей: Cursor использует несколько провайдеров для обучения собственных моделей:
• Voltage Park
• Databricks MosaicML
• Foundry
☁️ Физическая инфраструктура
Вся инфраструктура работает на облачных провайдерах. Суалех говорит:
"Мы очень 'облачная' компания. Мы в основном полагаемся на AWS, а затем на Azure для инференса. Мы также используем несколько других, более новых GPU-облаков."
Большая часть CPU-инфраструктуры работает на AWS. Они также управляют десятками тысяч GPU NVIDIA H100 🖥️ Значительная часть GPU работает в Azure.
Инференс является безусловно самым большим случаем использования GPU для Cursor, что означает генерацию следующих токенов, либо как автодополнение, либо как полные блоки кода. Фактически, Azure GPU используются исключительно для инференса, а не для других LLM-связанных работ, таких как fine-tuning и обучение моделей.
Terraform — это то, что Cursor использует для управления инфраструктурой, такой как GPU и виртуальные машины, например, EC2 инстансы.
⚡ 2. Как работает автодополнение Cursor
Чтобы понять некоторые технические вызовы создания Cursor, давайте посмотрим, что происходит при первом запуске редактора.
Низколатентный движок синхронизации: предложения автодополнения
При открытии проекта или папки вы, вероятно, начнете редактировать файлы. Это означает, что Cursor должен генерировать предложения автодополнения, которые команда Cursor называет "tab suggestions".
Низколатентный движок синхронизации питает "tab model". Это генерирует предложения, которые отображаются серым цветом и могут быть приняты нажатием клавиши "Tab". Предложения должны генерироваться быстро, в идеале менее чем за секунду. Вот что происходит за кулисами:
🔸 Небольшая часть текущего контекстного окна (код) собирается локально клиентом
🔸 Код шифруется
🔸 Зашифрованный код/контекст отправляется на бэкенд
🔸 Бэкенд расшифровывает код/контекст
🔸 Предложение генерируется с использованием собственной LLM-модели Cursor
🔸 Предложение отправляется обратно
🔸 IDE отображает предложение. Нажатие "Tab" принимает предложение
🔸 ...процесс повторяется для следующего предложения
Эта "tab model" должна быть максимально быстрой, а передача данных — минимальной. Всегда есть компромисс между тем, сколько контекста отправлять, и качеством предложений: чем больше релевантного контекста может отправить Cursor, тем лучше предложения. Однако отправка большого количества контекста может замедлить отображение предложений, поэтому правильное решение этого вопроса является одним из вызовов для инженеров Cursor.
💬 3. Как работает чат Cursor без хранения кода на сервере
Cursor поддерживает режим чата для вопросов о кодовой базе, "общения" с кодовой базой или просьб к Cursor сделать что-то, что запустит агента для рефакторинга, добавления функциональности, изменения метода и т.д. Никакой исходный код не хранится на бэкенде, но все операции LLM выполняются там. Способ управления этим — через индексы кодовой базы.
Задавая вопрос в режиме чата: давайте возьмем пример вопроса о методе createTodo(), который является частью кодовой базы, определенной в server.js.
Промпт отправляется на сервер Cursor, где он интерпретируется и решает, что нужно выполнить поиск по кодовой базе.
Поиск выполняется с использованием индексов кодовой базы. Индексы кодовой базы — это ранее созданные эмбеддинги. Он пытается найти эмбеддинги, которые лучше всего соответствуют контексту, используя векторный поиск.
Запрос кода от клиента: сервер не хранит никакого исходного кода, но теперь запрашивает исходный код как из server.js, так и из index.html, чтобы он мог проанализировать оба и решить, какой релевантен.
Наконец, после векторного поиска и запроса релевантного исходного кода от клиента, сервер имеет контекст, необходимый для ответа на вопрос.
🔍 Семантическое индексирование кода с фрагментами кода
Чтобы разрешить векторный поиск с использованием эмбеддингов, Cursor сначала должен разбить код на более мелкие фрагменты, создать эмбеддинги и сохранить эти эмбеддинги на сервере:
🔸 Создание фрагментов кода. Cursor нарезает содержимое файла на более мелкие части. Каждая часть позже станет эмбеддингом
🔸 Создание эмбеддингов без хранения имен файлов или кода. Cursor даже не хочет хранить имена файлов на сервере, потому что это может считаться конфиденциальной информацией. Вместо этого он отправляет обфусцированные имена файлов и зашифрованные фрагменты кода на сервер. Сервер расшифровывает код, создает эмбеддинг с использованием моделей эмбеддингов OpenAI или одной из своих собственных, и сохраняет эмбеддинг в своей векторной базе данных, Turbopuffer
Создание эмбеддингов вычислительно дорого и является одной из причин, почему это делается на бэкенде Cursor, используя GPU в облаке. Индексирование обычно занимает менее минуты для кодовых баз среднего размера и может занимать минуты или дольше для больших кодовых баз.
🌳 Поддержание актуальности индекса с использованием деревьев Меркла
По мере редактирования кодовой базы с помощью Cursor или другой IDE, серверный индекс Cursor устаревает. Наивным решением было бы запускать операцию переиндексации каждые несколько минут. Однако, поскольку индексирование дорого в вычислительном отношении и использует пропускную способность для передачи зашифрованных фрагментов кода, это не идеально. Вместо этого Cursor умно использует деревья Меркла и высоколатентный движок синхронизации (движок синхронизации работает каждые 3 минуты) для поддержания актуальности серверных индексов.
Дерево Меркла — это дерево, каждый лист которого является криптографическим хешем базового файла (например, хеш для файла main.js). А каждый узел — комбинацией хешей своих детей.
Как работает это дерево Меркла:
• Каждый файл получает хеш, основанный на его содержимом. Листья дерева — это файлы
• Каждая папка получает хеш, основанный на хеше своих детей
Cursor использует очень похожее дерево Меркла, за исключением того, что он использует обфусцированные имена файлов. Клиент Cursor создает дерево Меркла на основе локальных файлов, а сервер также создает его на основе файлов, которые он закончил индексировать.
Каждые 3 минуты Cursor выполняет синхронизацию индекса. Для определения того, какие файлы нуждаются в переиндексации, он сравнивает два дерева Меркла; одно на клиенте, которое является источником истины, и одно на сервере, которое является состоянием индекса.
Обход дерева используется для определения места, где нужна переиндексация. Обход дерева — это не то, что мы, разработчики, часто реализуем, но для этого случая использования инженерам Cursor пришлось это сделать. Дерево Меркла делает обход дерева эффективным, потому что, начиная с корневого узла, довольно легко определить, совпадают ли хеши.
🔒 Безопасное индексирование
Даже несмотря на то, что Cursor не хранит код на серверной стороне, есть чувствительные части кодовой базы, которые плохо отправлять, даже в зашифрованном виде. Чувствительные данные включают секреты, API-ключи и пароли.
Использование .gitignore и .cursorignore — лучший способ обеспечить безопасность индексирования. Секреты, API-ключи, пароли и другая чувствительная информация не должны загружаться в систему контроля версий и обычно хранятся как локальные переменные или в локальных файлах окружения (.env файлы), которые добавляются в .gitignore. Cursor уважает .gitignore и не будет индексировать файлы, перечисленные там, а также не будет отправлять содержимое этих файлов на сервер.
Перед загрузкой фрагментов для индексирования Cursor также сканирует фрагменты кода на возможные секреты или чувствительные данные и не отправляет их.
📈 Индексирование очень больших кодовых баз
Для массивных кодовых баз — часто монорепозиториев с десятками миллионов строк кода — индексирование всей кодовой базы крайне времязатратно, использует много вычислительных ресурсов Cursor и в целом не нужно. Использование файла .cursorignore является разумным подходом в этом случае.
🎯 4. Anyrun: сервис-оркестратор Cursor
Anyrun — это название компонента-оркестратора Cursor, полностью написанного на Rust. Забавный факт: "Anyrun" — это отсылка к названию компании Cursor, Anysphere.
Anyrun отвечает за безопасный запуск ИИ-агентов в облаке. Всякий раз, когда вы запускаете задачу в Cursor через чат, агент начинает работать в облаке в фоновом режиме.
Изоляция процессов является обязательной при запуске агентов. То есть агент, работающий в облаке, должен иметь доступ только к коду и контексту для текущего пользователя, который его запускает, и ни к чему другому. Anyrun использует различные подходы к изоляции по клиентам:
• Виртуальные машины AWS EC2: используются для корпоративных клиентов. Этот подход обеспечивает полную изоляцию на границе ВМ
• AWS Firecracker: подход легкой виртуализации, использующий микро-ВМ. Основное преимущество Firecracker — молниеносное время запуска: микро-ВМ запускается всего за 125 миллисекунд! 🚀
В двух словах, вот как работает Anyrun:
🔸 Получает запрос на запуск агента 🔸 Выбирает подходящий метод изоляции (EC2 или Firecracker) 🔸 Запускает изолированную среду 🔸 Развертывает агента в этой среде 🔸 Управляет жизненным циклом агента 🔸 Обеспечивает безопасность и изоляцию
Снапшотинг — еще одна функция, которую поддерживает Anysphere; в скоро выходящей функции пользователь может сделать "снапшот" в Cursor и создать "точку сохранения". При этом Anyrun делает сохранение текущего состояния ВМ. Пользователь может затем позже "перемотать" к этой сохраненной точке.
⚠️ 5. Инженерные вызовы
Паттерны использования диктуют выбор технологий
Паттерны записи и чтения данных диктуют тип технологических решений, которые Cursor принимает для различных частей системы:
Рабочие процессы с кодом: транзакционные, нужна низкая латентность. Для функциональности, связанной с кодом (предложения tab, индексирование, обновление дерева Меркла), рабочая нагрузка представляет собой смесь чтения и записи. Индексы кода создаются как эмбеддинги, которые затем читаются. Латентность для этих операций должна быть максимально низкой.
Из-за потребности в низкой латентности важно иметь серверы рядом с пользователем. Поэтому рабочие нагрузки распределены по нескольким регионам, таким как западное и восточное побережья США, Великобритания, Европа, Япония, и обычно выбирается бэкенд, физически ближайший к пользователю, чтобы сократить латентность.
Аналитические рабочие нагрузки: интенсивная запись, допустима высокая латентность. Аналитические рабочие нагрузки — это такие вещи, как хранение метаданных о каждом запросе агента и других логов. Для этого случая использования латентность не так важна, и допустима eventual consistency. Cursor использует Warpstream для потоковой передачи событий и логов.
📊 Проблемы масштаба
Повторяющийся инженерный вызов исходит от невероятного роста Cursor: использование часто удваивалось месяц за месяцем, и все метрики использования сейчас в 100 раз выше, чем 12 месяцев назад.
Технологии и подходы, которые работают в меньшем масштабе, начинают ломаться при таком размере. Возьмем проблему индексирования файлов и их хранения в базе данных на серверной стороне. Год назад у Cursor было несколько миллионов файлов для индексирования в день, и эмбеддинги могли поместиться в одну базу данных без использования шардинга. По мере роста использования у Cursor стали сотни миллионов, а теперь миллиарды новых эмбеддингов для ежедневного хранения.
🥶 Проблема холодного старта в масштабе
Недооцененный вызов — это то, насколько сложно сделать "холодный старт" для массивного сервиса. Как объясняет Суалех:
"Представьте, что вы обрабатываете 100 000 запросов в секунду, и внезапно все ваши узлы умирают. При перезапуске системы ваши узлы обычно поднимаются один за другим. Скажем, вам удалось перезапустить 10 узлов из флота в 1000. Если вы не запретите людям делать запросы, эти 10 узлов будут разбиты всеми входящими запросами. Прежде чем эти 10 узлов смогли бы стать здоровыми, вы только что перегрузили эти узлы!
Это кусало нас много раз в прошлом. Всякий раз, когда у вас есть плохой инцидент, который требует холодного старта, вам нужно выяснить, как сделать это хорошо."
🔀 Головная боль шардинга
Хороший пример того, как рост использования вызывает проблемы, — это векторная база данных Cursor, которая хранит эмбеддинги. Ранее Cursor использовал Pinecone, у которого есть слоган "векторная база данных для масштаба в продакшене".
Pinecone работает с концепцией "подов". Под — это единица оборудования, которая хранит индексы; думайте об этом как о ВМ или микро-ВМ под капотом. Поды имеют заданный размер хранилища, и количество векторов (эмбеддингов), которые они могут хранить, основано на размерности вектора.
Хотя большинство реальных приложений не вырастают до сотен миллионов векторов, Cursor вырос! И поэтому им пришлось реализовать шардинг данных. Суалех объясняет:
"Нам пришлось вручную шардить индексы для пользователей в in-memory базы данных Pinecone. Однако шардинг привел к проблемам, таким как необходимость решать, когда перемещать пользователей между шардами, поскольку шард становился слишком большим.
Шардинг вызвал сложности, такие как:
• Недоиспользование: мы обнаружили, что либо работаем намеренно с более низким использованием в хранилище: обычно ограничивая использование на 70% пространства базы данных
• Сложные миграции: когда пространство базы данных в шарде начинает достигать 80-90%, нам нужно решить, какого пользователя вывести из шарда"
Постоянная необходимость пере-шардинга по мере роста использования стала подверженной ошибкам и расстраивающей. Суалех сказал мне, что самый большой урок, который выучила команда, — это избегать шардинга, где это возможно, в будущем.
Turbopuffer был стартапом, который обещал это, поэтому Cursor попробовал его и мигрировал значительную часть своих случаев использования векторного поиска. Суалех говорит мне:
"Замечательная вещь в Turbopuffer — это то, как каждый пользователь Cursor является отдельным арендатором в их системе. Это означает, что нам больше не нужно иметь дело со сложными миграциями между шардами."
🔍 Трудно обнаруживаемые сбои
При работе с крупномасштабной системой вы начинаете получать неожиданные баги, которые могут вызвать сбои, которые трудно обнаружить и отладить.
У Cursor была проблема, когда эмбеддинги для очень больших файлов из 60 000 строк или более не сохранялись в их базе данных эмбеддингов из-за бага в инфраструктуре базы данных. Этот баг был неизвестен команде, и отчетность пропустила его. Но поскольку эти большие эмбеддинги не сохранялись, процесс пытался снова после неудачи... и затем снова, и снова.
Внезапно нагрузка на базу данных увеличивалась, в то время как частота ошибок оставалась нормальной. Команда подозревала, что что-то не так, но не могла точно понять, что именно! 🤔 Несколько инженеров начали отладку:
• Кто-то заметил, что коэффициент попаданий в кеш был немного не в порядке: подсказка! • Они добавили больше мониторинга вокруг кеширования • Затем они выяснили, что что-то не так с большими файлами • Команда подозревала, что race condition что-то делает • ...но затем поиск фактического race condition был большой работой! Поиск race conditions — даже когда вы знаете, что ищете их — удивительно сложен!
Это был один пример из многих сбоев, с которыми справилась команда.