Как рассчитываются квоты запросов Google Drive API? 'userRateLimitExceeded' срабатывает, когда ниже известных квот - PullRequest
3 голосов
/ 30 апреля 2019

В настоящее время при интеграции проекта с Google Drive с использованием Клиентской библиотеки Drive API для Java , при использовании служебной учетной записи для олицетворения пользователя для извлечения его содержимого диска, userRateLimitExceeded запускается, когдачисло зарегистрированных запросов значительно ниже минимальной определенной квоты, которую можно увидеть в консоли.

В домене, который используется для проверки интеграции с Google Диском, в настоящее время количество запросов на пользователя в расчете на каждые 100 секунд составляет 1000.при запуске программы, где служебная учетная запись используется для олицетворения пользователя и получения его файлов, клиент Java создает GoogleJsonResponseException из-за useLimits , а именно userRateLimitExceeded ,Однако максимальный когда-либо зарегистрированный консольный всплеск составляет 198 запросов / минуту, что намного ниже указанного предела.

Попытка установить случайный параметр quotaUser для запроса, как подробно описано в разрешении ошибки page но это приводило к тому же результату.

Экспоненциальная политика отката, описанная в документации , запускается с ожиданием в 1 секунду, после чего увеличение на самом деле мало помогает, с запросами, в основном, накапливающимисячерез 20, 30 секунд после запуска квоты.

Чтобы диагностировать это, создали небольшой модульный тест для различных сценариев запуска, где мы запускаем 1000 вызываемых объектов, которые просто перечисляют первые 100 файлов вхорошо известная область Google Диска в указанном домене с использованием экземпляра объекта Drive .

public class GoogleDriveRequestTest {

    private Drive googleDrive;
    //other class attributes

    @Before
    public void setup() throws Exception {
        //sensitive data 

        final Credential credential = new GoogleCredential.Builder()
                .setTransport(httpTransport)
                .setJsonFactory(JacksonFactory.getDefaultInstance())
                .setServiceAccountId(serviceAccountId)
                .setServiceAccountPrivateKey(gdrivePrivateKey)
                .setServiceAccountScopes(ImmutableSet.of(DriveScopes.DRIVE,
                        DirectoryScopes.ADMIN_DIRECTORY_USER,
                        DirectoryScopes.ADMIN_DIRECTORY_GROUP))
                .setServiceAccountUser(serviceAccountUser)
                .build();

        this.googleDrive = new Drive.Builder(httpTransport, JacksonFactory.getDefaultInstance(), credential)
                .setApplicationName("Google Drive API Load Test")
                .build();

        //other initialization code
    }

    @Test
    public void shouldRequestListOfFilesOverAndOverAgain() {
        Stream<Integer> infiniteStream = Stream.iterate(0, i -> i + 1);

        AtomicInteger requestCounter = new AtomicInteger(1);

        infiniteStream
                .limit(1000)
                .map(i -> new GoogleDriveCallable(requestCounter))
                .parallel()
                .map(executorService::submit)
                .map(execution -> {
                    try {
                        return execution.get();
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                })
                .forEach(triple -> System.out.println("Completed request n " + triple.getMiddle() + " in " + triple.getRight() + " millis on thread " + triple.getLeft()));
    }

    private class GoogleDriveCallable implements Callable<Triple<String, Integer, Long>> {
        private final AtomicInteger requestNumber;

        public GoogleDriveCallable(AtomicInteger requestNumber) {
            this.requestNumber = requestNumber;
        }

        @Override
        public Triple<String, Integer, Long> call() throws Exception {
            try {
                try {
                    StopWatch timeIt = StopWatch.createStarted();
                    googleDrive
                            .files()
                            .list()
                            .setSpaces("drive")
                            .setQuotaUser(UUID.randomUUID().toString())
                            .setFields("nextPageToken, files(id, name, mimeType)")
                            .setPageSize(100)
                            .execute();
                    timeIt.stop();
                    return new ImmutableTriple<>(Thread.currentThread().getName(), requestNumber.getAndIncrement(), timeIt.getTime());
                } catch (GoogleJsonResponseException gjre) {
                    GoogleJsonError.ErrorInfo firstReportedError = gjre.getDetails().getErrors().get(0);
                    if (USER_LIMIT_QUOTA_EXCEEDED_ERROR_REASON.equals(firstReportedError.getReason())) {
                        fail("Google user rate limit triggered during request n " + requestNumber);
                    } else {
                        throw gjre;
                    }
                }
            } catch (Exception e) {
                throw new RuntimeException("BOOM during request n " + requestNumber, e);
            }
            return null;
        }
    }
}

Запуск этого модульного теста с разным количеством потоков (минимальная разница в 5 минут между запусками, чтобы гарантировать отсутствиеинтерференция):

  • 1 поток
    • все 1000 запросов проходят через
      • мин. Время: 6 м49 с (в среднем 2,44 запроса в секунду -> 244 запросов в 100 с)
      • макс. Время: 7 м 52 с (в среднем 2,12 запроса в секунду --> 212 запросов в 100 секунд)
  • 2 потока
    • все 1000 запросов проходят
      • мин. Время: 3m36s (в среднем 4,63 запроса в секунду -> 463 запроса в 100 секунд)
      • максимальное время: 3 м 46 с (в среднем 4,42 запроса в секунду -> 442 запроса в 100 секунд)
  • 3 потока
    • все 1000 запросов проходят
      • мин. Время: 2 мсек (в среднем 6,67 запросов в секунду -> 667 запросов в 100 секунд)
      • максимальное время: 2 мсек (в среднем 6,62 запросов в секунду -> 662 запросов в 100 секунд)
  • 4 потока
    • мин. время: 11 с(в среднем 8,27 запросов в секунду -> 827 запросов в 100 секунд) с userRateLimitExceeded , запущенных примерно через91 запрос
    • максимальное время: 40 с (в среднем 8,75 запросов в секунду -> 875 запросов в 100 секунд) с userRateLimitExceeded , запущенных после примерно 350 запросов
  • 5 потоков
    • мин. Время: 4 с (в среднем 8,75 запросов в секунду -> 875 запросов в 100 секунд) с userRateLimitExceeded срабатывает после 35 запросов
    • максимальное время: 7 с (в среднем 9,57 запросов в секунду -> 957 запросов в 100 секунд) с userRateLimitExceeded , запущенным после 67 запросов

Было подтверждено, чтоникто другой не использует домен, поэтому ничто не должно мешать этим тестам.

Почему эти два последних сценария не срабатывают, если квота пользователя срабатывает, если мы не достигли 100-секундного момента времени и, даже если экстраполировали показателидо 100 секунд, и они приближаются, им все еще не хватает 1000 запросов на пользователя на 100-секундную квоту?

1 Ответ

1 голос
/ 14 мая 2019

Связь с поддержкой Google подчеркнула, что, кроме известной квоты, бэкэнд-сервисы имеют пакетную защиту.

Таким образом, если скорость запроса постоянна, применяются квоты, но в случае запросаСкорость передачи пакетов и указанные пакеты, если они экстраполируются как обычный трафик, приводят к переполнению квот, API ответит ошибками UseLimit.

...