Best practices and limitations

  • Optimize throughput by using fewer larger jobs and ordering operations by type and parent resource.

  • Avoid concurrency issues by minimizing operations on the same objects across concurrent jobs and within a single job.

  • Retrieve results efficiently by avoiding frequent polling and limiting results per page to 1,000.

  • Batch jobs support up to one million operations, with a limit of 100 active or pending jobs per account, and requests for adding operations have size and operation count limits.

Consider these guidelines when you use BatchJobService.

Improve throughput

  • Fewer larger jobs are preferred over many smaller jobs.

  • Order uploaded operations by operation type (except for interdependent operations that must be grouped consecutively in atomic sub-batches). For example, if your job contains operations to add standard campaigns, ad groups, and ad group criteria, order the operations in your upload so that all of the campaign operations are first, followed by all of the ad group operations, and finally all ad group criterion operations.

  • Within operations of the same type, it can improve performance to group them by parent resource. For example, if you have a series of AdGroupCriterionOperation objects, it is more efficient to group operations by ad group rather than intermixing operations that affect ad group criteria in different ad groups.

Atomicity in batch splitting

The Google Ads API splits the operations in a submitted batch job into smaller sub-batches for processing. While standard sub-batches execute with partial failure enabled, sub-batches for certain interdependent operations are processed atomically as a single transaction:

For both AssetGroup and Performance Max Campaign creation sub-batches (up to 1,000 operations total per sub-batch; any child create operations beyond 999 spill into the next non-atomic sub-batch):

When an atomic sub-batch fails, the offending operation's BatchJobResult.status contains the underlying validation error, while the remaining operations in that sub-batch are rolled back with the corresponding transaction error for that sub-batch. Inspect the adjacent BatchJobResult entries sharing the same AdGroup, AssetGroup, or Campaign ID to identify the root-cause error.

If related operations in any of these groups are not added consecutively, the Google Ads API splits them across separate sub-batches, causing the modification to fail minimum asset requirements or leaving listing group trees incomplete. See Use listing group filters in batch jobs and Performance Max batch processing for details.

Logical grouping

When you modify a product targeting hierarchy (AssetGroupListingGroupFilterOperation in Performance Max campaigns or AdGroupCriterionOperation in Shopping campaigns) or create a new AssetGroup or Performance Max Campaign, group all operations targeting the same parent resource (AssetGroup, AdGroup, or Campaign) consecutively. This reduces backend lock contention and keeps interdependent trees together.

Data consistency

Because listing group filter trees and Performance Max asset requirements are validated at the end of each atomic sub-batch transaction, avoid splitting updates to the same parent resource across discontinuous ranges in a job or across concurrent jobs.

Avoid concurrency issues

  • When you submit multiple concurrent jobs for the same account, reduce the likelihood of jobs operating on the same objects at the same time while maintaining large job sizes. Many unfinished jobs with the status of RUNNING that try to mutate the same set of objects can lead to deadlock-like conditions, resulting in severe slow-down and even job failures.

  • Don't submit multiple operations that mutate the same object in the same job, as the result can be unpredictable.

Retrieve results optimally

  • Don't poll the job status too frequently or you risk hitting rate limit errors.

  • Leave page_size unset (or set it to the maximum of 1000) when you call ListBatchJobResults to minimize pagination round trips, and only set response_content_type to MUTABLE_RESOURCE if your application inspects the returned resource fields beyond resource_name.

  • The results order is the same as the upload order.

Additional usage guidance

  • You can set an approximate upper bound for how long a batch job is allowed to run before being canceled. When you create a new batch job, set the metadata.execution_limit_seconds field to a positive integer for your preferred time limit, in seconds. There is no default time limit if metadata.execution_limit_seconds is not set.

  • Although the protocol limit is 10,000 operations per request, we recommend adding no more than 1,000 operations per AddBatchJobOperationsRequest and using the sequence_token to upload the rest of the operations to the same job. Depending on the size of the operations, sending too many operations in a single AddBatchJobOperationsRequest can cause a BatchJobError.REQUEST_TOO_LARGE error. You can handle this error by reducing the number of operations and retrying the AddBatchJobOperationsRequest.

Limitations

  • Each BatchJob supports up to one million operations. Exceeding this limit when you call AddBatchJobOperations returns a ResourceCountLimitExceededError.RESOURCE_LIMIT error (with ResourceLimitType.BATCH_JOB_OPERATIONS_PER_JOB in ErrorDetails.resource_count_details).

  • Each account can have up to 100 active or pending jobs at the same time. Exceeding this limit when you create a batch job with MutateBatchJob returns a ResourceCountLimitExceededError.RESOURCE_LIMIT error (with ResourceLimitType.BATCH_JOBS_PER_CUSTOMER in ErrorDetails.resource_count_details).

  • Pending jobs older than 7 days are automatically removed.

  • Each AddBatchJobOperationsRequest has a hard limit of 10,000 mutate operations per request. Exceeding 10,000 operations in a single request returns a BatchJobError.REQUEST_TOO_LARGE error.

  • For the page_size field in ListBatchJobResultsRequest:

  • Each AddBatchJobOperationsRequest has a maximum size of 41,937,920 bytes. If you exceed this limit, you receive a BatchJobError.REQUEST_TOO_LARGE error (or an INTERNAL_ERROR if rejected at the transport layer). You can determine the serialized size of the request before submitting and take appropriate action if it is too large:

    Java

    
    static final int MAX_REQUEST_BYTES = 41_937_920;
    
    // ... (code to get the AddBatchJobOperationsRequest object)
    
    int sizeInBytes = request.getSerializedSize();
    

    C#

    
    const int MAX_REQUEST_BYTES = 41_937_920;
    
    // ... (code to get the AddBatchJobOperationsRequest object)
    
    int sizeInBytes = request.CalculateSize();
    

    PHP

    
    const MAX_REQUEST_BYTES = 41937920;
    
    // ... (code to get the AddBatchJobOperationsRequest object)
    
    $size_in_bytes = $request->byteSize();
    

    Python

    
    MAX_REQUEST_BYTES = 41_937_920
    
    # ... (code to get the AddBatchJobOperationsRequest object)
    
    # Supports both proto-plus (use_proto_plus=True) and native protobuf
    # (use_proto_plus=False) messages.
    pb = type(request).pb(request) if hasattr(type(request), "pb") else request
    size_in_bytes = pb.ByteSize()
    

    Ruby

    
    MAX_REQUEST_BYTES = 41_937_920
    
    # ... (code to get the AddBatchJobOperationsRequest object)
    
    size_in_bytes = request.to_proto.bytesize
    

    Perl

    
    use JSON::XS;
    use constant MAX_REQUEST_BYTES => 41937920;
    
    # ... (code to get the AddBatchJobOperationsRequest object)
    
    # The Perl client library uses REST/JSON; UTF-8 JSON byte length provides a
    # conservative upper-bound estimate of the serialized request size.
    my $json_encoder = JSON::XS->new->utf8->convert_blessed;
    my $size_in_bytes = length($json_encoder->encode($request));
    

Single mutate operation size

While the overall request can be up to 41,937,920 bytes, the serialized size of a single MutateOperation within the batch is capped at 10,484,504 bytes (10 MiB minus 1,256 bytes). Exceeding this limit returns a BatchJobError.REQUEST_TOO_LARGE error. Note that while the reference documentation for BatchJobError.REQUEST_TOO_LARGE cites the 10,484,504-byte threshold, AddBatchJobOperations returns this same error code when any of the three request thresholds (41,937,920 total request bytes, 10,484,504 single-operation bytes, or 10,000 operations per call) is exceeded, with the error's message field specifying which limit was violated.