Giter VIP home page Giter VIP logo

Comments (6)

dklimkin avatar dklimkin commented on May 14, 2024

Hi Hendrik,

As long as you don't change the configuration between threads, we expect the library to be thead-safe.

For multiple accounts / auth credentials, we recommend separate instances of AdwordsApi objects, local to thread. Is this the way you are using the library?

-Danial.

from google-api-ads-ruby.

hendricius avatar hendricius commented on May 14, 2024

Hi Danial,

the way we use it is as follows:

1 large Merchant Center Account.
~1500 Accounts in the Merchant Center
~15 Other accounts that gave access via the web UI to the merchant center account. Is the Linked Account the correct term?

We are basically a layer on top of AdWords. That simplifies a few things for the user compared to the AdWords UI.

We have around 5 workers that keep interacting with the AdWords API all the time. They are running in one process, 30 threads in total. A couple of those workers run simultaneously at the same time. So we have lots of AdwordsCampaign and for each of them we run the worker simultaneously. But each of them interacts with either one of the 1500 merchant center accounts or one of the 15 linked accounts. The crashes occur randomly, not all the time.

The worker that crashed is the following:

# purge_adgroups_without_keywords.rb
class PurgeAdgroupsWithoutKeywords
  include Sidekiq::Worker

  sidekiq_options retry: 2, backtrace: true

  def perform(adwords_campaign_id, test_run = false)
    adw = AdwordsCampaign.find adwords_campaign_id
    extraction = adw.extraction
    user = adw.user
    company = adw.company

    valid_for_sync = adw.adwords_registered? && extraction.present?

    return false if !valid_for_sync

    Rails.logger.info "Performing adgroup purge for campaign #{adw.id}"

    # Code below
    adgroups                  = adw.adwords.get_adgroups.map{|adgr| adgr[:id]}
    # Code below, everything fails here.
    adgroups_with_keywords    = adw.adwords.get_keywords(include_pending: true, ignore_index_limit: true).keys
    adgroups_without_keywords = adgroups - adgroups_with_keywords

    Rails.logger.info "Adgroups with keywords for campaign #{adw.id}: #{adgroups_with_keywords.join(' ')}"
    Rails.logger.info "Adgroups without keywords for campaign #{adw.id}: #{adgroups_without_keywords.join(' ')}"

    if !test_run
      # Code below
      adw.adwords.delete_adgroups_by_ids(adgroups_without_keywords)
      extraction.pages.where(adgroup_id: adgroups_without_keywords).update_all(adgroup_id: nil)

      Rails.logger.info "Removed #{adgroups_without_keywords.count} adgroups from AdWords"
    end
    adgroups_without_keywords
  end
end

This is the class we are using to do all the requests with the adwords API. I added the methods that are run below.

  # adwords_helper.rb
  # Get all adgroups from a campaign
  def get_adgroups
    @api = get_adwords_api

    service = @api.service(:AdGroupService, API_VERSION)

    selector = {
      :fields => ['Id', 'Name'],
      :predicates => [
        {:field => 'CampaignId', :operator => 'IN', :values => [self.campaign_id]},
        {:field => 'Status', :operator => 'IN', :values => ['ENABLED']},
      ],
      :paging => {
        :start_index => 0,
        :number_results => 500
      }
    }

    offset, page = 0, {}
    result = []

    begin
      page = service.get(selector)

      if page[:entries]
        page[:entries].each do |adgroup|
          result.push({:id => adgroup[:id], :name => adgroup[:name]})
        end
        offset += 500
        selector[:paging][:start_index] = offset
      end

      sleep(@@RETRY_INTERVAL)
    end while page[:total_num_entries] > offset

    result
  end

  # Get all keywords of adgroups
  def get_keywords(limit_to_adgroups: nil, include_pending: false, ignore_index_limit: false)
    @api = get_adwords_api

    service = @api.service(:AdGroupCriterionService, API_VERSION)
    selector = {
      :fields => ['Id', 'AdGroupId', 'KeywordText', 'KeywordMatchType', 'CpcBid'],
      :predicates => [
        {:field => 'CampaignId', :operator => 'IN', :values => [self.campaign_id]},
        {:field => 'CriteriaType', :operator => 'IN', :values => ['KEYWORD']},
        {:field => 'Status', :operator => 'IN', :values => ['ENABLED']},
        {:field => 'ApprovalStatus', :operator => 'IN', :values => (include_pending ? ['APPROVED', 'PENDING_REVIEW', 'UNDER_REVIEW'] : ['APPROVED'])}
      ],
      :paging => {
        :start_index => 0,
        :number_results => 500
      }
    }

    if limit_to_adgroups
      selector[:predicates].push({:field => 'AdGroupId', :operator => 'IN', :values => limit_to_adgroups})
    elsif self.adgroup_id
      selector[:predicates].push({:field => 'AdGroupId', :operator => 'IN', :values => [self.adgroup_id]})
    end

    offset, page = 0, {}
    result = {}

    begin
      # Everything fails here.
      page = service.get(selector)

      if page[:entries]
        page[:entries].each do |keyword|
          result[keyword[:ad_group_id].to_i] ||= []
          result[keyword[:ad_group_id].to_i].push({:id => keyword[:criterion][:id], :text => keyword[:criterion][:text], :match_type => keyword[:criterion][:match_type], :bid => keyword[:bidding_strategy_configuration][:bids].detect{|bid| bid[:bids_type] == 'CpcBid'}[:bid]})
        end
        offset += 500
        selector[:paging][:start_index] = offset

        # Maximum start index in this call
        if offset > 100000 && !ignore_index_limit
          Rails.logger.info "Can't fetch more than 100k keywords for campaign #{self.campaign_id}"
          break
        end
      end

      sleep(@@RETRY_INTERVAL)
    end while page[:total_num_entries] > offset

    result
  end

  # Deletes specific adgroups
  def delete_adgroups_by_ids(ids)
    unless perform_dangerous_operations?
      return false
    end
    @api = get_adwords_api

    service = @api.service(:MutateJobService, API_VERSION)

    operations = ids.map{|id| {
      :xsi_type => 'AdGroupOperation',
      :operator => 'SET',
      :operand => {
        :id => id,
        :status => 'REMOVED'
      }
    }}

    operations.each_slice(1000) do |ops|
      response = service.mutate(ops, {:prerequisite_job_ids => []})
      job_id = response[:id]
      status = poll_job(service, job_id)

      if status != 'COMPLETED'
        Rails.logger.fatal "Could not complete deletion of adgroups"
      end

      sleep(@@RETRY_INTERVAL)
    end
  end

Just referring to the ticket I posted earlier (sidekiq/sidekiq#2062 sidekiq/sidekiq#2062)

This is the exact stack trace that I received when the segmentation fault occurred.

c:0048 p:---- s:0249 e:000248 CFUNC  :initialize
c:0047 p:---- s:0247 e:000246 CFUNC  :new
c:0046 p:0030 s:0244 e:000243 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/2.1.0/set.rb:81 [FINISH]
c:0045 p:---- s:0239 e:000238 CFUNC  :new
c:0044 p:2070 s:0236 e:000234 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/2.1.0/rexml/parsers/baseparser.rb:379
c:0043 p:0007 s:0204 e:000203 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/2.1.0/rexml/parsers/baseparser.rb:184
c:0042 p:0042 s:0201 e:000200 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/nori-1.1.5/lib/nori/parser/rexml.rb:16
c:0041 p:0027 s:0192 e:000191 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/nori-1.1.5/lib/nori/parser.rb:31
c:0040 p:0036 s:0186 e:000185 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/nori-1.1.5/lib/nori.rb:12
c:0039 p:0030 s:0181 e:000180 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/savon-1.2.0/lib/savon/soap/response.rb:85
c:0038 p:0007 s:0178 e:000177 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/savon-1.2.0/lib/savon/soap/response.rb:63
c:0037 p:0035 s:0175 e:000174 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/google-ads-common-0.9.5/lib/ads_common/results_extractor.rb:39
c:0036 p:0100 s:0167 e:000166 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/google-ads-common-0.9.5/lib/ads_common/savon_service.rb:85
c:0035 p:0013 s:0156 e:000155 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/google-adwords-api-0.13.2/lib/adwords_api/v201406/ad_group_criter
c:0034 p:0300 s:0151 e:000150 METHOD /home/deployer/apps/robot/lib/adwords_helper.rb:693
c:0033 p:0129 s:0139 e:000138 METHOD /home/deployer/apps/robot/app/workers/purge_adgroups_without_keywords.rb:19
c:0032 p:0010 s:0126 e:000125 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/bundler/gems/sidekiq-5047f9220b82/lib/sidekiq/processor.rb:75
c:0031 p:0020 s:0121 e:000120 BLOCK  /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/bundler/gems/sidekiq-5047f9220b82/lib/sidekiq/processor.rb:52 [FINISH]
c:0030 p:---- s:0119 e:000118 CFUNC  :call
c:0029 p:0016 s:0116 e:000115 LAMBDA /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/bundler/gems/sidekiq-5047f9220b82/lib/sidekiq/middleware/chain.rb:127
c:0028 p:0038 s:0114 e:000113 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/bundler/gems/sidetiq-f3668e03c72c/lib/sidetiq/middleware/history.rb:8
c:0027 p:0030 s:0107 e:000106 LAMBDA /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/bundler/gems/sidekiq-5047f9220b82/lib/sidekiq/middleware/chain.rb:129
c:0026 p:0014 s:0105 e:000104 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/bundler/gems/sidekiq-failures-1bddc5c12cea/lib/sidekiq/failures/middle
c:0025 p:0030 s:0097 e:000096 LAMBDA /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/bundler/gems/sidekiq-5047f9220b82/lib/sidekiq/middleware/chain.rb:129
c:0024 p:0024 s:0095 e:000094 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/sidekiq-pro-1.9.1/lib/sidekiq/batch/middleware.rb:26

Other things that may be helpful:

  • We just upgraded from Rails 3.2.17 to Rails 4.2.17
  • We upgraded the google-adwords-api to 0.13.2
  • We are using Ubuntu 14.04 LTS
  • The server has 128 GB of RAM

Thanks a bunch for your help!

On 25 Nov 2014, at 11:36, Danial Klimkin <[email protected] mailto:[email protected]> wrote:

Hi Hendrik,

As long as you don't change the configuration between threads, we expect the library to be thead-safe.

For multiple accounts / auth credentials, we recommend separate instances of AdwordsApi objects, local to thread. Is this the way you are using the library?

-Danial.


Reply to this email directly or view it on GitHub #38 (comment).

from google-api-ads-ruby.

dklimkin avatar dklimkin commented on May 14, 2024

It looks like you are sharing the same AdwordsApi instance:

@api = get_adwords_api

Try using a separate one per executing thread.

from google-api-ads-ruby.

hendricius avatar hendricius commented on May 14, 2024

Hey Danial,

thanks a bunch for the hint. We are already using only 1 instance per thread. So it is created on the fly all the time.

  @@config = 'adwords_api.yml'

  def initialize(args = {})
    self.adwords_id = AdwordsConfig.config[:master_account]

    return unless args.is_a?(Hash)
    args.each do |k,v|
      instance_variable_set("@#{k}", v) unless v.nil?
    end
  end

  def get_adwords_api
    @api ||= create_adwords_api
    return @api
  end

  # Creates an instance of AdWords API class. Uses a configuration file and
  # Rails config directory.
  def create_adwords_api
    config_filename = File.join(Rails.root, 'config', @@config)
    config = YAML::load_file(config_filename)

    if self.adwords_id
      config[:authentication][:client_customer_id] = self.adwords_id
    end

    config[:authentication][:oauth2_token].merge!(self.credentials) if self.credentials

    @api = AdwordsApi::Api.new(config)
    @api.logger = logger if logger

    return @api
  end

Do you see anything else that could be an issue? Thanks!

from google-api-ads-ruby.

ckoenig avatar ckoenig commented on May 14, 2024

As far as I can see the version of the gyoku gem used by savon 1.2.0 as used by the adwords gem is not thread safe, see savonrb/gyoku#29.

from google-api-ads-ruby.

mcloonan avatar mcloonan commented on May 14, 2024

Closing this out, as it's a thread safety issue in a dependency.

from google-api-ads-ruby.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.