Stream reports using GoogleAdsService

  • To retrieve Google Ads API entities and reporting data, you can use either GoogleAdsService.SearchStream or GoogleAdsService.Search.

  • While both methods are suitable for production and fetching objects and reports, SearchStream provides a stream of GoogleAdsRow objects in a single continuous response, whereas Search returns pages of GoogleAdsRow objects over multiple responses.

  • SearchStream sends a single request and initiates a persistent connection, allowing data packets to download immediately and potentially offering improved performance for bigger reports compared to the paged requests of Search.

  • For multiple page reports, SearchStream is typically faster than Search as it avoids multiple round trips.

  • Daily limits for both methods are based on access levels, and a single query or report is counted as one operation regardless of whether it is paged or streamed.

To retrieve Google Ads API entities and reporting data, use one of these methods:

Here are the high-level distinctions for the two methods:

GoogleAdsService.SearchStream GoogleAdsService.Search
Suitable for production code Yes Yes
Service GoogleAdsService GoogleAdsService
Scenario Fetching objects and reports Fetching objects and reports
Response Stream of GoogleAdsRow objects Pages of GoogleAdsRow objects
Response's fields Only those specified in the query Only those specified in the query
Daily limits Daily limits based on access levels Daily limits based on access levels

While Search sends multiple paginated requests to download an entire report, SearchStream sends a single request and initiates a persistent gRPC connection with the Google Ads API regardless of report size.

For SearchStream, data packets start to download immediately in batches of SearchGoogleAdsStreamResponse objects. Your code can iterate over incoming batches as they arrive without waiting for the entire stream to finish.

By eliminating the round-trip network time required to request each individual page of a Search response, SearchStream offers improved performance over paging, especially for large reports.

Example

This example looks at a report that consists of 100,000 rows. The following table breaks down the request and response differences between the two methods:

SearchStream Search
Page size Not Applicable 10,000 rows per page
Number of API requests 1 request 10 requests
Number of API responses 1 continuous stream 10 responses

Performance factors

For most use cases, we recommend SearchStream over Search for the following reasons:

  • Single-page reports (under 10,000 rows): There are no significant performance differences between the two methods.
  • Multi-page reports: SearchStream is typically faster because multiple network round trips are avoided, and reading or writing from disk cache is less of a factor.
  • Memory efficiency: When processing large reports with SearchStream, iterate through stream chunks and process rows as they arrive rather than buffering all rows in memory at once to avoid out-of-memory (OOM) errors.
  • Connection resiliency: Because SearchStream relies on a persistent connection, long-running streams can be interrupted by network drops or deadline timeouts. Configure appropriate RPC timeouts and implement retry logic for transient stream errors.

Rate limits

Daily operation limits for both methods adhere to the standard limits and access levels of your Google Cloud project. A single logical report query is counted as one operation toward your daily operation quota regardless of whether the result is streamed with SearchStream or retrieved across multiple cached pages using Search with a page_token. However, each paged Search request still counts as an individual RPC call against short-term rate limits.

Code example

The following code example demonstrates how to execute a streaming report query using the client libraries:

Java

private void runExample(GoogleAdsClient googleAdsClient, long customerId) {
  try (GoogleAdsServiceClient googleAdsServiceClient =
      googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) {
    String query = "SELECT campaign.id, campaign.name FROM campaign ORDER BY campaign.id";
    // Constructs the SearchGoogleAdsStreamRequest.
    SearchGoogleAdsStreamRequest request =
        SearchGoogleAdsStreamRequest.newBuilder()
            .setCustomerId(Long.toString(customerId))
            .setQuery(query)
            .build();

    // Creates and issues a search Google Ads stream request that will retrieve all campaigns.
    ServerStream<SearchGoogleAdsStreamResponse> stream =
        googleAdsServiceClient.searchStreamCallable().call(request);

    // Iterates through and prints all of the results in the stream response.
    for (SearchGoogleAdsStreamResponse response : stream) {
      for (GoogleAdsRow googleAdsRow : response.getResultsList()) {
        System.out.printf(
            "Campaign with ID %d and name '%s' was found.%n",
            googleAdsRow.getCampaign().getId(), googleAdsRow.getCampaign().getName());
      }
    }
  }
}
      

C#

public void Run(GoogleAdsClient client, long customerId)
{
    // Get the GoogleAdsService.
    GoogleAdsServiceClient googleAdsService = client.GetService(
        Services.V25.GoogleAdsService);

    // Create a query that will retrieve all campaigns.
    string query = @"SELECT
                    campaign.id,
                    campaign.name,
                    campaign.network_settings.target_content_network
                FROM campaign
                ORDER BY campaign.id";

    try
    {
        // Issue a search request.
        googleAdsService.SearchStream(customerId.ToString(), query,
            delegate (SearchGoogleAdsStreamResponse resp)
            {
                foreach (GoogleAdsRow googleAdsRow in resp.Results)
                {
                    Console.WriteLine("Campaign with ID {0} and name '{1}' was found.",
                        googleAdsRow.Campaign.Id, googleAdsRow.Campaign.Name);
                }
            }
        );
    }
    catch (GoogleAdsException e)
    {
        Console.WriteLine("Failure:");
        Console.WriteLine($"Message: {e.Message}");
        Console.WriteLine($"Failure: {e.Failure}");
        Console.WriteLine($"Request ID: {e.RequestId}");
        throw;
    }
}
      

PHP

public static function runExample(GoogleAdsClient $googleAdsClient, int $customerId)
{
    $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();
    // Creates a query that retrieves all campaigns.
    $query = 'SELECT campaign.id, campaign.name FROM campaign ORDER BY campaign.id';
    // Issues a search stream request.
    /** @var GoogleAdsServerStreamDecorator $stream */
    $stream = $googleAdsServiceClient->searchStream(
        SearchGoogleAdsStreamRequest::build($customerId, $query)
    );

    // Iterates over all rows in all messages and prints the requested field values for
    // the campaign in each row.
    foreach ($stream->iterateAllElements() as $googleAdsRow) {
        /** @var GoogleAdsRow $googleAdsRow */
        printf(
            "Campaign with ID %d and name '%s' was found.%s",
            $googleAdsRow->getCampaign()->getId(),
            $googleAdsRow->getCampaign()->getName(),
            PHP_EOL
        );
    }
}
      

Python

def main(client: GoogleAdsClient, customer_id: str) -> None:
    ga_service: GoogleAdsServiceClient = client.get_service("GoogleAdsService")

    query: str = """
        SELECT
          campaign.id,
          campaign.name
        FROM campaign
        ORDER BY campaign.id"""

    # Issues a search request using streaming.
    stream: Iterator[SearchGoogleAdsStreamResponse] = ga_service.search_stream(
        customer_id=customer_id, query=query
    )

    for batch in stream:
        rows: List[GoogleAdsRow] = batch.results
        for row in rows:
            print(
                f"Campaign with ID {row.campaign.id} and name "
                f'"{row.campaign.name}" was found.'
            )
      

Ruby

def get_campaigns(customer_id)
  # GoogleAdsClient will read a config file from
  # ENV['HOME']/google_ads_config.rb when called without parameters
  client = Google::Ads::GoogleAds::GoogleAdsClient.new

  responses = client.service.google_ads.search_stream(
    customer_id: customer_id,
    query: 'SELECT campaign.id, campaign.name FROM campaign ORDER BY campaign.id',
  )

  responses.each do |response|
    response.results.each do |row|
      puts "Campaign with ID #{row.campaign.id} and name '#{row.campaign.name}' was found."
    end
  end
end
      

Perl

sub get_campaigns {
  my ($api_client, $customer_id) = @_;

  # Create a search Google Ads stream request that will retrieve all campaigns.
  my $search_stream_request =
    Google::Ads::GoogleAds::V25::Services::GoogleAdsService::SearchGoogleAdsStreamRequest
    ->new({
      customerId => $customer_id,
      query      =>
        "SELECT campaign.id, campaign.name FROM campaign ORDER BY campaign.id"
    });

  # Get the GoogleAdsService.
  my $google_ads_service = $api_client->GoogleAdsService();

  my $search_stream_handler =
    Google::Ads::GoogleAds::Utils::SearchStreamHandler->new({
      service => $google_ads_service,
      request => $search_stream_request
    });

  # Issue a search request and process the stream response to print the requested
  # field values for the campaign in each row.
  $search_stream_handler->process_contents(
    sub {
      my $google_ads_row = shift;
      printf "Campaign with ID %d and name '%s' was found.\n",
        $google_ads_row->{campaign}{id}, $google_ads_row->{campaign}{name};
    });

  return 1;
}
      

curl