Google Analytics Core reporting API v3 needs 4 minutes to respond

Viewed 1092

We have been using the Google Analytics core reporting API v3 in our Laravel application for over 2 years now. We have done some optimizations over the years but our setup has been the same and has been working for quite some time now.

Earlier this month something strange started happening, very rarely a request would take around 4 minutes to complete. The first occurrence I could find was on the 6th of December. first slow response

At first I thought nothing of it and assumed we hit some kind of limit, however the problem persisted and keeps showing up more and more.

So the past couple of days I have been searching for the root of the problem and I haven't the slightest idea about why this is happening. There is no sign in the developers console of hitting limits.

I took sometime to find out how many responses where slow when I kept calling the API in rapid succession and 4 out of 14 calls took between 240.3 and 240.5 seconds. I've printed the results of the calls and have only called 1 specific view to see if it wasn't an issue with sending to much data in 1 request. The view did not have data for the day so all requests came back with 0 rows and 0's as total for all metrics. There were no error messages appended.

Response times

The developer console says otherwise, it says non of the requests take over 300ms in the past hour: developer console times

This is happening on our servers and local environment so I do not think it has something to do with nginx/internet issues.

I am pretty sure my code is fine and this does have something to do with limits but I have added it anyway. This method pulls all the views we have stored in our database and does 3 calls to the API to pull multiple metrics, this can't be done in 1 request because you can't not request over 10 metrics at a time.

public function getExternal()
{
    $this->logInfo("Retrieving reports");
    $views = $this->getLinkedViews();
    $i = 1;
    foreach ($views as $view) {
        try {
            $params= array('dimensions' => 'ga:campaign, ga:source, ga:medium, ga:date' );
            $start = microtime(true);
            $response = $this->analytics->data_ga->get(
                'ga:' . $view->datasource_id,
                $this->startDate,
                $this->endDate,
                'ga:users, ga:sessions, ga:bounceRate, ga:avgSessionDuration, ga:goal1Completions , ga:goal2Completions , ga:goal3Completions , ga:goal4Completions',
                $params
                );
            var_dump('call time: ' . (microtime(true) - $start));
            $data = $response->getRows();
            if ($data != null) {
                $this->rawData[$view->datasource_id. '.1'] = $data;
            }
            $response = $this->analytics->data_ga->get(
                'ga:' . $view->datasource_id,
                $this->startDate,
                $this->endDate,
                'ga:goal5Completions , ga:goal6Completions , ga:goal7Completions , ga:goal8Completions , ga:goal9Completions , ga:goal10Completions , ga:goal11Completions , ga:goal12Completions',
                $params
                );
            $data = $response->getRows();
            if ($data != null) {
                $this->rawData[$view->datasource_id. '.2'] = $data;
            }
            $response = $this->analytics->data_ga->get(
                'ga:' . $view->datasource_id,
                $this->startDate,
                $this->endDate,
                'ga:goal13Completions , ga:goal14Completions , ga:goal15Completions , ga:goal16Completions , ga:goal17Completions , ga:goal18Completions , ga:goal19Completions , ga:goal20Completions',
                $params
            );
            $data = $response->getRows();
            if ($data != null) {
                $this->rawData[$view->datasource_id. '.3'] = $data;
            }
            $this->logInfo($view->name . ' metrics retrieved'. "(" .$i . " - " . count($views). ")", $view->datasource_id);
            $i++;
        } catch (\Google_Service_Exception $gse) {
            dd($gse);
            $this->logError($gse->getMessage(), isset($view->datasource_id) ? $view->datasource_id : null, $this->formatErrorCode($gse->getCode()));
        } catch (\Throwable $t){
            dd($t);
            $this->logError($t, isset($view->datasource_id) ? $view->datasource_id: null, $this->formatErrorCode('000001'));
        }
4 Answers

I reached out to Google, there response was as followed:

The team is aware of this issue, and is still currently working on a fix for this. For now, I would recommend the following suggestions in order to alleviate this issue:

• Simplify query complexity via reducing their scope (e.g. dimensions, columns, date ranges, filters, etc); see this page for a general idea of what drives complexity
• Implement exponential back-off

EDIT:
Google has fixed this issue, we had to implement the above suggestions because it seems we where getting throttled way more often than before the bug occurred.

I implemented a retry logic, sleeping it 5 seconds to cool down in case of timeout. I tested the API call and didn't matter how complex was the request, the success or fail was random.

To force the timeout, set a custom Guzzle client to the Google Client, specifying the max conn time in the constructor (more than 2s looks like it's gonna fail) so the client throws an exception when the request fails.

Exactly the same situation we have been struggling lately. Requests hangs occurs totally randomly no matter the complexity of the query, same happens even with the simplest query. The best workaround currently is to set a short timeout and retry, since it will most probably fail if taking more than 2 seconds. But in this case the only downside is that, if you go on retrying too much (even with exponential backoffs), you will start getting 403: "Number of recent failed reporting API requests is too high". It is weird that our requests does not actually "fail" as they don't appear at panel, but they still are considered as failed req. and result in start of these 403 responses. Error response graph

So I would suggest also increasing the req. timeout value on each try and give a larger break on these 403 errors. Maybe trying setting quotaUser and userIp params randomly each time would help also.

We had the exact same problem, with paying customers getting (obviously) pretty upset about it. Reducing complexity isn't a solution anymore as it has only gotten worse the last few days and even the simplest requests take ages or eventually time out.

FYI our solution: we got rid of Google Analytics and put everything in our own database => average request times from 1 minute and more to below 2 seconds. Most of the data, in combination with free API's such as https://geolocation-db.com/ is publicly available anyways.

Related