Linear хранит всю вашу базу данных в браузере — вот как это устроено
Сооснователь Linear Туомас написал движок синхронизации раньше любой продуктовой фичи — почти неслыханное для стартапа решение. Расплата: каждый клик сначала применяется локально в IndexedDB, а сервер узнаёт об этом уже после, отчего приложение ощущается нативным, а сеть полностью скрыта.
Мутации сначала попадают в локальное хранилище IndexedDB; сервер узнаёт через WebSocket только после перерисовки экрана. Каждое свойство модели — отдельный observable в MobX, поэтому изменение одного поля перерисовывает одну ячейку.
Четыре миграции сборщиков урезали отгружаемый JS на 50%, сжатый размер на 30%, первую отрисовку в Safari на 59%. Service worker тихо предкэширует ~1200 ассетов после первого входа.
Тред не убеждён. Пользователи жалуются на медленный поиск, разрастающуюся память, многоминутную блокировку синхронизации. Главный вопрос: оптимистичный UI — это выигрыш в производительности или ложь о сохранности данных?
Архитектура «база данных в браузере» от Linear, разобранная по косточкам

Разбор Linear на performance.dev открывается признанием самого́ сооснователя продукта: «Буквально первые строки кода, которые я написал, были движком синхронизации, что очень нетипично». Это единственное решение — построить слой синхронизации данных раньше всего, на что пользователь мог бы кликнуть, — определяет всё остальное в том, как работает приложение в треде.
База данных живёт в вашем браузере, а не на сервере

Архитектурная инверсия проста на словах, но труднее в исполнении. Традиционные инструменты управления проектами следуют одному циклу: клик пользователя → HTTP-запрос → запрос к серверу → перерисовка браузера, со спиннерами в ожидании. Linear устраняет этот цикл, держа полную копию данных рабочего пространства пользователя в IndexedDB. Когда вы меняете статус задачи, три вещи происходят одновременно: обновляется observable в MobX (мгновенно перерисовывая UI), мутация пишется в долговременную очередь транзакций в IndexedDB, а дельта ставится в очередь на передачу по WebSocket. К сети никогда не обращаются до того, как экран изменится.
При загрузке приложение гидратируется из IndexedDB в пул объектов MobX в памяти. Состояния «загрузка задач» нет, потому что данные уже есть на машине. Для крупных рабочих пространств самые тяжёлые таблицы (Issue и Comment) гидратируются лениво, по требованию — статья утверждает, что рабочее пространство на 10 000 задач загружается примерно так же быстро, как на 100, потому что стоимость старта зависит от структуры пространства, а не от его размера.
Четыре миграции сборщиков и service worker, держащий 1200 ассетов

21 МБ минифицированного JavaScript, которые один комментатор счёл подозрительными, агрессивно нарезаны на сотни чанков уровня маршрута, загружаемых по требованию и нацеленных только на современные браузеры (без устаревших полифилов). Четыре миграции сборщиков — Parcel → Rollup → Vite → Rolldown — каждая гналась за одной целью: меньше отгружаемого кода. Измеренные результаты: на 50% меньше кода, на 30% меньше после сжатия, загрузка с холодным кэшем на 10–30% быстрее, первая отрисовка в Safari улучшена на 59%, память снижена на 70–80%.
Вместо того чтобы позволять браузеру последовательно обнаруживать зависимости чанков через разбор JavaScript, Linear объявляет полный граф зависимостей записями <link rel=modulepreload> в HTML. Атрибут crossorigin на ссылках предзагрузки совпадает со входным скриптом, позволяя браузеру переиспользовать закэшированные запросы, а не считать предзагрузку и импорт отдельными запросами. Service worker предкэширует около 1200 хешированных ассетов после первого входа; к моменту, когда пользователь куда-либо перейдёт, всё приложение уже находится в памяти.
До того как выполнится хоть какой-то JavaScript, встроенный CSS отрисовывает правильное состояние загрузки — ширину сайдбара, радиус скругления, токены тёмной темы — прямо из localStorage, так что оболочка уже корректно оформлена к моменту прихода первого бандла.
Гранулярные observables: почему изменение одного поля не перерисовывает список

Каждое свойство каждой модели — отдельный observable в MobX. Компоненты, обёрнутые в observer(), точно знают, от каких полей зависят. Смена статуса одной задачи вызывает перерисовку только тех компонентов, что читают это поле, — не родительского списка, не сайдбара. Статья утверждает, что пятьдесят одновременных обновлений от нескольких пользователей равны пятидесяти перерисовкам отдельных ячеек, а не пятидесяти перерисовкам всего списка. Удаление любой из трёх опор (локальная база, оптимистичная запись, гранулярные observables) деградирует целое: локальные данные без оптимистичной записи всё равно показывают спиннеры при сохранении; оптимистичная запись без гранулярных observables вызывает рывки при каждом подтверждении сервера.
Анимации следуют той же дисциплине: переходы получают только композитные свойства (transform, opacity) — никогда вызывающие лейаут вроде height или margin. Состояния наведения появляются мгновенно и угасают за 150 мс, имитируя нативный ритм macOS. Токены тайминга сидят заметно ниже отраслевых норм: --speed-quickTransition: .1s, --speed-regularTransition: .25s.
Тред переворачивает нарратив: это скорость или её иллюзия?
«Это причудливо хвалебно. Приложение быстрое, потому что оно некорректно. У пользователя нет способа узнать, согласован ли его вид с видом любого другого пользователя… Разработчики Linear, похоже, верят, что тихая потеря данных — приемлемая цена за поддержание иллюзии скорости». — давний участник HN с 24k карма
Это звучит как самый острый разворот треда. Статья подаёт оптимистичные обновления как приём производительности; критик переосмысляет их как угрозу корректности. И он не один.
«Мой клиент Linear постоянно застревает в „синхронизации“, полностью блокируя любое взаимодействие. Я бы предпочёл спиннеры загрузки, чем оказаться, по сути, запертым вне всего приложения на 10 минут». — активный пользователь HN
Третий голос — сооснователь Rocicorp, команды за движком синхронизации Zero — пришёл не опровергать, а вербовать:
«Если вас интересует такой опыт для вашего приложения, посмотрите Zero (https://zero.rocicorp.dev/). Мы также перечисляем здесь некоторые альтернативы… Если интересно, как эти штуки работают внутри, посмотрите проектный документ Replicache: https://doc.replicache.dev/concepts/how-it-works. Replicache был предшественником Zero, и базовый протокол работает так же». — Аарон Будман, сооснователь Rocicorp
Комментарий Будмана — тихий разворот треда: локальная синхронизация — не проприетарная магия Linear, а паттерн с растущей экосистемой альтернатив.
Сооснователь Clor напрямую протыкает бенчмарк статьи «300 мс против миллисекунд»:
«Проблему большого RTT между HTTP-клиентом и сервером, когда она вызвана скоростью света, не решить. Но можно разместить бэкенд рядом с пользователями и сделать его быстрым. Например, вполне возможно держать бэкенд веб-приложения в пределах ~10 мс RTT от большинства пользователей и рендерить ответы тоже за ~10 мс. Иначе говоря, вполне можно сделать традиционное CRUD-приложение, где то же действие занимает скорее 30 мс, а не 300 мс». — сооснователь Clor
Что на самом деле изменилось — и о чём статья молчит
Статья — настоящий технический документ: стратегия предзагрузки модулей, история гранулярности MobX, четыре миграции сборщиков, токены тайминга анимаций — всё это реально, конкретно и проверяемо. Чего она избегает — это режим отказа. Инженер в треде, который месяцами строил похожий движок синхронизации, каталогизирует, что ломается, когда два пользователя одновременно офлайн: конфликты «побеждает последний пишущий», удалённые-затем-отредактированные элементы, расхождение порядка, дрейф схемы при переподключении, 5000 очередных мутаций, выстреливающих разом. «Всё, что может пойти не так в онлайн-первом мире, может пойти не так и в офлайн-первом, но вас уведомят об этом разом и позже — или вообще никогда».
Движок синхронизации, который Туомас написал в первый день, — не решённая задача, наряженная в быстрый UI. Это трудная задача распределённых систем, наряженная в быстрый UI. Статья объясняет, почему Linear ощущается быстрым. Тред объясняет, чего это ощущение стоит.
Получайте такие разборы каждый день
Главные истории Hacker News — на email, в Telegram или RSS.
Qwen 3.7 Max стоит долю от цены Claude, и разрыв сокращается
Инженер и самопровозглашённый фанат фантастики утверждает, что Anthropic и OpenAI превратились в «экономики OnlyFans» — выкачивают деньги с подписок у преданных фанатов через парасоциальную привязанность к бренду, а не за счёт реального превосходства моделей. Китайские открытые модели, особенно Qwen 3.7 Max, перешагнули порог «достаточно хорошо».