right_support 2.8.8 → 2.8.9

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.8.8
1
+ 2.8.9
@@ -405,27 +405,30 @@ module RightSupport::DB
405
405
  rows
406
406
  end
407
407
 
408
- # This method is an attempt to circumvent the Cassandra gem limitation of returning only 100 columns for wide rows
409
- # This method returns only columns that are within the result set specified by a secondary index equality query
410
- # This method will iterate through chunks of rows of the resultset and it will yield to the caller all of the
411
- # columns in chunks of 1,000 until all of the columns have been retrieved
408
+ # This method is an attempt to circumvent the Cassandra gem limitation of returning only 100 columns for wide rows,
409
+ # and also to help reliably iterate through a column family when the node is busy and experiencing many timeouts.
410
+ #
411
+ # Internally, it uses Cassandra#get_indexed_slices to find rows that match your index constraint; when it finds
412
+ # a wide row (with more than 1,000 columns), it continues to iterate through the columns of that row
412
413
  #
413
414
  # == Parameters:
414
415
  # @param [String] index column name
415
416
  # @param [String] index column value
416
417
  #
417
418
  # == Yields:
418
- # @yield [Array<String, Array<CassandraThrift::ColumnOrSuperColumn>>] irray containing ndex column value passed in and an array of columns matching the index query
419
+ # @yield [Array<String, Array<CassandraThrift::ColumnOrSuperColumn>>] array containing index column value passed in and an array of columns matching the index query
419
420
  def stream_all_indexed_slices(index, key)
420
421
  expr = do_op(:create_idx_expr, index, key, "EQ")
421
-
422
422
  start_row = ''
423
- max_row_count = 100
424
- max_initial_column_count = 1000 # number of columns to retrieve in the initial 2ndary index search
425
- max_additional_column_count = 10000 # Number of columns to retrieve in a batch once we're targetting a single (long) row
426
423
 
427
424
  # Loop over all CF rows, with batches of X
428
425
  while (start_row != nil)
426
+ # Reset these to their initieral values on every iteration thru the loop, in case
427
+ # we backed off due to timeouts (see rescue clauses below)
428
+ max_row_count = 100 # how many rows to grab at once
429
+ max_initial_column_count = 1000 # how much to grab at first for each row
430
+ max_additional_column_count = 1000 # how much to grab in each chunk of a long row
431
+
429
432
  clause = do_op(:create_idx_clause, [expr], start_row, max_row_count)
430
433
 
431
434
  # Now, for each batch of rows, make sure don't ask for "ALL" columns of each row, to avoid hitting rows with a huge amount of columns,
@@ -433,16 +436,14 @@ module RightSupport::DB
433
436
  begin
434
437
  rows = self.conn.get_indexed_slices(column_family, clause, :count => max_initial_column_count)
435
438
  rescue Exception => e
436
- wrapped_timeout = e.is_a?(CassandraThrift::TimedOutException)
437
- unwrapped_timeout = e.is_a?(Thrift::TransportException) && (e.type == Thrift::TransportException::TIMED_OUT)
438
- unwrapped_disconnect = e.is_a?(Thrift::TransportException) && (e.type == Thrift::TransportException::NOT_OPEN)
439
-
440
- if (wrapped_timeout || unwrapped_timeout || unwrapped_disconnect) && (timeout_retries < retry_timeout)
441
- timeout_retries += 1
439
+ if retryable_read_timeout?(e)
440
+ logger.error "CassandraModel#stream_all_indexed_slices retrying get_indexed_slices with fewer rows/cols due to a %s: %s @ %s (cf='%s' start_row='%s' count=%d)" %
441
+ [e.class.name, e.message, e.backtrace.first, column_family, start_row, max_row_count]
442
+ max_row_count /= 10 if max_row_count > 1
443
+ max_initial_column_count /= 10 if max_initial_column_count > 1
442
444
  retry
443
445
  else
444
- timeout_retries = 0
445
- raise e
446
+ raise
446
447
  end
447
448
  end
448
449
 
@@ -467,16 +468,13 @@ module RightSupport::DB
467
468
  :start => last_column_name,
468
469
  :slices_not_hash => true ).first.columns[1..-1]
469
470
  rescue Exception => e
470
- wrapped_timeout = e.is_a?(CassandraThrift::TimedOutException)
471
- unwrapped_timeout = e.is_a?(Thrift::TransportException) && (e.type == Thrift::TransportException::TIMED_OUT)
472
- unwrapped_disconnect = e.is_a?(Thrift::TransportException) && (e.type == Thrift::TransportException::NOT_OPEN)
473
-
474
- if (wrapped_timeout || unwrapped_timeout || unwrapped_disconnect) && (timeout_retries < retry_timeout)
475
- timeout_retries += 1
471
+ if retryable_read_timeout?(e)
472
+ logger.error "CassandraModel#stream_all_indexed_slices retrying get_range with fewer rows/cols due to a %s: %s @ %s (cf='%s' row='%s' start='%s' count=%d)" %
473
+ [e.class.name, e.message, e.backtrace.first, column_family, row_key, last_column_name, max_additional_column_count]
474
+ max_additional_column_count /= 10 if max_additional_column_count > 1
476
475
  retry
477
476
  else
478
- timeout_retries = 0
479
- raise e
477
+ raise
480
478
  end
481
479
  end
482
480
 
@@ -693,6 +691,29 @@ module RightSupport::DB
693
691
  end
694
692
  end
695
693
 
694
+ # Determine whether an exception can safely be retried, if the operation being performed
695
+ # was a read (i.e. is idempotent).
696
+ # @param [Exception] e the exception in question
697
+ # @return [Boolean] true if yes, false otherwise
698
+ def retryable_read_timeout?(e)
699
+ # Thrift RPC timeout that has been "improved" by cassandra gem's moronic exception-wrapping
700
+ wrapped_timeout = e.is_a?(CassandraThrift::TimedOutException)
701
+
702
+ # I have no idea what these are, but they show up when we have a local socket-read timeout after 10s
703
+ # Probably due to this line from the thrift gem (in lib/thrift/transport/socket.rb)
704
+ # raise TransportException.new(TransportException::TIMED_OUT, "Socket: Timed out reading #{sz} bytes from #{@desc}")
705
+ # The TransportException gets wrapped into a CassandraThrift::Cassandra::Client::TransportException and in the process
706
+ # the type is lost and the message is promoted to "type"
707
+ bogus_timeout = e.is_a?(Thrift::TransportException) && e.type.is_a?(String) && e.type =~ /Timed out/i
708
+
709
+ # Thrift RPC timeout
710
+ unwrapped_timeout = e.is_a?(Thrift::TransportException) && (e.type == Thrift::TransportException::TIMED_OUT)
711
+
712
+ # Thrift socket disconnect
713
+ unwrapped_disconnect = e.is_a?(Thrift::TransportException) && (e.type == Thrift::TransportException::NOT_OPEN)
714
+
715
+ wrapped_timeout || bogus_timeout || unwrapped_timeout || unwrapped_disconnect
716
+ end
696
717
  end # self
697
718
 
698
719
  attr_accessor :key, :attributes
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "right_support"
8
- s.version = "2.8.8"
8
+ s.version = "2.8.9"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Tony Spataro", "Sergey Sergyenko", "Ryan Williamson", "Lee Kirchhoff", "Alexey Karpik", "Scott Messier"]
12
- s.date = "2014-01-09"
12
+ s.date = "2014-01-13"
13
13
  s.description = "A toolkit of useful, reusable foundation code created by RightScale."
14
14
  s.email = "support@rightscale.com"
15
15
  s.extra_rdoc_files = [
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: right_support
3
3
  version: !ruby/object:Gem::Version
4
- hash: 63
4
+ hash: 61
5
5
  prerelease:
6
6
  segments:
7
7
  - 2
8
8
  - 8
9
- - 8
10
- version: 2.8.8
9
+ - 9
10
+ version: 2.8.9
11
11
  platform: ruby
12
12
  authors:
13
13
  - Tony Spataro
@@ -20,7 +20,7 @@ autorequire:
20
20
  bindir: bin
21
21
  cert_chain: []
22
22
 
23
- date: 2014-01-09 00:00:00 Z
23
+ date: 2014-01-13 00:00:00 Z
24
24
  dependencies:
25
25
  - !ruby/object:Gem::Dependency
26
26
  version_requirements: &id001 !ruby/object:Gem::Requirement