litestack 0.1.6 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +3 -3
- data/bench/bench.rb +3 -3
- data/bench/bench_jobs_raw.rb +28 -12
- data/bench/skjob.rb +5 -3
- data/bench/uljob.rb +5 -4
- data/lib/active_record/connection_adapters/litedb_adapter.rb +1 -1
- data/lib/litestack/litejobqueue.rb +34 -15
- data/lib/litestack/litequeue.rb +5 -3
- data/lib/litestack/litesupport.rb +9 -4
- data/lib/litestack/version.rb +1 -1
- data/lib/sequel/adapters/shared/litedb.rb +1054 -0
- metadata +3 -3
- data/lib/active_support/cache/ultralite_cache_store.rb +0 -100
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2662941f303da99554039370e6e53e4c13956c52070e93ad6862742881ca8063
|
4
|
+
data.tar.gz: ccd5583a9e0e7f5c559f8dafdde913bba256b7b5a679fe927cd31805c1289022
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1a139b3c42cd3f9a327fc7d8f1487dab2e95f2c88d07e063eecc2e438c91236f301a8e3928c9a3b41030fd8c08f4c118c449e56ea4be35b73b00fe2aa240faf8
|
7
|
+
data.tar.gz: 409221a087df5707bd6da477bb9091daf2ee0fc5f8114d71a7666324676bc62e5d77b6edfd1e3e7d29d601985f7936b2e3545e618b1c888b6c7a748544bbafde
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.1.7] - 2022-03-05
|
4
|
+
|
5
|
+
- Code cleanup, removal of references to older name
|
6
|
+
- Fix for the litedb rake tasks (thanks: netmute)
|
7
|
+
- More fixes for the new concurrency model
|
8
|
+
- Introduced a logger for the Litejobqueue (doesn't work with Polyphony, fix should come soon)
|
9
|
+
|
3
10
|
## [0.1.6] - 2022-03-03
|
4
11
|
|
5
12
|
- Revamped the locking model, more robust, minimal performance hit
|
data/README.md
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
![litestack](https://github.com/oldmoe/litestack/blob/master/assets/litestack_logo_teal_large.png?raw=true)
|
2
2
|
|
3
3
|
|
4
|
-
litestack is a revolutionary gem for Ruby and Ruby on Rails that provides an all-in-one solution for web application development. It exploits the power and embeddedness of SQLite to include a full-fledged SQL database, a fast cache
|
4
|
+
litestack is a revolutionary gem for Ruby and Ruby on Rails that provides an all-in-one solution for web application development. It exploits the power and embeddedness of SQLite to include a full-fledged SQL database, a fast cache and a robust job queue all in a single package.
|
5
5
|
|
6
|
-
Compared to conventional approaches that require separate servers and databases,
|
6
|
+
Compared to conventional approaches that require separate servers and databases, Litestack offers superior performance, efficiency, ease of use, and cost savings. Its embedded database and cache reduce memory and CPU usage, while its simple interface streamlines the development process. Overall, LiteStack sets a new standard for web application development and is an excellent choice for those who demand speed, efficiency, and simplicity.
|
7
7
|
|
8
8
|
You can read more about why litestack can be a good choice for your next web application **[here](WHYLITESTACK.md)**, you might also be interested in litestack **[benchmarks](BENCHMARKS.md)**.
|
9
9
|
|
@@ -83,7 +83,7 @@ adapter: litedb
|
|
83
83
|
litedb offers integration with the Sequel database toolkit and can be configured as follows
|
84
84
|
|
85
85
|
```ruby
|
86
|
-
DB = Sequel.
|
86
|
+
DB = Sequel.connect("litedb://path_to_db_file")
|
87
87
|
```
|
88
88
|
|
89
89
|
|
data/bench/bench.rb
CHANGED
@@ -2,8 +2,8 @@ require 'sqlite3'
|
|
2
2
|
|
3
3
|
def bench(msg, iterations=1000)
|
4
4
|
GC.start
|
5
|
-
GC.compact
|
6
|
-
|
5
|
+
#GC.compact
|
6
|
+
STDERR.puts "Starting #{iterations} iterations of #{msg} ... "
|
7
7
|
t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
8
8
|
iterations.times do |i|
|
9
9
|
yield i
|
@@ -12,7 +12,7 @@ def bench(msg, iterations=1000)
|
|
12
12
|
time = ((t2 - t1)*1000).to_i.to_f / 1000 rescue 0
|
13
13
|
ips = ((iterations/(t2-t1))*100).to_i.to_f / 100 rescue "infinity?"
|
14
14
|
#{m: msg, t: time, ips: iteratinos/time, i: iterations}
|
15
|
-
|
15
|
+
STDERR.puts " .. finished in #{time} seconds (#{ips} ips)"
|
16
16
|
end
|
17
17
|
|
18
18
|
@db = SQLite3::Database.new(":memory:") # sqlite database for fast random string generation
|
data/bench/bench_jobs_raw.rb
CHANGED
@@ -1,29 +1,45 @@
|
|
1
|
-
#require 'polyphony'
|
2
|
-
require 'async/scheduler'
|
3
1
|
require './bench'
|
4
2
|
|
5
|
-
|
3
|
+
count = ARGV[0].to_i rescue 1000
|
4
|
+
env = ARGV[1] || "t"
|
5
|
+
delay = ARGV[2].to_f rescue 0
|
6
6
|
|
7
|
-
|
7
|
+
# Sidekiq bench
|
8
|
+
###############
|
8
9
|
require './skjob.rb'
|
9
|
-
require './uljob.rb'
|
10
|
-
|
11
|
-
puts Litesupport.environment
|
12
10
|
|
13
|
-
t =
|
11
|
+
t = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
14
12
|
puts "make sure sidekiq is started with skjob.rb as the job"
|
15
13
|
bench("enqueuing sidekiq jobs", count) do |i|
|
16
|
-
SidekiqJob.perform_async(count, t)
|
14
|
+
SidekiqJob.perform_async(count, t, delay)
|
17
15
|
end
|
18
16
|
|
19
17
|
puts "Don't forget to check the sidekiq log for processing time conclusion"
|
20
18
|
|
21
|
-
|
19
|
+
# Litejob bench
|
20
|
+
###############
|
21
|
+
|
22
|
+
if env == "t" # threaded
|
23
|
+
# do nothing
|
24
|
+
elsif env == "a" # async
|
25
|
+
require 'async/scheduler'
|
26
|
+
Fiber.set_scheduler Async::Scheduler.new
|
27
|
+
elsif env == "p" # polyphony
|
28
|
+
require 'polyphony'
|
29
|
+
end
|
30
|
+
|
31
|
+
require './uljob.rb'
|
32
|
+
|
33
|
+
STDERR.puts "litejob started in #{Litesupport.environment} environmnet"
|
34
|
+
|
35
|
+
t = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
22
36
|
bench("enqueuing litejobs", count) do |i|
|
23
|
-
MyJob.perform_async(count, t)
|
37
|
+
MyJob.perform_async(count, t, delay)
|
24
38
|
end
|
25
39
|
|
26
|
-
|
40
|
+
puts "Please wait for the benchmark to finish .."
|
41
|
+
|
42
|
+
Fiber.scheduler.run if env == "a"
|
27
43
|
|
28
44
|
sleep
|
29
45
|
|
data/bench/skjob.rb
CHANGED
@@ -3,11 +3,13 @@ require 'sidekiq'
|
|
3
3
|
class SidekiqJob
|
4
4
|
include Sidekiq::Job
|
5
5
|
@@count = 0
|
6
|
-
def perform(count, time)
|
7
|
-
sleep
|
6
|
+
def perform(count, time, sleep_interval = nil)
|
7
|
+
sleep sleep_interval if sleep_interval
|
8
8
|
@@count += 1
|
9
9
|
if @@count == count
|
10
|
-
|
10
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
11
|
+
STDERR.puts "Sidekiq finished in #{now - time} seconds (#{count / (now - time)} jps)"
|
12
|
+
@@count = 0
|
11
13
|
end
|
12
14
|
end
|
13
15
|
end
|
data/bench/uljob.rb
CHANGED
@@ -4,12 +4,13 @@ require '../lib/litestack'
|
|
4
4
|
class MyJob
|
5
5
|
include Litejob
|
6
6
|
@@count = 0
|
7
|
-
#
|
8
|
-
def perform(count, time)
|
9
|
-
|
7
|
+
#self.queue = :default
|
8
|
+
def perform(count, time, sleep_interval = nil)
|
9
|
+
sleep sleep_interval if sleep_interval
|
10
10
|
@@count += 1
|
11
11
|
if @@count == count
|
12
|
-
|
12
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
13
|
+
STDERR.puts "Litejob finished in #{now - time} seconds (#{count / (now - time)} jps)"
|
13
14
|
end
|
14
15
|
end
|
15
16
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# frozen_stringe_literal: true
|
2
|
-
|
2
|
+
require 'logger'
|
3
3
|
require 'oj'
|
4
4
|
require 'yaml'
|
5
5
|
require_relative './litequeue'
|
@@ -32,9 +32,10 @@ class Litejobqueue
|
|
32
32
|
DEFAULT_OPTIONS = {
|
33
33
|
config_path: "./litejob.yml",
|
34
34
|
path: "./queue.db",
|
35
|
-
queues: [["default",
|
36
|
-
workers:
|
37
|
-
|
35
|
+
queues: [["default", 1]],
|
36
|
+
workers: 5,
|
37
|
+
logger: STDOUT,
|
38
|
+
sleep_intervals: [0.001, 0.005, 0.025, 0.125, 0.625, 1.0, 2.0]
|
38
39
|
}
|
39
40
|
|
40
41
|
@@queue = nil
|
@@ -57,7 +58,6 @@ class Litejobqueue
|
|
57
58
|
#
|
58
59
|
def initialize(options = {})
|
59
60
|
@options = DEFAULT_OPTIONS.merge(options)
|
60
|
-
@worker_sleep_index = 0
|
61
61
|
config = YAML.load_file(@options[:config_path]) rescue {} # an empty hash won't hurt
|
62
62
|
config.keys.each do |k| # symbolize keys
|
63
63
|
config[k.to_sym] = config[k]
|
@@ -65,6 +65,11 @@ class Litejobqueue
|
|
65
65
|
end
|
66
66
|
@options.merge!(config)
|
67
67
|
@queue = Litequeue.new(@options) # create a new queue object
|
68
|
+
if @options[:logger].respond_to? :info
|
69
|
+
@logger = @options[:logger]
|
70
|
+
else
|
71
|
+
@logger = Logger.new(@options[:logger])
|
72
|
+
end
|
68
73
|
# group and order queues according to their priority
|
69
74
|
pgroups = {}
|
70
75
|
@options[:queues].each do |q|
|
@@ -72,7 +77,7 @@ class Litejobqueue
|
|
72
77
|
pgroups[q[1]] << [q[0], q[2] == "spawn"]
|
73
78
|
end
|
74
79
|
@queues = pgroups.keys.sort.reverse.collect{|p| [p, pgroups[p]]}
|
75
|
-
@workers = @options[:workers].times.collect{create_worker}
|
80
|
+
@workers = @options[:workers].times.collect{ create_worker }
|
76
81
|
end
|
77
82
|
|
78
83
|
# push a job to the queue
|
@@ -85,7 +90,10 @@ class Litejobqueue
|
|
85
90
|
# jobqueue.push(EasyJob, params) # the job will be performed asynchronously
|
86
91
|
def push(jobclass, params, delay=0, queue=nil)
|
87
92
|
payload = Oj.dump([jobclass, params])
|
88
|
-
|
93
|
+
#res =
|
94
|
+
res = @queue.push(payload, delay, queue)
|
95
|
+
@logger.info("[litejob]:[ENQ] id: #{res} class: #{jobclass}")
|
96
|
+
res
|
89
97
|
end
|
90
98
|
|
91
99
|
# delete a job from the job queue
|
@@ -99,7 +107,9 @@ class Litejobqueue
|
|
99
107
|
# jobqueue.delete(id)
|
100
108
|
def delete(id)
|
101
109
|
job = @queue.delete(id)
|
110
|
+
@logger.info("[litejob]:[DEL] job: #{job}")
|
102
111
|
Oj.load(job) if job
|
112
|
+
job
|
103
113
|
end
|
104
114
|
|
105
115
|
private
|
@@ -111,27 +121,36 @@ class Litejobqueue
|
|
111
121
|
else
|
112
122
|
yield
|
113
123
|
end
|
114
|
-
end
|
115
|
-
|
124
|
+
end
|
125
|
+
|
116
126
|
# create a worker according to environment
|
117
127
|
def create_worker
|
118
128
|
Litesupport.spawn do
|
129
|
+
if @options[:logger].respond_to? :info
|
130
|
+
logger = @options[:logger]
|
131
|
+
else
|
132
|
+
logger = Logger.new(@options[:logger])
|
133
|
+
end
|
134
|
+
worker_sleep_index = 0
|
135
|
+
i = 0
|
119
136
|
loop do
|
120
137
|
processed = 0
|
121
138
|
@queues.each do |level| # iterate through the levels
|
122
139
|
level[1].each do |q| # iterate through the queues in the level
|
123
140
|
index = 0
|
124
141
|
max = level[0]
|
125
|
-
while index < max && payload = @queue.pop(q[0]) # fearlessly use the same queue object
|
142
|
+
while index < max && payload = @queue.pop(q[0], 1) # fearlessly use the same queue object
|
126
143
|
processed += 1
|
127
144
|
index += 1
|
128
145
|
begin
|
129
146
|
id, job = payload[0], payload[1]
|
130
147
|
job = Oj.load(job)
|
148
|
+
logger.info "[litejob]:[DEQ] id: #{id} class: #{job[0]}"
|
131
149
|
klass = eval(job[0])
|
132
150
|
schedule(q[1]) do # run the job in a new context
|
133
151
|
begin
|
134
152
|
klass.new.perform(*job[1])
|
153
|
+
logger.info "[litejob]:[END] id: #{id} class: #{job[0]}"
|
135
154
|
rescue Exception => e
|
136
155
|
puts e
|
137
156
|
puts e.message
|
@@ -143,18 +162,18 @@ class Litejobqueue
|
|
143
162
|
puts e.message
|
144
163
|
puts e.backtrace
|
145
164
|
end
|
146
|
-
Litesupport.switch #give other
|
165
|
+
Litesupport.switch #give other contexts a chance to run here
|
147
166
|
end
|
148
167
|
end
|
149
168
|
end
|
150
169
|
if processed == 0
|
151
|
-
sleep @options[:sleep_intervals][
|
152
|
-
|
170
|
+
sleep @options[:sleep_intervals][worker_sleep_index]
|
171
|
+
worker_sleep_index += 1 if worker_sleep_index < @options[:sleep_intervals].length - 1
|
153
172
|
else
|
154
|
-
|
173
|
+
worker_sleep_index = 0 # reset the index
|
155
174
|
end
|
156
175
|
end
|
157
176
|
end
|
158
|
-
end
|
177
|
+
end
|
159
178
|
|
160
179
|
end
|
data/lib/litestack/litequeue.rb
CHANGED
@@ -52,8 +52,10 @@ class Litequeue
|
|
52
52
|
alias_method :"<<", :push
|
53
53
|
|
54
54
|
# pop an item from the queue, optionally with a specific queue name (default queue name is 'default')
|
55
|
-
def pop(queue='default')
|
56
|
-
|
55
|
+
def pop(queue='default', limit = 1)
|
56
|
+
res = @queue.acquire {|q| res = q.stmts[:pop].execute!(queue, limit)[0] }
|
57
|
+
#return res[0] if res.length == 1
|
58
|
+
#res
|
57
59
|
end
|
58
60
|
|
59
61
|
# delete an item from the queue
|
@@ -90,7 +92,7 @@ class Litequeue
|
|
90
92
|
db.mmap_size = @options[:mmap_size]
|
91
93
|
db.execute("CREATE TABLE IF NOT EXISTS _ul_queue_(queue TEXT DEFAULT('default') NOT NULL ON CONFLICT REPLACE, fire_at INTEGER DEFAULT(unixepoch()) NOT NULL ON CONFLICT REPLACE, id TEXT DEFAULT(hex(randomblob(8)) || (strftime('%f') * 100)) NOT NULL ON CONFLICT REPLACE, value TEXT, created_at INTEGER DEFAULT(unixepoch()) NOT NULL ON CONFLICT REPLACE, PRIMARY KEY(queue, fire_at ASC, id) ) WITHOUT ROWID")
|
92
94
|
db.stmts[:push] = db.prepare("INSERT INTO _ul_queue_(queue, fire_at, value) VALUES ($1, (strftime('%s') + $2), $3) RETURNING fire_at || '-' || id")
|
93
|
-
db.stmts[:pop] = db.prepare("DELETE FROM _ul_queue_ WHERE (queue, fire_at, id)
|
95
|
+
db.stmts[:pop] = db.prepare("DELETE FROM _ul_queue_ WHERE (queue, fire_at, id) IN (SELECT queue, fire_at, id FROM _ul_queue_ WHERE queue = ifnull($1, 'default') AND fire_at <= (unixepoch()) ORDER BY fire_at ASC LIMIT ifnull($2, 1)) RETURNING fire_at || '-' || id, value")
|
94
96
|
db.stmts[:delete] = db.prepare("DELETE FROM _ul_queue_ WHERE queue = ifnull($1, 'default') AND fire_at = $2 AND id = $3 RETURNING value")
|
95
97
|
db
|
96
98
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'sqlite3'
|
2
|
+
require 'hiredis'
|
2
3
|
|
3
4
|
module Litesupport
|
4
5
|
|
@@ -11,6 +12,11 @@ module Litesupport
|
|
11
12
|
@env ||= detect_environment
|
12
13
|
end
|
13
14
|
|
15
|
+
def self.max_contexts
|
16
|
+
return 50 if environment == :fiber || environment == :polyphony
|
17
|
+
5
|
18
|
+
end
|
19
|
+
|
14
20
|
# identify which environment we are running in
|
15
21
|
# we currently support :fiber, :polyphony, :iodine & :threaded
|
16
22
|
# in the future we might want to expand to other environments
|
@@ -55,7 +61,7 @@ module Litesupport
|
|
55
61
|
Thread.current.switch_fiber
|
56
62
|
true
|
57
63
|
else
|
58
|
-
#
|
64
|
+
#Thread.pass
|
59
65
|
false
|
60
66
|
end
|
61
67
|
end
|
@@ -124,7 +130,7 @@ module Litesupport
|
|
124
130
|
result = nil
|
125
131
|
while !acquired do
|
126
132
|
@mutex.synchronize do
|
127
|
-
if resource = @resources.find{|r| r[1] == :free}
|
133
|
+
if resource = @resources.find{|r| r[1] == :free }
|
128
134
|
resource[1] = :busy
|
129
135
|
begin
|
130
136
|
result = yield resource[0]
|
@@ -133,11 +139,10 @@ module Litesupport
|
|
133
139
|
ensure
|
134
140
|
resource[1] = :free
|
135
141
|
acquired = true
|
136
|
-
return nil
|
137
142
|
end
|
138
143
|
end
|
139
144
|
end
|
140
|
-
sleep 0.
|
145
|
+
sleep 0.001 unless acquired
|
141
146
|
end
|
142
147
|
result
|
143
148
|
end
|
data/lib/litestack/version.rb
CHANGED