Скрипт на Google Apps Script, который в песочнице отрабатывает за 40 секунд, у клиента на боевых данных падает с ошибкой Exceeded maximum execution time - с этого обычно начинается настоящее знакомство с лимитами Google Apps Script. Официальной сводной таблицы у Google по сути нет: 6 минут на выполнение упоминаются в одном разделе документации, 20 000 вызовов UrlFetch в сутки - в другом, 20 триггеров на пользователя - в третьем. За несколько лет автоматизации Google Таблиц, форм на Tilda и статусов доставки СДЭК я упирался почти в каждый из этих потолков минимум один раз. Разберу, какие ограничения реально всплывают на практике, откуда они берутся и как спроектировать скрипт так, чтобы не переписывать его через месяц после запуска.
Какие лимиты Google Apps Script действуют на аккаунт
Первое, что стоит понять: набор квот зависит от типа аккаунта. У личного Gmail и у корпоративного Google Workspace лимиты Apps Script отличаются иногда в разы, и разработчик часто узнаёт об этом только когда скрипт, который отлично работал у него в тестовом аккаунте, начинает падать у клиента на обычной личной почте.
| Параметр | Обычный Gmail-аккаунт | Google Workspace |
|---|---|---|
| Время выполнения скрипта | 6 минут | 30 минут |
| Runtime простых триггеров (onEdit, onOpen) | 30 секунд | 30 секунд |
| Runtime кастомных функций в Таблицах | 30 секунд | 30 секунд |
| Суммарное время работы триггеров в сутки | 90 минут | 6 часов |
| Триггеров на пользователя на один скрипт | 20 | 20 |
| Вызовов UrlFetch в сутки | 20 000 | 20 000 |
| Писем через MailApp в сутки | 100 | 1 500 |
Цифры привожу по документации Google на момент публикации, но она периодически меняется без анонсов, поэтому если строите на лимитах критичную логику - закладывайте запас минимум в 20-30%, а не работайте впритык к паспортным значениям.
Лимит времени выполнения: 6 минут - это меньше, чем кажется
На бумаге 6 минут звучат достаточно, пока не начинаешь гонять цикл по нескольким тысячам строк Google Таблицы с обращением к внешнему API на каждой итерации. Условная задача - подтянуть статус заказа из личного кабинета СДЭК для 3000 строк с интервалом между запросами хотя бы в 200 мс ради устойчивости - сама по себе съедает больше 10 минут, и скрипт падает по таймауту на середине, оставляя часть строк обновлёнными, а часть нет.
Приём, который реально работает на практике: не пытаться обработать весь массив за один запуск. Скрипт сохраняет индекс последней обработанной строки в PropertiesService, обрабатывает порцию за 4-5 минут с запасом до лимита, а перед завершением сам ставит себе одноразовый триггер на следующий запуск через ScriptApp.newTrigger(…).timeAfter(60000). Получается конвейер из коротких запусков вместо одного длинного, который никогда не упрётся в потолок.
Отдельно стоит помнить про простые триггеры - onEdit и onOpen: у них лимит жёстче, 30 секунд, и он не увеличивается на Workspace. Если в обработчике onEdit висит тяжёлая логика с внешними запросами, она обязана либо укладываться в эти секунды, либо передавать работу дальше через собственный триггер с более щедрым лимитом.
Сколько триггеров можно повесить на скрипт
20 триггеров на пользователя на один скрипт - лимит одинаковый что для личного аккаунта, что для Workspace, и в него легко упереться на проекте, где на разные листы одной Таблицы навешаны отдельные onEdit, плюс несколько time-driven триггеров для разных участков автоматизации, плюс триггеры для отправки отчётов. При добавлении новой функции я сначала смотрю список действующих триггеров через File → Project Triggers, а не добавляю новый вслепую - иначе рано или поздно ловишь ошибку This trigger is not available anymore, и часть автоматизации молча перестаёт работать.
Второй потолок, который менее заметен - суммарное время работы всех триггеров в сутки: 90 минут для обычного аккаунта и 6 часов для Workspace. Если несколько time-driven триггеров запускаются каждую минуту и каждый работает по 2-3 минуты, бюджет выгорает за первые несколько часов дня, а остальные запуски просто не происходят без явной ошибки в логах - скрипт как будто засыпает без объяснений.
Для сравнения: бот на aiogram, который крутится на своём VPS, такими квотами вообще не связан - процесс на сервере обрабатывает обновления Telegram столько раз в секунду, сколько выдержит железо, и никакого дневного бюджета минут у него нет. Это одна из причин, почему для высокочастотной логики я стараюсь не тянуть её в Apps Script, а выносить на отдельный сервер.
Бесплатный материал
🎁 Полезный скрипт в подарок
Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.
Без спама. Отписка в 1 клик.
Квоты на сервисы, которые ловишь незаметно
Помимо времени выполнения и триггеров, у каждого встроенного сервиса Apps Script свой потолок, и часть из них выясняется только в проде.
- UrlFetchApp ограничен 20 000 вызовами в сутки и 50 МБ на один ответ. Звучит много, пока не начинаешь синхронизировать статусы заказов с СДЭК или подтягивать данные из WooCommerce каждые пять минут для сотен позиций - при неудачном ретрае без задержки квота выгорает за несколько часов, а не за сутки.
- MailApp и GmailApp дают 100 писем в сутки на личном аккаунте и 1500 на Workspace - этого достаточно для уведомлений админу, но не для рассылки клиентам по каждому заказу с Tilda-формы: там нужен отдельный сервис отправки, а не Apps Script в роли почтового шлюза.
- PropertiesService хранит суммарно 500 КБ и не больше 9 КБ на одно значение - удобно для флагов и индексов обработки, но не для кэша больших ответов API. Для них логичнее CacheService с TTL до 6 часов.
- Отдельная головная боль - ошибка Service invoked too many times in a short time при частых обращениях к SpreadsheetApp. Она возникает не от превышения суточной квоты, а от слишком частых вызовов подряд, поэтому цикл из тысяч setValue() по одной ячейке нужно заменять на один getRange().setValues() с двумерным массивом.
Как проектировать скрипт, чтобы не упираться в лимиты
Из практики по десяткам интеграций с Google Таблицами и Tilda-скриптами вывел для себя правила, которые снимают большинство проблем с лимитами Apps Script ещё на этапе архитектуры:
- Разбивать длинные обработки на порции с сохранением состояния в PropertiesService и продолжением через ScriptApp.newTrigger, а не пытаться закрыть весь массив данных за один запуск.
- Ставить LockService.getScriptLock() на любой обработчик, который может сработать параллельно - иначе два одновременных триггера начинают писать в одну Таблицу и портят друг другу данные, а заодно быстрее выжигают квоту.
- Кэшировать повторяющиеся внешние запросы через CacheService вместо того, чтобы дёргать один и тот же метод СДЭК или платёжного API на каждой итерации цикла.
- Делать экспоненциальный backoff при ошибках UrlFetch вместо мгновенного повтора - иначе одна нестабильная интеграция сжигает суточный лимит вызовов за час.
- Работать с диапазонами Таблиц батчами: одна операция getValues/setValues на весь массив вместо цикла с обращением к ячейке за ячейкой.
Часть этой логики - батчинг, ретраи с задержкой, работу с PropertiesService - проще не писать заново на каждом проекте: в библиотеке готовых скриптов для Google Таблиц у меня уже собраны рабочие заготовки под типовые задачи с учётом лимитов, о которых идёт речь в статье.
Когда лимиты Apps Script - сигнал переезжать на другую платформу
Если задача регулярно упирается в 6 минут, требует более частого запуска, чем раз в минуту, или должна стабильно принимать вебхуки без задержек на холодный старт - это повод не бороться дальше с квотами, а вынести логику за пределы Apps Script.
| Критерий | Google Apps Script | n8n (self-hosted) | Python-скрипт на VPS |
|---|---|---|---|
| Лимит времени на один запуск | 6-30 минут | не ограничен платформой | не ограничен |
| Дневной бюджет времени триггеров | 90 минут / 6 часов | нет | нет |
| Стоимость хостинга | бесплатно в рамках Google-аккаунта | от аренды сервера | от аренды сервера |
| Гибкость интеграций с CRM и эквайрингом | ограничена встроенными сервисами | широкая, через ноды и HTTP | максимальная, любой SDK |
Для интеграции T‑Bank или другого эквайринга с WooCommerce, для приёма вебхуков с большим потоком заказов или для обработки статусов СДЭК в реальном времени я обычно сразу закладываю n8n или отдельный Python-сервис вместо Apps Script - так же, как aiogram-бот логичнее держать на своём сервере, а не пытаться эмулировать его через триггеры Таблиц. Apps Script отлично закрывает автоматизацию внутри Google-экосистемы, но не задумывался как замена полноценному бэкенду.
Связка сервисов без программистов
Автоматизация / n8n
от 25 000 ₽
Подробнее →Частые вопросы
Что произойдёт, если скрипт Apps Script превысит лимит времени выполнения?
Выполнение прерывается с ошибкой Exceeded maximum execution time, и ничего не откатывается автоматически. Если скрипт писал в Таблицу построчно, часть строк успеет обновиться, часть нет - поэтому важно проектировать обработку с чекпоинтами и делать её идемпотентной, чтобы повторный запуск не портил уже обработанные данные.
Можно ли увеличить лимиты Google Apps Script, купив Google Workspace?
Часть квот действительно растёт - время выполнения скрипта увеличивается с 6 до 30 минут, суточный бюджет времени триггеров с 90 минут до 6 часов, лимит писем с 100 до 1500 в сутки. Но не все ограничения снимаются: 20 триггеров на пользователя на скрипт и 30-секундный лимит простых триггеров одинаковы для любого типа аккаунта, и купить их расширение отдельно нельзя.
Как понять, что скрипт скоро упрётся в квоту?
Смотрю журнал выполнений в Apps Script Dashboard - там видно фактическое время каждого запуска и причины ошибок. Отдельно веду счётчик обращений к UrlFetch в PropertiesService, чтобы не гадать по факту падения, а видеть приближение к суточному лимиту заранее.
Стоит ли использовать Apps Script для интеграции с СДЭК или платёжными системами вроде T‑Bank?
Для периодической проверки статусов или несложных уведомлений - да, это быстрый и дешёвый вариант. Для приёма вебхуков с ощутимым потоком заказов или для комплексной связки CRM, эквайринга и доставки лимиты Apps Script и задержки холодного старта веб-приложения становятся узким местом, и логичнее выносить эту часть на отдельный сервис.