activejob-compressible 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +34 -0
- data/LICENSE +21 -0
- data/README.md +158 -0
- data/lib/activejob/compressible/configuration.rb +46 -0
- data/lib/activejob/compressible/version.rb +7 -0
- data/lib/activejob/compressible.rb +78 -0
- data/lib/activejob-compressible.rb +5 -0
- data/sig/activejob/compressible/configuration.rbs +16 -0
- data/sig/activejob/compressible/version.rbs +5 -0
- data/sig/activejob/compressible.rbs +23 -0
- metadata +94 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 28d3f54a6ca82b0bfbc6d00db5ff31159f7ce19fd56e157e2805d913cec79165
|
4
|
+
data.tar.gz: dd256e7a07ff11bdb73180d05a4f85c1bfb94aab754b7c8d64ab22517aa95bb0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d716916f8c7d9d4d8ee07a0ac08caedc9445e9cd6f8a0cd8946f1cc8f635b47fe87b0faa9a9f8c40953061d0bc035a9e0dcb1f0d53b7e444ebc17644c98faf26
|
7
|
+
data.tar.gz: ed162b58f90dbc68136bd2b5e6132ad4d64afa9d2291904733dd86baa1b87d94c4536334dbaa9822f1d0dffe1a093875d8dc1cb28aa200c476d54a61416257fe
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [1.0.0] - 2025-06-05
|
9
|
+
|
10
|
+
### Added
|
11
|
+
- Initial release of ActiveJob::Compressible gem
|
12
|
+
- Transparent compression/decompression for large ActiveJob payloads
|
13
|
+
- Automatic detection of Rails cache backend limits for smart defaults
|
14
|
+
- Configurable compression threshold via `ActiveJob::Compressible.configure`
|
15
|
+
- Support for zlib compression algorithm with base64 encoding
|
16
|
+
- Backward compatibility for existing uncompressed jobs
|
17
|
+
- Safe compression markers (`"_compressed"` key) for reliable detection
|
18
|
+
- Comprehensive test suite with dynamic configuration testing
|
19
|
+
- Support for Ruby >= 3.1 and Rails 6.0-8.x
|
20
|
+
- Detailed documentation and usage examples
|
21
|
+
|
22
|
+
### Technical Details
|
23
|
+
- Uses zlib compression with base64 encoding for safety
|
24
|
+
- Only compresses Hash arguments that exceed the configured threshold
|
25
|
+
- Defaults to cache `value_max_bytes` minus 100KB for safety margin
|
26
|
+
- Graceful fallback to 948KB default if cache limits not detected
|
27
|
+
- Thread-safe configuration system
|
28
|
+
- Minimal performance overhead (~1-5ms for typical payloads)
|
29
|
+
|
30
|
+
### Documentation
|
31
|
+
- Complete README with usage examples and configuration options
|
32
|
+
- API documentation with inline comments
|
33
|
+
- Development setup and testing instructions
|
34
|
+
- Performance characteristics and compatibility notes
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 Evan Lee
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
# ActiveJob::Compressible
|
2
|
+
|
3
|
+
Transparent compression for large ActiveJob payloads to avoid cache backend limits.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
The `ActiveJob::Compressible` concern provides automatic compression and decompression for large job arguments to prevent exceeding cache or queue backend size limits (such as Redis/Memcached). This is especially useful for jobs that handle large payloads like webhook events, API responses, or bulk data processing.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'activejob-compressible'
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle install
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install activejob-compressible
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
Include the concern in your job classes:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
class WebhookJob < ApplicationJob
|
31
|
+
include ActiveJob::Compressible
|
32
|
+
|
33
|
+
def perform(large_webhook_payload)
|
34
|
+
# Process the payload - compression/decompression is transparent
|
35
|
+
process_webhook(large_webhook_payload)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
## How It Works
|
41
|
+
|
42
|
+
- **Automatic Compression**: When jobs are serialized (enqueued), the first argument is checked. If it's a Hash and its JSON representation exceeds the compression threshold, it's automatically compressed using zlib and base64 encoding.
|
43
|
+
|
44
|
+
- **Transparent Decompression**: When jobs are deserialized (dequeued), compressed arguments are automatically detected and decompressed.
|
45
|
+
|
46
|
+
- **Backward Compatibility**: Old jobs (created before adding the concern) continue to work without any changes during rolling deployments.
|
47
|
+
|
48
|
+
- **Safe Markers**: Compressed payloads are marked with a `"_compressed"` key to ensure reliable detection.
|
49
|
+
|
50
|
+
## Configuration
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
ActiveJob::Compressible.configure do |config|
|
54
|
+
config.compression_threshold = 500_000 # Compress payloads > 500KB
|
55
|
+
end
|
56
|
+
```
|
57
|
+
|
58
|
+
### Configuration Options
|
59
|
+
|
60
|
+
- **`compression_threshold`**: Size in bytes above which payloads will be compressed. Defaults to your cache backend's `value_max_bytes` minus 100KB for safety, or 948KB if not detected.
|
61
|
+
|
62
|
+
- **`compression_algorithm`**: Currently only `:zlib` is supported.
|
63
|
+
|
64
|
+
### Default Behavior
|
65
|
+
|
66
|
+
The gem automatically detects your Rails cache configuration and sets a safe default threshold:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
# Automatically uses your cache backend's limits
|
70
|
+
max_bytes = Rails.cache.options[:value_max_bytes] || 1_048_576
|
71
|
+
default_threshold = max_bytes - 100_000 # Safe margin
|
72
|
+
```
|
73
|
+
|
74
|
+
## Examples
|
75
|
+
|
76
|
+
### Basic Usage
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
class DataProcessingJob < ApplicationJob
|
80
|
+
include ActiveJob::Compressible
|
81
|
+
|
82
|
+
def perform(large_dataset)
|
83
|
+
# large_dataset is automatically compressed if > threshold
|
84
|
+
process_data(large_dataset)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Enqueue with large payload
|
89
|
+
DataProcessingJob.perform_later({
|
90
|
+
"records" => large_array_of_data,
|
91
|
+
"metadata" => additional_info
|
92
|
+
})
|
93
|
+
```
|
94
|
+
|
95
|
+
### Custom Configuration
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
# config/initializers/activejob_compressible.rb
|
99
|
+
ActiveJob::Compressible.configure do |config|
|
100
|
+
config.compression_threshold = 750_000 # 750KB threshold
|
101
|
+
end
|
102
|
+
```
|
103
|
+
|
104
|
+
### Multiple Job Classes
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
class ApiResponseJob < ApplicationJob
|
108
|
+
include ActiveJob::Compressible
|
109
|
+
# Inherits default configuration
|
110
|
+
end
|
111
|
+
|
112
|
+
class BulkImportJob < ApplicationJob
|
113
|
+
include ActiveJob::Compressible
|
114
|
+
# Also inherits default configuration
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
118
|
+
## Requirements
|
119
|
+
|
120
|
+
- Ruby >= 2.7.0
|
121
|
+
- ActiveJob >= 6.0
|
122
|
+
- ActiveSupport >= 6.0
|
123
|
+
|
124
|
+
## Compatibility
|
125
|
+
|
126
|
+
- **Rails 6.0-8.x**: Full support
|
127
|
+
- **Queue Adapters**: Works with any ActiveJob queue adapter (Sidekiq, Resque, etc.)
|
128
|
+
- **Cache Backends**: Automatically detects Redis, Memcached, and other cache limits
|
129
|
+
- **Rolling Deployments**: Safe to deploy incrementally
|
130
|
+
|
131
|
+
## Performance
|
132
|
+
|
133
|
+
- **Compression Ratio**: Typically 60-90% size reduction for JSON payloads
|
134
|
+
- **Speed**: zlib compression/decompression adds ~1-5ms for typical payloads
|
135
|
+
- **Memory**: Minimal overhead, only processes large payloads
|
136
|
+
|
137
|
+
## Development
|
138
|
+
|
139
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests.
|
140
|
+
|
141
|
+
```bash
|
142
|
+
# Run tests
|
143
|
+
bundle exec rake test
|
144
|
+
|
145
|
+
# Run tests quietly
|
146
|
+
bundle exec rake test TESTOPTS="--quiet"
|
147
|
+
|
148
|
+
# Run RuboCop
|
149
|
+
bundle exec rubocop
|
150
|
+
```
|
151
|
+
|
152
|
+
## Contributing
|
153
|
+
|
154
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/Archetypically/activejob-compressible.
|
155
|
+
|
156
|
+
## License
|
157
|
+
|
158
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveJob
|
4
|
+
# Compressible module provides transparent compression for large ActiveJob payloads.
|
5
|
+
module Compressible
|
6
|
+
# Configuration class for ActiveJob::Compressible settings.
|
7
|
+
#
|
8
|
+
# Manages compression behavior including threshold limits and algorithm selection.
|
9
|
+
# The compression threshold determines when job arguments should be compressed
|
10
|
+
# based on their serialized size.
|
11
|
+
class Configuration
|
12
|
+
attr_accessor :compression_threshold
|
13
|
+
attr_reader :compression_algorithm
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@compression_threshold = default_compression_threshold
|
17
|
+
@compression_algorithm = :zlib
|
18
|
+
end
|
19
|
+
|
20
|
+
def compression_algorithm=(algorithm)
|
21
|
+
raise ArgumentError, "Only :zlib compression algorithm is currently supported" unless algorithm == :zlib
|
22
|
+
|
23
|
+
@compression_algorithm = algorithm
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def default_compression_threshold
|
29
|
+
max_bytes = begin
|
30
|
+
Rails.cache.options[:value_max_bytes] if defined?(Rails)
|
31
|
+
rescue StandardError
|
32
|
+
nil
|
33
|
+
end || 1_048_576
|
34
|
+
max_bytes - 100_000
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.configuration
|
39
|
+
@configuration ||= Configuration.new
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.configure
|
43
|
+
yield(configuration)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
require "zlib"
|
5
|
+
require "base64"
|
6
|
+
require "json"
|
7
|
+
|
8
|
+
module ActiveJob
|
9
|
+
# CompressibleJob is a concern for ActiveJob classes that transparently compresses
|
10
|
+
# and decompresses large job arguments to avoid exceeding cache or queue backend limits
|
11
|
+
# (such as Dalli/Memcached or Redis). This is especially useful for jobs that handle
|
12
|
+
# large payloads, such as webhook events or API responses.
|
13
|
+
#
|
14
|
+
# Usage:
|
15
|
+
# class MyJob < ApplicationJob
|
16
|
+
# include ActiveJob::Compressible
|
17
|
+
# # ...
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# How it works:
|
21
|
+
# - When the job is serialized (enqueued), the first argument is checked.
|
22
|
+
# - If the argument is a Hash and its JSON representation exceeds the compression threshold,
|
23
|
+
# it is compressed using zlib and base64, and marked with a special key.
|
24
|
+
# - When the job is deserialized (dequeued), the argument is checked for the marker and
|
25
|
+
# transparently decompressed if needed.
|
26
|
+
# - Old jobs (with uncompressed payloads) are still supported for backward compatibility.
|
27
|
+
#
|
28
|
+
# Configuration:
|
29
|
+
# - The compression threshold is configurable via ActiveJob::Compressible.configuration
|
30
|
+
# - Defaults to cache backend's :value_max_bytes minus 100,000 bytes for safety
|
31
|
+
#
|
32
|
+
# This concern is safe for rolling deployments and can be included in any job class that may
|
33
|
+
# enqueue large payloads.
|
34
|
+
module Compressible
|
35
|
+
extend ActiveSupport::Concern
|
36
|
+
|
37
|
+
included do
|
38
|
+
def serialize
|
39
|
+
super.tap do |h|
|
40
|
+
if h["arguments"] && h["arguments"].first.is_a?(Hash)
|
41
|
+
h["arguments"][0] = self.class.compress_if_needed(h["arguments"].first)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class_methods do
|
48
|
+
def deserialize(job_data)
|
49
|
+
arg = job_data["arguments"].first
|
50
|
+
job_data = job_data.dup
|
51
|
+
job_data["arguments"][0] = decompress_if_needed(arg)
|
52
|
+
super
|
53
|
+
end
|
54
|
+
|
55
|
+
def compress_if_needed(obj)
|
56
|
+
json_str = obj.to_json
|
57
|
+
if json_str.bytesize > compression_threshold
|
58
|
+
compressed = Base64.encode64(Zlib::Deflate.deflate(json_str))
|
59
|
+
{ "_compressed" => true, "data" => compressed }
|
60
|
+
else
|
61
|
+
obj
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def decompress_if_needed(arg)
|
66
|
+
if arg.is_a?(Hash) && arg["_compressed"]
|
67
|
+
JSON.parse(Zlib::Inflate.inflate(Base64.decode64(arg["data"])))
|
68
|
+
else
|
69
|
+
arg
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def compression_threshold
|
74
|
+
ActiveJob::Compressible.configuration.compression_threshold
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module ActiveJob
|
2
|
+
module Compressible
|
3
|
+
class Configuration
|
4
|
+
attr_accessor compression_threshold: Integer
|
5
|
+
attr_reader compression_algorithm: Symbol
|
6
|
+
|
7
|
+
def initialize: () -> void
|
8
|
+
|
9
|
+
def compression_algorithm=: (Symbol algorithm) -> Symbol
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def default_compression_threshold: () -> Integer
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ActiveJob
|
2
|
+
module Compressible
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def serialize: () -> Hash[String, untyped]
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def deserialize: (Hash[String, untyped] job_data) -> ActiveJob::Base
|
9
|
+
|
10
|
+
def compress_if_needed: (Hash[String, untyped] obj) -> (Hash[String, untyped] | Hash[String, String | bool])
|
11
|
+
|
12
|
+
def decompress_if_needed: (untyped arg) -> untyped
|
13
|
+
|
14
|
+
def compression_threshold: () -> Integer
|
15
|
+
end
|
16
|
+
|
17
|
+
VERSION: String
|
18
|
+
|
19
|
+
def self.configuration: () -> Configuration
|
20
|
+
|
21
|
+
def self.configure: () { (Configuration) -> void } -> void
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activejob-compressible
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Evan Lee
|
8
|
+
bindir: exe
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-06-05 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: activejob
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '6.0'
|
19
|
+
- - "<"
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '9.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '6.0'
|
29
|
+
- - "<"
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: '9.0'
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: activesupport
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '6.0'
|
39
|
+
- - "<"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '9.0'
|
42
|
+
type: :runtime
|
43
|
+
prerelease: false
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '6.0'
|
49
|
+
- - "<"
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '9.0'
|
52
|
+
description: Automatically compress/decompress large job arguments to avoid size restrictions
|
53
|
+
email:
|
54
|
+
- evan.lee@shopify.com
|
55
|
+
executables: []
|
56
|
+
extensions: []
|
57
|
+
extra_rdoc_files: []
|
58
|
+
files:
|
59
|
+
- CHANGELOG.md
|
60
|
+
- LICENSE
|
61
|
+
- README.md
|
62
|
+
- lib/activejob-compressible.rb
|
63
|
+
- lib/activejob/compressible.rb
|
64
|
+
- lib/activejob/compressible/configuration.rb
|
65
|
+
- lib/activejob/compressible/version.rb
|
66
|
+
- sig/activejob/compressible.rbs
|
67
|
+
- sig/activejob/compressible/configuration.rbs
|
68
|
+
- sig/activejob/compressible/version.rbs
|
69
|
+
homepage: https://github.com/Archetypically/activejob-compressible
|
70
|
+
licenses:
|
71
|
+
- MIT
|
72
|
+
metadata:
|
73
|
+
rubygems_mfa_required: 'true'
|
74
|
+
homepage_uri: https://github.com/Archetypically/activejob-compressible
|
75
|
+
source_code_uri: https://github.com/Archetypically/activejob-compressible
|
76
|
+
changelog_uri: https://github.com/Archetypically/activejob-compressible/blob/main/CHANGELOG.md
|
77
|
+
rdoc_options: []
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '3.1'
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
requirements: []
|
91
|
+
rubygems_version: 3.6.2
|
92
|
+
specification_version: 4
|
93
|
+
summary: Transparent compression for large ActiveJob payloads
|
94
|
+
test_files: []
|