litestack 0.4.1 → 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.standard.yml +3 -0
- data/BENCHMARKS.md +23 -7
- data/CHANGELOG.md +35 -0
- data/Gemfile +1 -7
- data/README.md +124 -6
- data/ROADMAP.md +45 -0
- data/Rakefile +3 -1
- data/WHYLITESTACK.md +1 -1
- data/assets/litecache_metrics.png +0 -0
- data/assets/litedb_metrics.png +0 -0
- data/assets/litemetric_logo_teal.png +0 -0
- data/assets/litesearch_logo_teal.png +0 -0
- data/bench/bench.rb +17 -10
- data/bench/bench_cache_rails.rb +45 -14
- data/bench/bench_cache_raw.rb +44 -28
- data/bench/bench_jobs_rails.rb +18 -12
- data/bench/bench_jobs_raw.rb +17 -10
- data/bench/bench_queue.rb +4 -6
- data/bench/rails_job.rb +5 -7
- data/bench/skjob.rb +4 -4
- data/bench/uljob.rb +6 -6
- data/bin/liteboard +2 -1
- data/lib/action_cable/subscription_adapter/litecable.rb +5 -8
- data/lib/active_job/queue_adapters/litejob_adapter.rb +6 -8
- data/lib/active_record/connection_adapters/litedb_adapter.rb +72 -84
- data/lib/active_support/cache/litecache.rb +61 -41
- data/lib/generators/litestack/install/install_generator.rb +3 -3
- data/lib/generators/litestack/install/templates/cable.yml +0 -3
- data/lib/generators/litestack/install/templates/database.yml +7 -1
- data/lib/litestack/liteboard/liteboard.rb +269 -149
- data/lib/litestack/litecable.rb +41 -37
- data/lib/litestack/litecable.sql.yml +22 -11
- data/lib/litestack/litecache.rb +118 -93
- data/lib/litestack/litecache.sql.yml +83 -22
- data/lib/litestack/litecache.yml +1 -1
- data/lib/litestack/litedb.rb +35 -40
- data/lib/litestack/litejob.rb +30 -29
- data/lib/litestack/litejobqueue.rb +63 -65
- data/lib/litestack/litemetric.rb +80 -92
- data/lib/litestack/litemetric.sql.yml +244 -234
- data/lib/litestack/litemetric_collector.sql.yml +38 -41
- data/lib/litestack/litequeue.rb +39 -41
- data/lib/litestack/litequeue.sql.yml +39 -31
- data/lib/litestack/litescheduler.rb +24 -18
- data/lib/litestack/litesearch/index.rb +93 -63
- data/lib/litestack/litesearch/model.rb +66 -65
- data/lib/litestack/litesearch/schema.rb +53 -56
- data/lib/litestack/litesearch/schema_adapters/backed_adapter.rb +46 -50
- data/lib/litestack/litesearch/schema_adapters/basic_adapter.rb +44 -35
- data/lib/litestack/litesearch/schema_adapters/contentless_adapter.rb +3 -6
- data/lib/litestack/litesearch/schema_adapters/standalone_adapter.rb +7 -9
- data/lib/litestack/litesearch/schema_adapters.rb +4 -9
- data/lib/litestack/litesearch.rb +6 -9
- data/lib/litestack/litesupport.rb +78 -87
- data/lib/litestack/railtie.rb +1 -1
- data/lib/litestack/version.rb +2 -2
- data/lib/litestack.rb +6 -4
- data/lib/railties/rails/commands/dbconsole.rb +16 -20
- data/lib/sequel/adapters/litedb.rb +16 -21
- data/lib/sequel/adapters/shared/litedb.rb +168 -168
- data/scripts/build_metrics.rb +91 -0
- data/scripts/test_cable.rb +30 -0
- data/scripts/test_job_retry.rb +33 -0
- data/scripts/test_metrics.rb +60 -0
- data/template.rb +2 -2
- metadata +115 -7
data/lib/litestack/litecable.rb
CHANGED
@@ -1,18 +1,16 @@
|
|
1
1
|
# frozen_stringe_literal: true
|
2
2
|
|
3
3
|
# all components should require the support module
|
4
|
-
require_relative
|
5
|
-
require_relative
|
4
|
+
require_relative "litesupport"
|
5
|
+
require_relative "litemetric"
|
6
6
|
|
7
|
-
require
|
8
|
-
require
|
7
|
+
require "base64"
|
8
|
+
require "oj"
|
9
9
|
|
10
10
|
class Litecable
|
11
|
-
|
12
11
|
include Litesupport::Liteconnection
|
13
12
|
include Litemetric::Measurable
|
14
13
|
|
15
|
-
|
16
14
|
DEFAULT_OPTIONS = {
|
17
15
|
config_path: "./litecable.yml",
|
18
16
|
path: Litesupport.root.join("cable.sqlite3"),
|
@@ -22,56 +20,63 @@ class Litecable
|
|
22
20
|
listen_interval: 0.05, # check new messages every 50 milliseconds
|
23
21
|
metrics: false
|
24
22
|
}
|
25
|
-
|
26
|
-
def initialize(options = {})
|
27
|
-
@messages = Litesupport::Pool.new(1){[]}
|
23
|
+
|
24
|
+
def initialize(options = {})
|
25
|
+
@messages = Litesupport::Pool.new(1) { [] }
|
28
26
|
init(options)
|
29
27
|
collect_metrics if @options[:metrics]
|
30
28
|
end
|
31
|
-
|
29
|
+
|
32
30
|
# broadcast a message to a specific channel
|
33
|
-
def broadcast(channel, payload=nil)
|
31
|
+
def broadcast(channel, payload = nil)
|
34
32
|
# group meesages and only do broadcast every 10 ms
|
35
33
|
# but broadcast locally normally
|
36
|
-
@messages.acquire{|msgs| msgs << [channel.to_s, Oj.dump(payload)]}
|
34
|
+
@messages.acquire { |msgs| msgs << [channel.to_s, Oj.dump(payload)] }
|
37
35
|
capture(:broadcast, channel)
|
38
|
-
local_broadcast(channel, payload)
|
36
|
+
local_broadcast(channel, payload)
|
39
37
|
end
|
40
|
-
|
38
|
+
|
41
39
|
# subscribe to a channel, optionally providing a success callback proc
|
42
40
|
def subscribe(channel, subscriber, success_callback = nil)
|
43
41
|
@subscribers.acquire do |subs|
|
44
42
|
subs[channel] = {} unless subs[channel]
|
45
43
|
subs[channel][subscriber] = true
|
46
44
|
end
|
45
|
+
success_callback&.call
|
47
46
|
capture(:subscribe, channel)
|
48
47
|
end
|
49
|
-
|
48
|
+
|
50
49
|
# unsubscribe from a channel
|
51
50
|
def unsubscribe(channel, subscriber)
|
52
|
-
@subscribers.acquire{|subs|
|
51
|
+
@subscribers.acquire { |subs|
|
52
|
+
begin
|
53
|
+
subs[channel].delete(subscriber)
|
54
|
+
rescue
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
}
|
53
58
|
capture(:unsubscribe, channel)
|
54
59
|
end
|
55
60
|
|
56
|
-
private
|
57
|
-
|
58
|
-
# broadcast the message to local subscribers
|
59
|
-
def local_broadcast(channel, payload=nil)
|
61
|
+
private
|
62
|
+
|
63
|
+
# broadcast the message to local subscribers
|
64
|
+
def local_broadcast(channel, payload = nil)
|
60
65
|
subscribers = []
|
61
|
-
@subscribers.acquire do |subs|
|
62
|
-
|
66
|
+
@subscribers.acquire do |subs|
|
67
|
+
break unless subs[channel]
|
63
68
|
subscribers = subs[channel].keys
|
64
69
|
end
|
65
70
|
subscribers.each do |subscriber|
|
66
|
-
subscriber.call(payload)
|
71
|
+
subscriber.call(payload)
|
67
72
|
capture(:message, channel)
|
68
73
|
end
|
69
|
-
end
|
70
|
-
|
74
|
+
end
|
75
|
+
|
71
76
|
def setup
|
72
77
|
super # create connection
|
73
78
|
@pid = Process.pid
|
74
|
-
@subscribers = Litesupport::Pool.new(1){{}}
|
79
|
+
@subscribers = Litesupport::Pool.new(1) { {} }
|
75
80
|
@running = true
|
76
81
|
@listener = create_listener
|
77
82
|
@pruner = create_pruner
|
@@ -81,48 +86,47 @@ class Litecable
|
|
81
86
|
|
82
87
|
def create_broadcaster
|
83
88
|
Litescheduler.spawn do
|
84
|
-
while @running
|
89
|
+
while @running
|
85
90
|
@messages.acquire do |msgs|
|
86
91
|
if msgs.length > 0
|
87
92
|
run_sql("BEGIN IMMEDIATE")
|
88
|
-
while msg = msgs.shift
|
93
|
+
while (msg = msgs.shift)
|
89
94
|
run_stmt(:publish, msg[0], msg[1], @pid)
|
90
95
|
end
|
91
96
|
run_sql("END")
|
92
|
-
end
|
97
|
+
end
|
93
98
|
end
|
94
99
|
sleep 0.02
|
95
|
-
end
|
100
|
+
end
|
96
101
|
end
|
97
102
|
end
|
98
103
|
|
99
104
|
def create_pruner
|
100
105
|
Litescheduler.spawn do
|
101
|
-
while @running
|
106
|
+
while @running
|
102
107
|
run_stmt(:prune, @options[:expire_after])
|
103
108
|
sleep @options[:expire_after]
|
104
|
-
end
|
109
|
+
end
|
105
110
|
end
|
106
111
|
end
|
107
112
|
|
108
113
|
def create_listener
|
109
114
|
Litescheduler.spawn do
|
110
|
-
while @running
|
115
|
+
while @running
|
111
116
|
@last_fetched_id ||= (run_stmt(:last_id)[0][0] || 0)
|
112
117
|
run_stmt(:fetch, @last_fetched_id, @pid).to_a.each do |msg|
|
113
118
|
@logger.info "RECEIVED #{msg}"
|
114
119
|
@last_fetched_id = msg[0]
|
115
|
-
local_broadcast(msg[1], Oj.load(msg[2]))
|
120
|
+
local_broadcast(msg[1], Oj.load(msg[2]))
|
116
121
|
end
|
117
122
|
sleep @options[:listen_interval]
|
118
|
-
end
|
123
|
+
end
|
119
124
|
end
|
120
125
|
end
|
121
126
|
|
122
127
|
def create_connection
|
123
128
|
super("#{__dir__}/litecable.sql.yml") do |conn|
|
124
|
-
conn.wal_autocheckpoint = 10000
|
129
|
+
conn.wal_autocheckpoint = 10000
|
125
130
|
end
|
126
131
|
end
|
127
|
-
|
128
132
|
end
|
@@ -2,23 +2,34 @@ schema:
|
|
2
2
|
1:
|
3
3
|
create_table_messages: >
|
4
4
|
CREATE TABLE IF NOT EXISTS messages(
|
5
|
-
id INTEGER PRIMARY KEY autoincrement,
|
6
|
-
channel TEXT NOT NULL,
|
7
|
-
value TEXT NOT NULL,
|
8
|
-
pid INTEGER,
|
5
|
+
id INTEGER PRIMARY KEY autoincrement,
|
6
|
+
channel TEXT NOT NULL,
|
7
|
+
value TEXT NOT NULL,
|
8
|
+
pid INTEGER,
|
9
9
|
created_at INTEGER NOT NULL ON CONFLICT REPLACE DEFAULT(unixepoch())
|
10
10
|
);
|
11
11
|
create_index_messages_by_date: >
|
12
|
-
CREATE INDEX IF NOT EXISTS messages_by_date ON messages(created_at);
|
12
|
+
CREATE INDEX IF NOT EXISTS messages_by_date ON messages(created_at);
|
13
13
|
|
14
14
|
stmts:
|
15
|
+
publish: >
|
16
|
+
INSERT INTO messages(channel, value, pid)
|
17
|
+
VALUES ($1, $2, $3);
|
15
18
|
|
16
|
-
|
19
|
+
last_id: >
|
20
|
+
SELECT max(id) FROM messages;
|
17
21
|
|
18
|
-
|
19
|
-
|
20
|
-
|
22
|
+
fetch: >
|
23
|
+
SELECT id, channel, value, created_at
|
24
|
+
FROM messages
|
25
|
+
WHERE id > $1
|
26
|
+
AND pid != $2;
|
21
27
|
|
22
|
-
prune:
|
28
|
+
prune: >
|
29
|
+
DELETE FROM messages
|
30
|
+
WHERE created_at < (unixepoch() - $1);
|
23
31
|
|
24
|
-
check_prune:
|
32
|
+
check_prune: >
|
33
|
+
SELECT count(*)
|
34
|
+
FROM messages
|
35
|
+
WHERE created_at < (unixepoch() - $1);
|
data/lib/litestack/litecache.rb
CHANGED
@@ -1,33 +1,32 @@
|
|
1
1
|
# frozen_stringe_literal: true
|
2
2
|
|
3
3
|
# all components should require the support module
|
4
|
-
require_relative
|
5
|
-
require_relative
|
4
|
+
require_relative "litesupport"
|
5
|
+
require_relative "litemetric"
|
6
6
|
|
7
7
|
##
|
8
|
-
#Litecache is a caching library for Ruby applications that is built on top of SQLite. It is designed to be simple to use, very fast, and feature-rich, providing developers with a reliable and efficient way to cache data.
|
8
|
+
# Litecache is a caching library for Ruby applications that is built on top of SQLite. It is designed to be simple to use, very fast, and feature-rich, providing developers with a reliable and efficient way to cache data.
|
9
9
|
#
|
10
|
-
#One of the main features of Litecache is automatic key expiry, which allows developers to set an expiration time for each cached item. This ensures that cached data is automatically removed from the cache after a certain amount of time has passed, reducing the risk of stale data being served to users.
|
10
|
+
# One of the main features of Litecache is automatic key expiry, which allows developers to set an expiration time for each cached item. This ensures that cached data is automatically removed from the cache after a certain amount of time has passed, reducing the risk of stale data being served to users.
|
11
11
|
#
|
12
|
-
#In addition, Litecache supports LRU (Least Recently Used) removal, which means that if the cache reaches its capacity limit, the least recently used items will be removed first to make room for new items. This ensures that the most frequently accessed data is always available in the cache.
|
12
|
+
# In addition, Litecache supports LRU (Least Recently Used) removal, which means that if the cache reaches its capacity limit, the least recently used items will be removed first to make room for new items. This ensures that the most frequently accessed data is always available in the cache.
|
13
13
|
#
|
14
|
-
#Litecache also supports integer value increment/decrement, which allows developers to increment or decrement the value of a cached item in a thread-safe manner. This is useful for implementing counters or other types of numerical data that need to be updated frequently.
|
14
|
+
# Litecache also supports integer value increment/decrement, which allows developers to increment or decrement the value of a cached item in a thread-safe manner. This is useful for implementing counters or other types of numerical data that need to be updated frequently.
|
15
15
|
#
|
16
|
-
#Overall, Litecache is a powerful and flexible caching library that provides automatic key expiry, LRU removal, and integer value increment/decrement capabilities. Its fast performance and simple API make it an excellent choice for Ruby applications that need a reliable and efficient way to cache data.
|
16
|
+
# Overall, Litecache is a powerful and flexible caching library that provides automatic key expiry, LRU removal, and integer value increment/decrement capabilities. Its fast performance and simple API make it an excellent choice for Ruby applications that need a reliable and efficient way to cache data.
|
17
17
|
|
18
18
|
class Litecache
|
19
|
-
|
20
19
|
include Litesupport::Liteconnection
|
21
20
|
include Litemetric::Measurable
|
22
21
|
|
23
22
|
# the default options for the cache
|
24
|
-
# can be overriden by passing new options in a hash
|
23
|
+
# can be overriden by passing new options in a hash
|
25
24
|
# to Litecache.new
|
26
25
|
# path: "./cache.db"
|
27
26
|
# expiry: 60 * 60 * 24 * 30 -> one month default expiry if none is provided
|
28
|
-
# size: 128 * 1024 * 1024 -> 128MB
|
27
|
+
# size: 128 * 1024 * 1024 -> 128MB
|
29
28
|
# mmap_size: 128 * 1024 * 1024 -> 128MB to be held in memory
|
30
|
-
# min_size: 32 * 1024 ->
|
29
|
+
# min_size: 32 * 1024 -> 32KB
|
31
30
|
# return_full_record: false -> only return the payload
|
32
31
|
# sleep_interval: 1 -> 1 second of sleep between cleanup runs
|
33
32
|
|
@@ -35,15 +34,15 @@ class Litecache
|
|
35
34
|
path: Litesupport.root.join("cache.sqlite3"),
|
36
35
|
config_path: "./litecache.yml",
|
37
36
|
sync: 0,
|
38
|
-
expiry: 60 * 60 * 24 * 30, # one month
|
39
|
-
size: 128 * 1024 * 1024, #128MB
|
40
|
-
mmap_size: 128 * 1024 * 1024, #128MB
|
41
|
-
min_size: 8
|
42
|
-
return_full_record: false, #only return the payload
|
43
|
-
sleep_interval:
|
37
|
+
expiry: 60 * 60 * 24 * 30, # one month
|
38
|
+
size: 128 * 1024 * 1024, # 128MB
|
39
|
+
mmap_size: 128 * 1024 * 1024, # 128MB
|
40
|
+
min_size: 8 * 1024 * 1024, # 16MB
|
41
|
+
return_full_record: false, # only return the payload
|
42
|
+
sleep_interval: 30, # 30 seconds
|
44
43
|
metrics: false
|
45
44
|
}
|
46
|
-
|
45
|
+
|
47
46
|
# creates a new instance of Litecache
|
48
47
|
# can optionally receive an options hash which will be merged
|
49
48
|
# with the DEFAULT_OPTIONS (the new hash overrides any matching keys in the default one).
|
@@ -61,58 +60,72 @@ class Litecache
|
|
61
60
|
#
|
62
61
|
# litecache.clear # nothing remains in the cache
|
63
62
|
# litecache.close # optional, you can safely kill the process
|
64
|
-
|
63
|
+
|
65
64
|
def initialize(options = {})
|
66
|
-
options[:size] = DEFAULT_OPTIONS[:min_size] if options[:size] && options[:size] < DEFAULT_OPTIONS[:min_size]
|
67
|
-
|
68
|
-
|
65
|
+
options[:size] = DEFAULT_OPTIONS[:min_size] if options[:size] && options[:size] < DEFAULT_OPTIONS[:min_size]
|
66
|
+
init(options)
|
67
|
+
@expires_in = @options[:expiry] || 60 * 60 * 24 * 30
|
69
68
|
collect_metrics if @options[:metrics]
|
70
69
|
end
|
71
|
-
|
72
|
-
# add a key, value pair to the cache, with an optional expiry value (number of seconds)
|
70
|
+
|
71
|
+
# add a key, value pair to the cache, with an optional expiry value (number of seconds)
|
73
72
|
def set(key, value, expires_in = nil)
|
74
73
|
key = key.to_s
|
75
|
-
expires_in
|
74
|
+
expires_in ||= @expires_in
|
76
75
|
@conn.acquire do |cache|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
retry
|
84
|
-
end
|
76
|
+
cache.stmts[:setter].execute!(key, value, expires_in)
|
77
|
+
capture(:set, key)
|
78
|
+
rescue SQLite3::FullException
|
79
|
+
cache.stmts[:extra_pruner].execute!(0.2)
|
80
|
+
cache.execute("vacuum")
|
81
|
+
retry
|
85
82
|
end
|
86
|
-
|
83
|
+
true
|
87
84
|
end
|
88
85
|
|
89
|
-
#
|
86
|
+
# set multiple keys and values in one shot set_multi({k1: v1, k2: v2, ... })
|
87
|
+
def set_multi(keys_and_values, expires_in = nil)
|
88
|
+
expires_in ||= @expires_in
|
89
|
+
transaction do |conn|
|
90
|
+
keys_and_values.each_pair do |k, v|
|
91
|
+
begin
|
92
|
+
key = k.to_s
|
93
|
+
conn.stmts[:setter].execute!(key, v, expires_in)
|
94
|
+
capture(:set, key)
|
95
|
+
rescue SQLite3::FullException
|
96
|
+
conn.stmts[:extra_pruner].execute!(0.2)
|
97
|
+
conn.execute("vacuum")
|
98
|
+
retry
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
true
|
103
|
+
end
|
104
|
+
|
105
|
+
# add a key, value pair to the cache, but only if the key doesn't exist, with an optional expiry value (number of seconds)
|
90
106
|
def set_unless_exists(key, value, expires_in = nil)
|
91
107
|
key = key.to_s
|
92
|
-
expires_in
|
108
|
+
expires_in ||= @expires_in
|
93
109
|
changes = 0
|
94
110
|
@conn.acquire do |cache|
|
95
|
-
|
96
|
-
cache.
|
97
|
-
|
98
|
-
changes = cache.changes
|
99
|
-
end
|
100
|
-
capture(:set, key)
|
101
|
-
rescue SQLite3::FullException
|
102
|
-
cache.stmts[:extra_pruner].execute!(0.2)
|
103
|
-
cache.execute("vacuum")
|
104
|
-
retry
|
111
|
+
cache.transaction(:immediate) do
|
112
|
+
cache.stmts[:inserter].execute!(key, value, expires_in)
|
113
|
+
changes = cache.changes
|
105
114
|
end
|
115
|
+
capture(:set, key)
|
116
|
+
rescue SQLite3::FullException
|
117
|
+
cache.stmts[:extra_pruner].execute!(0.2)
|
118
|
+
cache.execute("vacuum")
|
119
|
+
retry
|
106
120
|
end
|
107
|
-
|
121
|
+
changes > 0
|
108
122
|
end
|
109
|
-
|
123
|
+
|
110
124
|
# get a value by its key
|
111
125
|
# if the key doesn't exist or it is expired then null will be returned
|
112
126
|
def get(key)
|
113
127
|
key = key.to_s
|
114
|
-
if record = @conn.acquire{|cache| cache.stmts[:getter].execute!(key)[0] }
|
115
|
-
@last_visited[key] = true
|
128
|
+
if (record = @conn.acquire { |cache| cache.stmts[:getter].execute!(key)[0] })
|
116
129
|
capture(:get, key, 1)
|
117
130
|
return record[1]
|
118
131
|
end
|
@@ -120,6 +133,24 @@ class Litecache
|
|
120
133
|
nil
|
121
134
|
end
|
122
135
|
|
136
|
+
# get multiple values by their keys, a hash with values corresponding to the keys
|
137
|
+
# is returned,
|
138
|
+
def get_multi(*keys)
|
139
|
+
results = {}
|
140
|
+
transaction(:deferred) do |conn|
|
141
|
+
keys.length.times do |i|
|
142
|
+
key = keys[i].to_s
|
143
|
+
if (record = conn.stmts[:getter].execute!(key)[0])
|
144
|
+
results[keys[i]] = record[1] # use the original key format
|
145
|
+
capture(:get, key, 1)
|
146
|
+
else
|
147
|
+
capture(:get, key, 0)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
results
|
152
|
+
end
|
153
|
+
|
123
154
|
# delete a key, value pair from the cache
|
124
155
|
def delete(key)
|
125
156
|
changes = 0
|
@@ -127,59 +158,59 @@ class Litecache
|
|
127
158
|
cache.stmts[:deleter].execute!(key)
|
128
159
|
changes = cache.changes
|
129
160
|
end
|
130
|
-
|
161
|
+
changes > 0
|
131
162
|
end
|
132
|
-
|
163
|
+
|
133
164
|
# increment an integer value by amount, optionally add an expiry value (in seconds)
|
134
165
|
def increment(key, amount, expires_in = nil)
|
135
|
-
expires_in
|
136
|
-
@conn.acquire{|cache| cache.stmts[:incrementer].execute!(key.to_s, amount, expires_in) }
|
166
|
+
expires_in ||= @expires_in
|
167
|
+
@conn.acquire { |cache| cache.stmts[:incrementer].execute!(key.to_s, amount, expires_in) }
|
137
168
|
end
|
138
|
-
|
169
|
+
|
139
170
|
# decrement an integer value by amount, optionally add an expiry value (in seconds)
|
140
171
|
def decrement(key, amount, expires_in = nil)
|
141
172
|
increment(key, -amount, expires_in)
|
142
173
|
end
|
143
|
-
|
174
|
+
|
144
175
|
# delete all entries in the cache up limit (ordered by LRU), if no limit is provided approximately 20% of the entries will be deleted
|
145
|
-
def prune(limit=nil)
|
176
|
+
def prune(limit = nil)
|
146
177
|
@conn.acquire do |cache|
|
147
|
-
if limit
|
178
|
+
if limit&.is_a? Integer
|
148
179
|
cache.stmts[:limited_pruner].execute!(limit)
|
149
|
-
elsif limit
|
180
|
+
elsif limit&.is_a? Float
|
150
181
|
cache.stmts[:extra_pruner].execute!(limit)
|
151
182
|
else
|
152
|
-
cache.stmts[:pruner].execute!
|
183
|
+
cache.stmts[:pruner].execute!
|
153
184
|
end
|
154
185
|
end
|
155
186
|
end
|
156
|
-
|
187
|
+
|
157
188
|
# return the number of key, value pairs in the cache
|
158
189
|
def count
|
159
190
|
run_stmt(:counter)[0][0]
|
160
191
|
end
|
161
|
-
|
192
|
+
|
162
193
|
# return the actual size of the cache file
|
163
|
-
#def size
|
194
|
+
# def size
|
164
195
|
# run_stmt(:sizer)[0][0]
|
165
|
-
#end
|
166
|
-
|
196
|
+
# end
|
197
|
+
|
167
198
|
# delete all key, value pairs in the cache
|
168
199
|
def clear
|
169
200
|
run_sql("delete FROM data")
|
170
201
|
end
|
171
|
-
|
202
|
+
|
172
203
|
# close the connection to the cache file
|
173
204
|
def close
|
174
205
|
@running = false
|
175
206
|
super
|
176
207
|
end
|
177
|
-
|
208
|
+
|
178
209
|
# return the maximum size of the cache
|
179
210
|
def max_size
|
180
211
|
run_sql("SELECT s.page_size * c.max_page_count FROM pragma_page_size() as s, pragma_max_page_count() as c")[0][0].to_f / (1024 * 1024)
|
181
212
|
end
|
182
|
-
|
213
|
+
|
183
214
|
def snapshot
|
184
215
|
{
|
185
216
|
summary: {
|
@@ -193,55 +224,49 @@ class Litecache
|
|
193
224
|
}
|
194
225
|
end
|
195
226
|
|
196
|
-
|
197
227
|
# low level access to SQLite transactions, use with caution
|
198
|
-
def transaction(mode
|
199
|
-
return cache.transaction(mode){yield} unless acquire
|
228
|
+
def transaction(mode=:immediate)
|
200
229
|
@conn.acquire do |cache|
|
201
|
-
cache.
|
230
|
+
if cache.transaction_active?
|
202
231
|
yield
|
232
|
+
else
|
233
|
+
cache.transaction(mode) do
|
234
|
+
yield cache
|
235
|
+
end
|
203
236
|
end
|
204
237
|
end
|
205
238
|
end
|
206
239
|
|
207
|
-
private
|
208
|
-
|
240
|
+
private
|
241
|
+
|
209
242
|
def setup
|
210
243
|
super # create connection
|
211
244
|
@bgthread = spawn_worker # create backgroud pruner thread
|
212
245
|
end
|
213
|
-
|
246
|
+
|
214
247
|
def spawn_worker
|
215
248
|
Litescheduler.spawn do
|
216
249
|
while @running
|
217
250
|
@conn.acquire do |cache|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
rescue SQLite3::BusyException
|
226
|
-
retry
|
227
|
-
rescue SQLite3::FullException
|
228
|
-
cache.stmts[:extra_pruner].execute!(0.2)
|
229
|
-
rescue Exception
|
230
|
-
# database is closed
|
231
|
-
end
|
251
|
+
cache.stmts[:pruner].execute!
|
252
|
+
rescue SQLite3::BusyException
|
253
|
+
retry
|
254
|
+
rescue SQLite3::FullException
|
255
|
+
cache.stmts[:extra_pruner].execute!(0.2)
|
256
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
257
|
+
# database is closed
|
232
258
|
end
|
233
259
|
sleep @options[:sleep_interval]
|
234
260
|
end
|
235
261
|
end
|
236
262
|
end
|
237
|
-
|
263
|
+
|
238
264
|
def create_connection
|
239
265
|
super("#{__dir__}/litecache.sql.yml") do |conn|
|
240
266
|
conn.cache_size = 2000
|
241
|
-
conn.journal_size_limit = [(@options[:size]/2).to_i, @options[:min_size]].min
|
267
|
+
conn.journal_size_limit = [(@options[:size] / 2).to_i, @options[:min_size]].min
|
242
268
|
conn.max_page_count = (@options[:size] / conn.page_size).to_i
|
243
269
|
conn.case_sensitive_like = true
|
244
270
|
end
|
245
|
-
end
|
246
|
-
|
271
|
+
end
|
247
272
|
end
|
@@ -1,28 +1,89 @@
|
|
1
1
|
schema:
|
2
|
-
1:
|
3
|
-
create_table_data: >
|
4
|
-
CREATE
|
2
|
+
1:
|
3
|
+
create_table_data: >
|
4
|
+
CREATE TABLE IF NOT EXISTS data(
|
5
|
+
id TEXT PRIMARY KEY,
|
6
|
+
value ANY,
|
7
|
+
expires_in INTEGER,
|
8
|
+
last_used INTEGER
|
9
|
+
) STRICT;
|
5
10
|
create_expiry_index: >
|
6
|
-
CREATE
|
11
|
+
CREATE INDEX IF NOT EXISTS expiry_index ON data (expires_in);
|
7
12
|
create_last_used_index: >
|
8
|
-
CREATE
|
13
|
+
CREATE INDEX IF NOT EXISTS last_used_index ON data (last_used);
|
9
14
|
|
10
15
|
stmts:
|
11
|
-
pruner:
|
12
|
-
|
13
|
-
|
14
|
-
|
16
|
+
pruner: >
|
17
|
+
DELETE FROM data WHERE expires_in <= unixepoch('now');
|
18
|
+
|
19
|
+
extra_pruner: >
|
20
|
+
DELETE FROM data WHERE id IN (
|
21
|
+
SELECT id
|
22
|
+
FROM data
|
23
|
+
ORDER BY last_used ASC
|
24
|
+
LIMIT (
|
25
|
+
SELECT CAST((count(*) * $1) AS int)
|
26
|
+
FROM data
|
27
|
+
)
|
28
|
+
);
|
29
|
+
|
30
|
+
limited_pruner: >
|
31
|
+
DELETE FROM data
|
32
|
+
WHERE id IN (
|
33
|
+
SELECT id
|
34
|
+
FROM data
|
35
|
+
ORDER BY last_used ASC
|
36
|
+
LIMIT $1
|
37
|
+
);
|
38
|
+
|
39
|
+
toucher: >
|
40
|
+
UPDATE data
|
41
|
+
SET last_used = unixepoch('now')
|
42
|
+
WHERE id = $1;
|
43
|
+
|
15
44
|
setter: >
|
16
|
-
INSERT
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
INSERT
|
26
|
-
|
27
|
-
|
28
|
-
|
45
|
+
INSERT INTO data (id, value, expires_in, last_used)
|
46
|
+
VALUES ($1, $2, unixepoch('now') + $3, unixepoch('now'))
|
47
|
+
ON CONFLICT(id) DO UPDATE
|
48
|
+
SET
|
49
|
+
value = EXCLUDED.value,
|
50
|
+
last_used = EXCLUDED.last_used,
|
51
|
+
expires_in = EXCLUDED.expires_in;
|
52
|
+
|
53
|
+
inserter: >
|
54
|
+
INSERT INTO data (id, value, expires_in, last_used)
|
55
|
+
VALUES ($1, $2, unixepoch('now') + $3, unixepoch('now'))
|
56
|
+
ON CONFLICT(id) DO UPDATE
|
57
|
+
SET
|
58
|
+
value = EXCLUDED.value,
|
59
|
+
last_used = EXCLUDED.last_used,
|
60
|
+
expires_in = EXCLUDED.expires_in
|
61
|
+
WHERE id = $1
|
62
|
+
AND expires_in <= unixepoch('now');
|
63
|
+
|
64
|
+
finder: >
|
65
|
+
SELECT id FROM data WHERE id = $1;
|
66
|
+
|
67
|
+
getter: >
|
68
|
+
SELECT id, value, expires_in FROM data WHERE id = $1 AND expires_in >= unixepoch('now');
|
69
|
+
|
70
|
+
deleter: >
|
71
|
+
delete FROM data WHERE id = $1 RETURNING value;
|
72
|
+
|
73
|
+
incrementer: >
|
74
|
+
INSERT INTO data (id, value, expires_in, last_used)
|
75
|
+
VALUES ($1, $2, unixepoch('now') + $3, unixepoch('now'))
|
76
|
+
ON CONFLICT(id) DO UPDATE
|
77
|
+
SET
|
78
|
+
value = CAST(value AS int) + CAST(EXCLUDED.value AS int),
|
79
|
+
last_used = EXCLUDED.last_used,
|
80
|
+
expires_in = EXCLUDED.expires_in;
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
counter: >
|
85
|
+
SELECT count(*) FROM data;
|
86
|
+
|
87
|
+
sizer: >
|
88
|
+
SELECT size.page_size * count.page_count
|
89
|
+
FROM pragma_page_size() AS size, pragma_page_count() AS count;
|