Skip to content

Commit 8d37336

Browse files
authored
Merge branch 'master' into fix-testing
2 parents 55e4a87 + 074d83b commit 8d37336

14 files changed

+264
-45
lines changed

BENCHMARKS.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,16 +125,16 @@ On average, Litecable is quite faster than the Redis based version and offers be
125125

126126
> ![litesearch](https://github.com/oldmoe/litestack/blob/master/assets/litesearch_logo_teal.png?raw=true)
127127
128-
Litesearch was benchmarked against Mielsearch, both using their respective ActiveRecord integrations. Mielsearch was running on the same machine as the benchmark script and was using the default configuration options. The dataset used for testing was the infamous Enron email corpus. Redisearch was not benchmarked due to the clients being not Rails 7.1 compatible (yet), will probably bench Redisearch when they are.
128+
Litesearch was benchmarked against Meilisearch, both using their respective ActiveRecord integrations. Meilisearch was running on the same machine as the benchmark script and was using the default configuration options. The dataset used for testing was the infamous Enron email corpus. Redisearch was not benchmarked due to the clients being not Rails 7.1 compatible (yet), will probably bench Redisearch when they are.
129129

130130
### Building the index
131131

132-
||MieliSearch|Litesearch|
132+
||Meilisearch|Litesearch|
133133
|-:|-:|-:|
134134
|Time to insert 10K docs|265.42 seconds|29.06 seconds|
135135
|Inserted docs/second|38|344|
136136
|Search latency (3 terms)|7.51 ms| 0.051ms|
137137
|Searches/second|133|19608|
138138
|Index rebuild|0.822|0.626|
139139

140-
We only limited the test to 10K documents becuause MieliSearch was taking a long time to index, so we decided to stop at a reasonable sample size. The search numbers for litesearch were double checked, event against a 100K document set and they remained virtually the same. It is clear that litesearch is much faster than MieliSearch in both indexing and searching, this could be partially attributed to litesearch being a simpler text search engine, but still, the difference is huge! For rebuilding the index though, Litesearch is not that much faster than Mielisearch.
140+
We only limited the test to 10K documents becuause Meilisearch was taking a long time to index, so we decided to stop at a reasonable sample size. The search numbers for litesearch were double checked, event against a 100K document set and they remained virtually the same. It is clear that litesearch is much faster than Meilisearch in both indexing and searching, this could be partially attributed to litesearch being a simpler text search engine, but still, the difference is huge! For rebuilding the index though, Litesearch is not that much faster than Meilisearch.

CAVEATS.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# CAVEATS
2+
3+
This is a document that lists some caveats that you need to consider when dealing with Litestack and SQLite applications
4+
5+
## Single host (server) only
6+
7+
By design, SQLite can be accessed only (in a performant and safe way) from a single host machine. That means that you cannot have things like auto-scaling of app servers since there is but a single one of them. This app server can be a huge one, with many processes, but only one app server at a time.
8+
9+
## Containerization becomes a bit tricky
10+
11+
Container technology was originally designed for stateless applications. Based on this, one of the core aspects of containers is that they are immutable, deploying an app to container means destroying one container and creating another. With statefull applications, like database servers and similarly SQLite based applications, this will potentially mean a down time will be perceived, which in some approaches is mitigate but not perfectly (yet). Expect some quirks when dealing with SQLite and containers.
12+
13+
## Single writer / multi reader
14+
15+
In its default configuration, Litestack allows one writer at a time to write to the database file, while simultaneously allowing any number of readers to access the database. Generally SQLite is pretty fast in writes, but you should be aware that a long running write operation will prevent any other writers from proceeding, for example creating an index on a very large table will stop other writers until the index creation is finished. There is no concurrent index creation facility in SQLite, yet.
16+
17+
## Litestack does not release the GVL
18+
19+
This also applies to the Ruby SQLite3 gem, when it is performing a query, it will not allow any other threads in the Ruby process to resume until it returns. Care should be taken when writing low latency multi-threaded applications with Litestack, it is generally recommended to use fibers over threads in that case, since everything is serialized anyway it makes sense to avoid the ovreheads of mult-threaded environments
20+

FILESYSTEMS.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Filesystems & Litestack
2+
3+
In the containerized world we live in, many layers of the hardware/software stacks are far abstracted that we no longer know they exist. For the filesystem enthusiasts out there this is a quick overview of how Litestack (and hence SQLite) can benefit from different filesytems
4+
5+
## XFS
6+
7+
A very stable and trusted filesystem with excellent performance charactersitcs
8+
9+
- Fast reads / writes
10+
11+
## EXT4
12+
13+
Another stable and performant filesystem
14+
15+
- Fast reads / writes
16+
17+
## F2FS
18+
19+
Specially built for solid state storage, has an atomic write mode that is supported by SQLite
20+
21+
- Fast reads
22+
- Reasonably fast durable writes in synchronous mode
23+
- Compression (not very useful)
24+
25+
## ZFS
26+
27+
Copy-on-write filesystem with a nifty set of features, very fast snapshotting but only for the FS as a whole, can send incremental snapshots to another host for backup. Naturally a lot more pages are written on write operations thus it is a bit slower in writes.
28+
29+
- Fast reads
30+
- Slower writes
31+
- FS Snapshotting with send/recv
32+
- fast device cache
33+
- Compression
34+
35+
## Btrfs
36+
37+
Another CoW filesystem that delivers snapshotting and incremental send/recv at a much granular level.
38+
39+
- Fast reads
40+
- Slower writes
41+
- Fast copies (free backups)
42+
- Sub volume Snapshotting with send/recv
43+
- Compression
44+
45+
## Bcachefs
46+
47+
A new CoW filesystem built on the foundations of the bcache module. Improving rapidly but still lacks a few of the common CoW fs features
48+
49+
- Fast reads
50+
- Slower writes
51+
- fast device cache
52+
53+
54+
55+

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ litedb is a wrapper around SQLite3, offering a better default configuration that
5959

6060
#### Direct litedb usage
6161

62-
litedb can be used exactly as the SQLite3 gem, since litedb iherits from SQLite3
62+
litedb can be used exactly as the SQLite3 gem, since litedb inherits from SQLite3
6363

6464
```ruby
6565
require 'litestack'
@@ -209,7 +209,7 @@ idx.search('kamal')
209209
idx.search('subject: awa')
210210
```
211211

212-
Litesearch integrates tightly with ActiveRecord ans Sequel, here are integration examples
212+
Litesearch integrates tightly with ActiveRecord and Sequel, here are integration examples
213213

214214
#### ActiveRecord
215215

bin/liteboard

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
require 'optparse'
55
require 'erb'
66
require 'yaml'
7+
require 'rackup'
78
require_relative '../lib/litestack/liteboard/liteboard'
89
DEFAULTS = {
910
config_path: Litemetric::DEFAULT_OPTIONS[:config_path],
@@ -17,7 +18,7 @@ options = {
1718
Port: 9292,
1819
Host: 'localhost',
1920
environment: 'production',
20-
pid: 'tmp/pids/liteboard.pid',
21+
pid: './liteboard.pid',
2122
quiet: false
2223
}
2324

@@ -58,7 +59,7 @@ if options[:config_path]
5859
exit
5960
end
6061
else # no config path! use the default
61-
config = Yaml.load_file(DEFAULTS[:config_path]) rescue nil
62+
config = YAML.load(ERB.new(File.read(DEFAULTS[:config_path])).result) rescue nil
6263
end
6364

6465
if config
@@ -81,4 +82,4 @@ options[:app] = Liteboard.app
8182
require_relative '../lib/litestack'
8283
puts "Starting Liteboard version #{Litestack::VERSION}"
8384

84-
Rack::Server.start(options)
85+
Rackup::Server.start(options)

lib/active_support/cache/litecache.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require "delegate"
2+
require "active_support"
23
require "active_support/core_ext/enumerable"
34
require "active_support/core_ext/array/extract_options"
45
require "active_support/core_ext/numeric/time"
@@ -73,6 +74,14 @@ def stats
7374

7475
private
7576

77+
def serialize_entrys(entry, **options)
78+
Marshal.dump(entry)
79+
end
80+
81+
def deserialize_entrys(entry)
82+
Marshal.load(entry.to_s)
83+
end
84+
7685
# Read an entry from the cache.
7786
def read_entry(key, **options)
7887
deserialize_entry(@cache.get(key))
@@ -94,15 +103,15 @@ def write_entry(key, entry, **options)
94103
def write_multi_entries(entries, **options)
95104
return if entries.empty?
96105
entries.each_pair {|k,v| entries[k] = serialize_entry(v, **options)}
97-
expires_in = options[:expires_in].to_i
106+
expires_in = options[:expires_in].to_i if options[:expires_in]
98107
if options[:race_condition_ttl] && expires_in > 0 && !options[:raw]
99108
expires_in += 5.minutes
100109
end
101110
@cache.set_multi(entries, expires_in)
102111
end
103112

104113
def write_serialized_entry(key, payload, **options)
105-
expires_in = options[:expires_in].to_i
114+
expires_in = options[:expires_in].to_i if options[:expires_in]
106115
if options[:race_condition_ttl] && expires_in > 0 && !options[:raw]
107116
expires_in += 5.minutes
108117
end

lib/litestack/liteboard/liteboard.rb

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# frozen_string_literal: true
22

3-
require "hanami/router"
3+
require "rack"
44
require "tilt"
55
require "erubi"
66

@@ -11,37 +11,32 @@ class Liteboard
1111
@@resolutions = {"minute" => [300, 12], "hour" => [3600, 24], "day" => [3600 * 24, 7], "week" => [3600 * 24 * 7, 53], "year" => [3600 * 24 * 365, 100]}
1212
@@res_mapping = {"hour" => "minute", "day" => "hour", "week" => "day", "year" => "week"}
1313
@@templates = {}
14-
@@app = Hanami::Router.new do
15-
get "/", to: ->(env) do
14+
@@app = proc do |env|
15+
case path = env["PATH_INFO"]
16+
when "/"
1617
Liteboard.new(env).call(:index)
17-
end
18-
19-
get "/topics/Litejob", to: ->(env) do
18+
when "/topics/Litejob"
2019
Liteboard.new(env).call(:litejob)
21-
end
22-
23-
get "/topics/Litecache", to: ->(env) do
20+
when "/topics/Litecache"
2421
Liteboard.new(env).call(:litecache)
25-
end
26-
27-
get "/topics/Litedb", to: ->(env) do
22+
when "/topics/Litedb"
2823
Liteboard.new(env).call(:litedb)
29-
end
30-
31-
get "/topics/Litecable", to: ->(env) do
24+
when "/topics/Litecable"
3225
Liteboard.new(env).call(:litecable)
3326
end
27+
3428
end
3529

3630
def initialize(env)
3731
@env = env
38-
@params = @env["router.params"]
32+
@req = Rack::Request.new(@env)
33+
@params = @req.params
3934
@running = true
4035
@lm = Litemetric.instance
4136
end
4237

4338
def params(key)
44-
URI.decode_uri_component(@params[key].to_s)
39+
URI.decode_uri_component(@params[key.to_s].to_s)
4540
end
4641

4742
def call(method)
@@ -227,9 +222,9 @@ def litejob
227222
@topic = "Litejob"
228223
@events = @lm.events_summaries(@topic, @resolution, @order, @dir, @search, @step * @count)
229224
@events.each do |event|
230-
data_points = @lm.event_data_points(@step, @count, @resolution, @topic, event["name"])
225+
data_points = @lm.event_data_points(@step, @count, @resolution, @topic, event[:name])
231226
event["counts"] = data_points.collect { |r| [r["rtime"], r["rcount"] || 0] }
232-
event["values"] = data_points.collect { |r| [r["rtime"], r["rtotal"] || 0] }
227+
event["values"] = data_points.collect { |r| [r["rtime"], (r["rtotal"] || 0.0)] }
233228
end
234229
@snapshot = read_snapshot(@topic)
235230
@size = begin
@@ -391,6 +386,7 @@ def round(float)
391386
end
392387

393388
def format(float)
389+
float = float.round(3)
394390
string = float.to_s
395391
whole, decimal = string.split(".")
396392
whole = whole.chars.reverse.each_slice(3).map(&:join).join(",").reverse

lib/litestack/litecache.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,13 +162,13 @@ def delete(key)
162162
end
163163

164164
# increment an integer value by amount, optionally add an expiry value (in seconds)
165-
def increment(key, amount, expires_in = nil)
165+
def increment(key, amount=1, expires_in = nil)
166166
expires_in ||= @expires_in
167167
@conn.acquire { |cache| cache.stmts[:incrementer].execute!(key.to_s, amount, expires_in) }
168168
end
169169

170170
# decrement an integer value by amount, optionally add an expiry value (in seconds)
171-
def decrement(key, amount, expires_in = nil)
171+
def decrement(key, amount=1, expires_in = nil)
172172
increment(key, -amount, expires_in)
173173
end
174174

lib/litestack/litejobqueue.rb

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -141,15 +141,16 @@ def stop
141141

142142
def exit_callback
143143
@running = false # stop all workers
144-
return unless @jobs_in_flight > 0
145-
puts "--- Litejob detected an exit, cleaning up"
146-
index = 0
147-
while @jobs_in_flight > 0 && index < 30 # 3 seconds grace period for jobs to finish
148-
puts "--- Waiting for #{@jobs_in_flight} jobs to finish"
149-
sleep 0.1
150-
index += 1
144+
if @jobs_in_flight > 0
145+
puts "--- Litejob detected an exit, cleaning up"
146+
index = 0
147+
while @jobs_in_flight > 0 && index < 30 # 3 seconds grace period for jobs to finish
148+
puts "--- Waiting for #{@jobs_in_flight} jobs to finish"
149+
sleep 0.1
150+
index += 1
151+
end
152+
puts " --- Exiting with #{@jobs_in_flight} jobs in flight"
151153
end
152-
puts " --- Exiting with #{@jobs_in_flight} jobs in flight"
153154
end
154155

155156
def setup
@@ -179,6 +180,7 @@ def schedule(spawn = false, &block)
179180

180181
# create a worker according to environment
181182
def create_worker
183+
return if defined?(Rails) && !defined?(Rails::Server)
182184
Litescheduler.spawn do
183185
worker_sleep_index = 0
184186
while @running

lib/litestack/litemetric.rb

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,11 @@ def run_stmt_hash(stmt, *args)
148148
end
149149

150150
def exit_callback
151-
return unless @collector.count > 0
152-
warn "--- Litemetric detected an exit, flushing metrics"
153151
@running = false
154-
@collector.flush
152+
if @collector.count > 0
153+
warn "--- Litemetric detected an exit, flushing metrics"
154+
@collector.flush
155+
end
155156
end
156157

157158
def setup
@@ -203,8 +204,8 @@ def collect_metrics
203204
def create_snapshotter
204205
Litescheduler.spawn do
205206
while @running
206-
sleep @litemetric.options[:snapshot_interval]
207207
capture_snapshot
208+
sleep @litemetric.options[:snapshot_interval]
208209
end
209210
end
210211
end

lib/litestack/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# frozen_string_literal: true
22

33
module Litestack
4-
VERSION = "0.4.3"
4+
VERSION = "0.4.4"
55
end

litestack.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Gem::Specification.new do |spec|
3333
spec.add_dependency "sqlite3"
3434
spec.add_dependency "oj"
3535
spec.add_dependency "rack"
36-
spec.add_dependency "hanami-router"
36+
spec.add_dependency "rackup"
3737
spec.add_dependency "tilt"
3838
spec.add_dependency "erubi"
3939

0 commit comments

Comments
 (0)