Skip to content

Commit 8cc65d9

Browse files
authored
Merge pull request #55583 from byroot/opt-aj-serialize-index
Optimize Active Job argument serialization by ~5x
2 parents 1dda631 + f155f3b commit 8cc65d9

20 files changed

+211
-140
lines changed

activejob/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
* `ActiveJob::Serializers::ObjectSerializers#klass` method is now public.
2+
3+
Custom Active Job serializers must have a public `#klass` method too.
4+
The returned class will be index allowing for faster serialization.
5+
6+
*Jean Boussier*
7+
18
* Allow jobs to the interrupted and resumed with Continuations
29

310
A job can use Continuations by including the `ActiveJob::Continuable`

activejob/lib/active_job/arguments.rb

Lines changed: 39 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,44 @@ module Arguments
3131
# serialized without mutation are returned as-is. Arrays/Hashes are
3232
# serialized element by element. All other types are serialized using
3333
# GlobalID.
34-
def serialize(arguments)
35-
arguments.map { |argument| serialize_argument(argument) }
34+
def serialize(argument)
35+
case argument
36+
when nil, true, false, Integer, Float # Types that can hardly be subclassed
37+
argument
38+
when String
39+
if argument.class == String
40+
argument
41+
else
42+
begin
43+
Serializers.serialize(argument)
44+
rescue SerializationError
45+
argument
46+
end
47+
end
48+
when Symbol
49+
{ OBJECT_SERIALIZER_KEY => "ActiveJob::Serializers::SymbolSerializer", "value" => argument.name }
50+
when GlobalID::Identification
51+
convert_to_global_id_hash(argument)
52+
when Array
53+
argument.map { |arg| serialize(arg) }
54+
when ActiveSupport::HashWithIndifferentAccess
55+
serialize_indifferent_hash(argument)
56+
when Hash
57+
symbol_keys = argument.keys
58+
symbol_keys.select! { |k| k.is_a?(Symbol) }
59+
symbol_keys.map!(&:name)
60+
61+
aj_hash_key = if Hash.ruby2_keywords_hash?(argument)
62+
RUBY2_KEYWORDS_KEY
63+
else
64+
SYMBOL_KEYS_KEY
65+
end
66+
result = serialize_hash(argument)
67+
result[aj_hash_key] = symbol_keys
68+
result
69+
else
70+
Serializers.serialize(argument)
71+
end
3672
end
3773

3874
# Deserializes a set of arguments. Intrinsic types that can safely be
@@ -68,48 +104,6 @@ def deserialize(arguments)
68104
private_constant :RESERVED_KEYS, :GLOBALID_KEY,
69105
:SYMBOL_KEYS_KEY, :RUBY2_KEYWORDS_KEY, :WITH_INDIFFERENT_ACCESS_KEY
70106

71-
def serialize_argument(argument)
72-
case argument
73-
when nil, true, false, Integer, Float # Types that can hardly be subclassed
74-
argument
75-
when String
76-
if argument.class == String
77-
argument
78-
else
79-
begin
80-
Serializers.serialize(argument)
81-
rescue SerializationError
82-
argument
83-
end
84-
end
85-
when GlobalID::Identification
86-
convert_to_global_id_hash(argument)
87-
when Array
88-
argument.map { |arg| serialize_argument(arg) }
89-
when ActiveSupport::HashWithIndifferentAccess
90-
serialize_indifferent_hash(argument)
91-
when Hash
92-
symbol_keys = argument.keys
93-
symbol_keys.select! { |k| k.is_a?(Symbol) }
94-
symbol_keys.map!(&:name)
95-
96-
aj_hash_key = if Hash.ruby2_keywords_hash?(argument)
97-
RUBY2_KEYWORDS_KEY
98-
else
99-
SYMBOL_KEYS_KEY
100-
end
101-
result = serialize_hash(argument)
102-
result[aj_hash_key] = symbol_keys
103-
result
104-
else
105-
if argument.respond_to?(:permitted?) && argument.respond_to?(:to_h)
106-
serialize_indifferent_hash(argument.to_h)
107-
else
108-
Serializers.serialize(argument)
109-
end
110-
end
111-
end
112-
113107
def deserialize_argument(argument)
114108
case argument
115109
when nil, true, false, String, Integer, Float
@@ -143,7 +137,7 @@ def custom_serialized?(hash)
143137

144138
def serialize_hash(argument)
145139
argument.each_with_object({}) do |(key, value), hash|
146-
hash[serialize_hash_key(key)] = serialize_argument(value)
140+
hash[serialize_hash_key(key)] = serialize(value)
147141
end
148142
end
149143

activejob/lib/active_job/railtie.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ class Railtie < Rails::Railtie # :nodoc:
5252
end
5353
end
5454

55+
initializer "active_job.action_controller_parameters" do |app|
56+
ActiveSupport.on_load(:active_job) do
57+
ActiveSupport.on_load(:action_controller) do
58+
ActiveJob::Serializers.add_serializers ActiveJob::Serializers::ActionControllerParametersSerializer
59+
end
60+
end
61+
end
62+
5563
initializer "active_job.set_configs" do |app|
5664
options = app.config.active_job
5765
options.queue_adapter ||= (Rails.env.test? ? :test : :async)

activejob/lib/active_job/serializers.rb

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,17 @@ module Serializers # :nodoc:
1919
autoload :ModuleSerializer
2020
autoload :RangeSerializer
2121
autoload :BigDecimalSerializer
22+
autoload :ActionControllerParametersSerializer
2223

23-
mattr_accessor :_additional_serializers
24-
self._additional_serializers = Set.new
24+
@serializers = Set.new
25+
@serializers_index = {}
2526

2627
class << self
2728
# Returns serialized representative of the passed object.
2829
# Will look up through all known serializers.
2930
# Raises ActiveJob::SerializationError if it can't find a proper serializer.
3031
def serialize(argument)
31-
serializer = serializers.detect { |s| s.serialize?(argument) }
32+
serializer = @serializers_index[argument.class] || serializers.find { |s| s.serialize?(argument) }
3233
raise SerializationError.new("Unsupported argument type: #{argument.class.name}") unless serializer
3334
serializer.serialize(argument)
3435
end
@@ -47,24 +48,53 @@ def deserialize(argument)
4748
end
4849

4950
# Returns list of known serializers.
50-
def serializers
51-
self._additional_serializers
51+
attr_reader :serializers
52+
53+
def serializers=(serializers)
54+
@serializers = serializers
55+
index_serializers
5256
end
5357

5458
# Adds new serializers to a list of known serializers.
5559
def add_serializers(*new_serializers)
56-
self._additional_serializers += new_serializers.flatten
60+
new_serializers = new_serializers.flatten
61+
new_serializers.map! do |s|
62+
if s.is_a?(Class) && s < ObjectSerializer
63+
s.instance
64+
else
65+
s
66+
end
67+
end
68+
69+
@serializers += new_serializers
70+
index_serializers
71+
@serializers
5772
end
73+
74+
private
75+
def index_serializers
76+
@serializers_index.clear
77+
serializers.each do |s|
78+
if s.respond_to?(:klass)
79+
@serializers_index[s.klass] = s
80+
elsif s.respond_to?(:klass, true)
81+
ActiveJob.deprecator.warn(<<~MSG.squish)
82+
#{s.klass.name}#klass method should be public.
83+
MSG
84+
@serializers_index[s.send(:klass)] = s
85+
end
86+
end
87+
end
5888
end
5989

60-
add_serializers SymbolSerializer,
61-
DurationSerializer,
62-
DateTimeSerializer,
63-
DateSerializer,
64-
TimeWithZoneSerializer,
65-
TimeSerializer,
66-
ModuleSerializer,
67-
RangeSerializer,
68-
BigDecimalSerializer
90+
add_serializers SymbolSerializer.instance,
91+
DurationSerializer.instance,
92+
DateTimeSerializer.instance,
93+
DateSerializer.instance,
94+
TimeWithZoneSerializer.instance,
95+
TimeSerializer.instance,
96+
ModuleSerializer.instance,
97+
RangeSerializer.instance,
98+
BigDecimalSerializer.instance
6999
end
70100
end
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# frozen_string_literal: true
2+
3+
module ActiveJob
4+
module Serializers
5+
class ActionControllerParametersSerializer < ObjectSerializer
6+
def serialize(argument)
7+
Arguments.serialize(argument.to_h.with_indifferent_access)
8+
end
9+
10+
def deserialize(hash)
11+
raise NotImplementedError # Serialized as a HashWithIndifferentAccess
12+
end
13+
14+
def serialize?(argument)
15+
argument.respond_to?(:permitted?) && argument.respond_to?(:to_h)
16+
end
17+
18+
def klass
19+
if defined?(ActionController::Parameters)
20+
ActionController::Parameters
21+
end
22+
end
23+
end
24+
end
25+
end

activejob/lib/active_job/serializers/big_decimal_serializer.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,9 @@ def deserialize(hash)
1313
BigDecimal(hash["value"])
1414
end
1515

16-
private
17-
def klass
18-
BigDecimal
19-
end
16+
def klass
17+
BigDecimal
18+
end
2019
end
2120
end
2221
end

activejob/lib/active_job/serializers/date_serializer.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@ def deserialize(hash)
1111
Date.iso8601(hash["value"])
1212
end
1313

14-
private
15-
def klass
16-
Date
17-
end
14+
def klass
15+
Date
16+
end
1817
end
1918
end
2019
end

activejob/lib/active_job/serializers/date_time_serializer.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@ def deserialize(hash)
77
DateTime.iso8601(hash["value"])
88
end
99

10-
private
11-
def klass
12-
DateTime
13-
end
10+
def klass
11+
DateTime
12+
end
1413
end
1514
end
1615
end

activejob/lib/active_job/serializers/duration_serializer.rb

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,19 @@ class DurationSerializer < ObjectSerializer # :nodoc:
66
def serialize(duration)
77
# Ideally duration.parts would be wrapped in an array before passing to Arguments.serialize,
88
# but we continue passing the bare hash for backwards compatibility:
9-
super("value" => duration.value, "parts" => Arguments.serialize(duration.parts))
9+
super("value" => duration.value, "parts" => Arguments.serialize(duration.parts.to_a))
1010
end
1111

1212
def deserialize(hash)
1313
value = hash["value"]
14-
parts = Arguments.deserialize(hash["parts"])
14+
parts = Arguments.deserialize(hash["parts"].to_h)
1515
# `parts` is originally a hash, but will have been flattened to an array by Arguments.serialize
1616
klass.new(value, parts.to_h)
1717
end
1818

19-
private
20-
def klass
21-
ActiveSupport::Duration
22-
end
19+
def klass
20+
ActiveSupport::Duration
21+
end
2322
end
2423
end
2524
end

activejob/lib/active_job/serializers/module_serializer.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@ def deserialize(hash)
1212
hash["value"].constantize
1313
end
1414

15-
private
16-
def klass
17-
Module
18-
end
15+
def klass
16+
Module
17+
end
1918
end
2019
end
2120
end

0 commit comments

Comments
 (0)