litestack 0.3.0 → 0.4.2
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 +34 -7
- data/CHANGELOG.md +21 -0
- data/Gemfile +1 -5
- data/Gemfile.lock +92 -0
- data/README.md +120 -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 +10 -13
- data/bench/bench_cache_raw.rb +17 -22
- data/bench/bench_jobs_rails.rb +19 -13
- 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/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 +65 -75
- data/lib/active_support/cache/litecache.rb +38 -41
- data/lib/generators/litestack/install/install_generator.rb +3 -3
- data/lib/generators/litestack/install/templates/database.yml +7 -1
- data/lib/litestack/liteboard/liteboard.rb +269 -149
- data/lib/litestack/litecable.rb +44 -40
- data/lib/litestack/litecable.sql.yml +22 -11
- data/lib/litestack/litecache.rb +80 -89
- data/lib/litestack/litecache.sql.yml +81 -22
- data/lib/litestack/litecache.yml +1 -1
- data/lib/litestack/litedb.rb +39 -38
- data/lib/litestack/litejob.rb +31 -31
- data/lib/litestack/litejobqueue.rb +107 -106
- data/lib/litestack/litemetric.rb +83 -95
- 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 +84 -0
- data/lib/litestack/litesearch/index.rb +260 -0
- data/lib/litestack/litesearch/model.rb +179 -0
- data/lib/litestack/litesearch/schema.rb +190 -0
- data/lib/litestack/litesearch/schema_adapters/backed_adapter.rb +143 -0
- data/lib/litestack/litesearch/schema_adapters/basic_adapter.rb +137 -0
- data/lib/litestack/litesearch/schema_adapters/contentless_adapter.rb +14 -0
- data/lib/litestack/litesearch/schema_adapters/standalone_adapter.rb +31 -0
- data/lib/litestack/litesearch/schema_adapters.rb +4 -0
- data/lib/litestack/litesearch.rb +34 -0
- data/lib/litestack/litesupport.rb +85 -186
- data/lib/litestack/railtie.rb +1 -1
- data/lib/litestack/version.rb +2 -2
- data/lib/litestack.rb +7 -4
- data/lib/railties/rails/commands/dbconsole.rb +11 -15
- data/lib/sequel/adapters/litedb.rb +18 -22
- 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 +112 -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
|
@@ -80,49 +85,48 @@ class Litecable
|
|
80
85
|
end
|
81
86
|
|
82
87
|
def create_broadcaster
|
83
|
-
|
84
|
-
while @running
|
88
|
+
Litescheduler.spawn do
|
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
|
-
|
101
|
-
while @running
|
105
|
+
Litescheduler.spawn do
|
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
|
-
|
110
|
-
while @running
|
114
|
+
Litescheduler.spawn do
|
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,31 +1,30 @@
|
|
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
29
|
# min_size: 32 * 1024 -> 32MB
|
31
30
|
# return_full_record: false -> only return the payload
|
@@ -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
|
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
|
43
42
|
sleep_interval: 1, # 1 second
|
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,57 +60,53 @@ 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]
|
65
|
+
options[:size] = DEFAULT_OPTIONS[:min_size] if options[:size] && options[:size] < DEFAULT_OPTIONS[:min_size]
|
67
66
|
@last_visited = {}
|
68
|
-
init(options)
|
67
|
+
init(options)
|
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 = @options[:expires_in] if expires_in.nil?
|
74
|
+
expires_in = @options[:expires_in] if expires_in.nil? || expires_in.zero?
|
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
|
-
|
89
|
-
# add a key, value pair to the cache, but only if the key doesn't exist, with an optional expiry value (number of seconds)
|
85
|
+
|
86
|
+
# 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
87
|
def set_unless_exists(key, value, expires_in = nil)
|
91
88
|
key = key.to_s
|
92
|
-
expires_in = @options[:expires_in] if expires_in.nil?
|
89
|
+
expires_in = @options[:expires_in] if expires_in.nil? || expires_in.zero?
|
93
90
|
changes = 0
|
94
91
|
@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
|
92
|
+
cache.transaction(:immediate) do
|
93
|
+
cache.stmts[:inserter].execute!(key, value, expires_in)
|
94
|
+
changes = cache.changes
|
105
95
|
end
|
96
|
+
capture(:set, key)
|
97
|
+
rescue SQLite3::FullException
|
98
|
+
cache.stmts[:extra_pruner].execute!(0.2)
|
99
|
+
cache.execute("vacuum")
|
100
|
+
retry
|
106
101
|
end
|
107
|
-
|
102
|
+
changes > 0
|
108
103
|
end
|
109
|
-
|
104
|
+
|
110
105
|
# get a value by its key
|
111
106
|
# if the key doesn't exist or it is expired then null will be returned
|
112
107
|
def get(key)
|
113
108
|
key = key.to_s
|
114
|
-
if record = @conn.acquire{|cache| cache.stmts[:getter].execute!(key)[0] }
|
109
|
+
if (record = @conn.acquire { |cache| cache.stmts[:getter].execute!(key)[0] })
|
115
110
|
@last_visited[key] = true
|
116
111
|
capture(:get, key, 1)
|
117
112
|
return record[1]
|
@@ -119,7 +114,7 @@ class Litecache
|
|
119
114
|
capture(:get, key, 0)
|
120
115
|
nil
|
121
116
|
end
|
122
|
-
|
117
|
+
|
123
118
|
# delete a key, value pair from the cache
|
124
119
|
def delete(key)
|
125
120
|
changes = 0
|
@@ -127,59 +122,59 @@ class Litecache
|
|
127
122
|
cache.stmts[:deleter].execute!(key)
|
128
123
|
changes = cache.changes
|
129
124
|
end
|
130
|
-
|
125
|
+
changes > 0
|
131
126
|
end
|
132
|
-
|
127
|
+
|
133
128
|
# increment an integer value by amount, optionally add an expiry value (in seconds)
|
134
129
|
def increment(key, amount, expires_in = nil)
|
135
|
-
expires_in
|
136
|
-
@conn.acquire{|cache| cache.stmts[:incrementer].execute!(key.to_s, amount, expires_in) }
|
130
|
+
expires_in ||= @expires_in
|
131
|
+
@conn.acquire { |cache| cache.stmts[:incrementer].execute!(key.to_s, amount, expires_in) }
|
137
132
|
end
|
138
|
-
|
133
|
+
|
139
134
|
# decrement an integer value by amount, optionally add an expiry value (in seconds)
|
140
135
|
def decrement(key, amount, expires_in = nil)
|
141
136
|
increment(key, -amount, expires_in)
|
142
137
|
end
|
143
|
-
|
138
|
+
|
144
139
|
# 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)
|
140
|
+
def prune(limit = nil)
|
146
141
|
@conn.acquire do |cache|
|
147
|
-
if limit
|
142
|
+
if limit&.is_a? Integer
|
148
143
|
cache.stmts[:limited_pruner].execute!(limit)
|
149
|
-
elsif limit
|
144
|
+
elsif limit&.is_a? Float
|
150
145
|
cache.stmts[:extra_pruner].execute!(limit)
|
151
146
|
else
|
152
|
-
cache.stmts[:pruner].execute!
|
147
|
+
cache.stmts[:pruner].execute!
|
153
148
|
end
|
154
149
|
end
|
155
150
|
end
|
156
|
-
|
151
|
+
|
157
152
|
# return the number of key, value pairs in the cache
|
158
153
|
def count
|
159
154
|
run_stmt(:counter)[0][0]
|
160
155
|
end
|
161
|
-
|
156
|
+
|
162
157
|
# return the actual size of the cache file
|
163
|
-
#def size
|
158
|
+
# def size
|
164
159
|
# run_stmt(:sizer)[0][0]
|
165
|
-
#end
|
166
|
-
|
160
|
+
# end
|
161
|
+
|
167
162
|
# delete all key, value pairs in the cache
|
168
163
|
def clear
|
169
164
|
run_sql("delete FROM data")
|
170
165
|
end
|
171
|
-
|
166
|
+
|
172
167
|
# close the connection to the cache file
|
173
168
|
def close
|
174
169
|
@running = false
|
175
170
|
super
|
176
171
|
end
|
177
|
-
|
172
|
+
|
178
173
|
# return the maximum size of the cache
|
179
174
|
def max_size
|
180
175
|
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
176
|
end
|
182
|
-
|
177
|
+
|
183
178
|
def snapshot
|
184
179
|
{
|
185
180
|
summary: {
|
@@ -193,10 +188,9 @@ class Litecache
|
|
193
188
|
}
|
194
189
|
end
|
195
190
|
|
196
|
-
|
197
191
|
# low level access to SQLite transactions, use with caution
|
198
|
-
def transaction(mode, acquire=true)
|
199
|
-
return cache.transaction(mode){yield} unless acquire
|
192
|
+
def transaction(mode, acquire = true)
|
193
|
+
return cache.transaction(mode) { yield } unless acquire
|
200
194
|
@conn.acquire do |cache|
|
201
195
|
cache.transaction(mode) do
|
202
196
|
yield
|
@@ -204,44 +198,41 @@ class Litecache
|
|
204
198
|
end
|
205
199
|
end
|
206
200
|
|
207
|
-
private
|
208
|
-
|
201
|
+
private
|
202
|
+
|
209
203
|
def setup
|
210
204
|
super # create connection
|
211
205
|
@bgthread = spawn_worker # create backgroud pruner thread
|
212
206
|
end
|
213
|
-
|
207
|
+
|
214
208
|
def spawn_worker
|
215
|
-
|
209
|
+
Litescheduler.spawn do
|
216
210
|
while @running
|
217
211
|
@conn.acquire do |cache|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
cache.stmts[:toucher].execute!(k) || true
|
222
|
-
end
|
223
|
-
cache.stmts[:pruner].execute!
|
212
|
+
cache.transaction(:immediate) do
|
213
|
+
@last_visited.delete_if do |k| # there is a race condition here, but not a serious one
|
214
|
+
cache.stmts[:toucher].execute!(k) || true
|
224
215
|
end
|
225
|
-
|
226
|
-
retry
|
227
|
-
rescue SQLite3::FullException
|
228
|
-
cache.stmts[:extra_pruner].execute!(0.2)
|
229
|
-
rescue Exception
|
230
|
-
# database is closed
|
216
|
+
cache.stmts[:pruner].execute!
|
231
217
|
end
|
218
|
+
rescue SQLite3::BusyException
|
219
|
+
retry
|
220
|
+
rescue SQLite3::FullException
|
221
|
+
cache.stmts[:extra_pruner].execute!(0.2)
|
222
|
+
rescue Exception # standard:disable Lint/RescueException
|
223
|
+
# database is closed
|
232
224
|
end
|
233
225
|
sleep @options[:sleep_interval]
|
234
226
|
end
|
235
227
|
end
|
236
228
|
end
|
237
|
-
|
229
|
+
|
238
230
|
def create_connection
|
239
231
|
super("#{__dir__}/litecache.sql.yml") do |conn|
|
240
232
|
conn.cache_size = 2000
|
241
|
-
conn.journal_size_limit = [(@options[:size]/2).to_i, @options[:min_size]].min
|
233
|
+
conn.journal_size_limit = [(@options[:size] / 2).to_i, @options[:min_size]].min
|
242
234
|
conn.max_page_count = (@options[:size] / conn.page_size).to_i
|
243
235
|
conn.case_sensitive_like = true
|
244
236
|
end
|
245
|
-
end
|
246
|
-
|
237
|
+
end
|
247
238
|
end
|
@@ -1,28 +1,87 @@
|
|
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 TEXT,
|
7
|
+
expires_in INTEGER,
|
8
|
+
last_used INTEGER
|
9
|
+
);
|
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 <= $1;
|
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;
|
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
|
+
counter: >
|
83
|
+
SELECT count(*) FROM data;
|
84
|
+
|
85
|
+
sizer: >
|
86
|
+
SELECT size.page_size * count.page_count
|
87
|
+
FROM pragma_page_size() AS size, pragma_page_count() AS count;
|