Skip to content

Commit

Permalink
Merge pull request #109 from luizkowalski/fix-specs
Browse files Browse the repository at this point in the history
Fix test suite and numerous code style and typo fixes
  • Loading branch information
oldmoe authored Apr 20, 2024
2 parents aa663a7 + 1af5c58 commit e76c76b
Show file tree
Hide file tree
Showing 42 changed files with 206 additions and 211 deletions.
4 changes: 3 additions & 1 deletion .standard.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
ignore:
- 'test/**/*':
- Style/GlobalVars
- Style/GlobalVars

ruby_version: 3.0
10 changes: 5 additions & 5 deletions CAVEATS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@

This is a document that lists some caveats that you need to consider when dealing with Litestack and SQLite applications

## Single host (server) only
## Single host (server) only

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.
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.

## Containerization becomes a bit tricky

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.

## Single writer / multi reader
## Single writer / multi reader

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.

## Litestack does not release the GVL

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
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

6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
- [View Diff](https://github.com/oldmoe/litestack/compare/v0.4.1...master)
- Add "sequel" as a development dependency
- Diff links in CHANGELOG (thanks Weston Ganger)
- Fix deamonize type in liteboard (thanks Julian Rubisch)
- Fix daemonize type in liteboard (thanks Julian Rubisch)
- Better Litecache schema (streamlined numeric value support)
- Support for set_multi and get_multi in Litecache (read_multi and write_multi support for Rails Cache store)
- More tests written for Litecache and Rails Litecache store
- Experimenting with removing the Rails LocalCache as it doesn't show enough improvement in perfromance to compensate for the memory overhead
- Experimenting with removing the Rails LocalCache as it doesn't show enough improvement in performance to compensate for the memory overhead
- Switch Litecache to a FIFO eviction model vs LRU (thanks Julian Rubisch and Stephen Margheim)

## [0.4.2] - 2023-11-11
Expand All @@ -20,7 +20,7 @@
- Fix Litesearch tests
- Suppress chatty Litejob exit detector when there are no jobs in flight
- Tidy up the test folder
- [#41](https://github.com/oldmoe/litestack/pull/41) - Fix bug in Litecable where the `connected` event was not getting propogated
- [#41](https://github.com/oldmoe/litestack/pull/41) - Fix bug in Litecable where the `connected` event was not getting propagated
- Add Litemetric and Liteboard info to README.doc
- Fix the testing rake task

Expand Down
10 changes: 5 additions & 5 deletions FILESYSTEMS.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Filesystems & Litestack

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
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 filesystem

## XFS

A very stable and trusted filesystem with excellent performance charactersitcs
A very stable and trusted filesystem with excellent performance characteristics

- Fast reads / writes

Expand All @@ -14,7 +14,7 @@ Another stable and performant filesystem

- Fast reads / writes

## F2FS
## F2FS

Specially built for solid state storage, has an atomic write mode that is supported by SQLite

Expand All @@ -32,7 +32,7 @@ Copy-on-write filesystem with a nifty set of features, very fast snapshotting bu
- fast device cache
- Compression

## Btrfs
## Btrfs

Another CoW filesystem that delivers snapshotting and incremental send/recv at a much granular level.

Expand All @@ -52,4 +52,4 @@ A new CoW filesystem built on the foundations of the bcache module. Improving ra




12 changes: 6 additions & 6 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,25 @@ This is a list of functional/non-functional features that litestack targets to i
- [ ] Automated testing matrix
- [ ] Automated gem builds
- [ ] Litedb improvements
- [ ] ActiveRecord Sqlite3 Adapter compatibility mode
- [ ] ActiveRecord Sqlite3 Adapter compatibility mode
- [ ] Sequel Sqlite3 Adapter compatibility mode
- [ ] Extension bundling/loading
- [ ] Extension bundling/loading
- [ ] Liteiob improvements
- [ ] Persist jobs even during execution
- [ ] Zombie job detection
- [ ] Better process exit handling
- [ ] Better process exit handling
- [ ] Faster job dispatch for the no delay case
- [ ] Database maintenance scripts
- [ ] Online backup
- [ ] Restore
- [ ] Litestream integration
- [ ] CoW filesystems specific backup path
- [ ] CoW filesystem-specific backup path
- [ ] Zero downtime deployment scripts
- [ ] Rails
- [ ] Genereic Rack Applications
- [ ] Generic Rack Applications
- [ ] Litemetric improvements
- [ ] Rails performance module
- [ ] Ruby Memory/GC module
- [ ] Restore generic module
- [ ] Better HTML/CSS
- [ ] Kredis replacement implementation?
- [ ] Kredis replacement implementation?
27 changes: 13 additions & 14 deletions bench/bench_cache_rails.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require "active_support"
require_relative "../lib/litestack"
require_relative "./bench"
require_relative "bench"

cache = ActiveSupport::Cache::Litecache.new

Expand Down Expand Up @@ -32,17 +32,17 @@
end

puts "== Multi Writes =="
bench("litecache multi-writes", count/5) do |i|
idx = i * 5
payload = {}
5.times {|j| payload[keys[idx + j]] = values[idx + j] }
bench("litecache multi-writes", count / 5) do |i|
idx = i * 5
payload = {}
5.times { |j| payload[keys[idx + j]] = values[idx + j] }
cache.write_multi(payload)
end

bench("Redis multi-writes", count/5) do |i|
idx = i * 5
payload = {}
5.times {|j| payload[keys[idx + j]] = values[idx + j] }
bench("Redis multi-writes", count / 5) do |i|
idx = i * 5
payload = {}
5.times { |j| payload[keys[idx + j]] = values[idx + j] }
redis.write_multi(payload)
end

Expand All @@ -56,21 +56,20 @@
end

puts "== Multi Reads =="
bench("litecache multi-reads", count/5) do |i|
bench("litecache multi-reads", count / 5) do |i|
idx = i * 5
payload = []
5.times {|j| payload << random_keys[idx+j]}
5.times { |j| payload << random_keys[idx + j] }
cache.read_multi(*payload)
end

bench("Redis multi-reads", count/5) do |i|
bench("Redis multi-reads", count / 5) do |i|
idx = i * 5
payload = []
5.times {|j| payload << random_keys[idx+j]}
5.times { |j| payload << random_keys[idx + j] }
redis.read_multi(*payload)
end


puts "=========================================================="

keys = []
Expand Down
33 changes: 18 additions & 15 deletions bench/bench_cache_raw.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require "redis"
require "sqlite3"
require_relative "./bench"
require_relative "bench"

# require 'polyphony'
require "async/scheduler"
Expand All @@ -11,7 +11,7 @@
require_relative "../lib/litestack/litecache"
# require 'litestack'

cache = Litecache.new #({path: "../db/cache.db"}) # default settings
cache = Litecache.new # ({path: "../db/cache.db"}) # default settings
redis = Redis.new # default settings

values = []
Expand Down Expand Up @@ -40,17 +40,20 @@
end

puts "== Multi Writes =="
bench("litecache multi-writes", count/5) do |i|
idx = i * 5
payload = {}
5.times {|j| payload[keys[idx + j]] = values[idx + j] }
bench("litecache multi-writes", count / 5) do |i|
idx = i * 5
payload = {}
5.times { |j| payload[keys[idx + j]] = values[idx + j] }
cache.set_multi(payload)
end
end

bench("Redis multi-writes", count/5) do |i|
idx = i * 5
payload = []
5.times {|j| payload << keys[idx + j]; payload << values[idx + j]}
bench("Redis multi-writes", count / 5) do |i|
idx = i * 5
payload = []
5.times { |j|
payload << keys[idx + j]
payload << values[idx + j]
}
redis.mset(*payload)
end

Expand All @@ -64,17 +67,17 @@
end

puts "== Multi Reads =="
bench("litecache multi-reads", count/5) do |i|
bench("litecache multi-reads", count / 5) do |i|
idx = i * 5
payload = []
5.times {|j| payload << random_keys[idx+j]}
5.times { |j| payload << random_keys[idx + j] }
cache.get_multi(*payload)
end

bench("Redis multi-reads", count/5) do |i|
bench("Redis multi-reads", count / 5) do |i|
idx = i * 5
payload = []
5.times {|j| payload << random_keys[idx+j]}
5.times { |j| payload << random_keys[idx + j] }
redis.mget(*payload)
end

Expand Down
19 changes: 9 additions & 10 deletions bin/liteboard
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ DEFAULTS = {
path: Litemetric::DEFAULT_OPTIONS[:path]
}

options = {
options = {
config_path: nil,
path: nil,
deamonize: false,
daemonize: false,
Port: 9292,
Host: 'localhost',
environment: 'production',
Expand All @@ -29,24 +29,24 @@ OptionParser.new do |parser|
parser.on("-s", "--server SERVER", "use SERVER (e.g. puma/falcon/iodine)") { |v| options[:port] = v }
parser.on("-H", "--host HOST", "listen on HOST (default: #{options[:Host]})") { |v| options[:Host] = v }
parser.on("-p", "--port PORT", "use PORT (default: #{options[:Port]})") { |v| options[:Port] = v.to_i rescue options[:Port] }
parser.on("-D", "--daemonize", "run in the background") { |v| options[:daemonize] = true }
parser.on("-E", "--env ENVIRONMENT", "which environment to use (default: #{options[:environment]})") { |v| options[:environment] = v }
parser.on("-q", "--quiet", "turn off logging") { |v| options[:quiet] = true }
parser.on("-D", "--daemonize", "run in the background") { |v| options[:daemonize] = true }
parser.on("-E", "--env ENVIRONMENT", "which environment to use (default: #{options[:environment]})") { |v| options[:environment] = v }
parser.on("-q", "--quiet", "turn off logging") { |v| options[:quiet] = true }
parser.on("-h", "--help", "print this message") do
puts parser
exit
end
end.parse!

def check_database(path)
unless File.exist?(path)
unless File.exist?(path)
puts "liteboard: missing database file, please ensure the db path is correct"
puts "liteboard: exiting"
exit
end
end

check_database(options[:path]) if options[:path]
check_database(options[:path]) if options[:path]

# if there is a config file then we need to check it
config = nil
Expand All @@ -57,7 +57,7 @@ if options[:config_path]
puts "liteboard: missing or bad config file, please ensure the config file path is correct"
puts "liteboard: exiting"
exit
end
end
else # no config path! use the default
config = YAML.load(ERB.new(File.read(DEFAULTS[:config_path])).result) rescue nil
end
Expand All @@ -66,13 +66,12 @@ if config
if options[:path].nil?
path = config['path'] || config[options[:environment]]['path']
options[:path] = path
end
end
end

# if still no path we assume a default db path
options[:path] = DEFAULTS[:path] if options[:path].nil?


# check the validity of the path before starting the server
check_database(options[:path])
Litemetric.options = options
Expand Down
24 changes: 12 additions & 12 deletions lib/active_support/cache/litecache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
module ActiveSupport
module Cache
class Litecache < Store
#prepend Strategy::LocalCache
# prepend Strategy::LocalCache

def self.supports_cache_versioning?
true
Expand All @@ -27,9 +27,9 @@ def increment(key, amount = 1, options = nil)
# todo: fix me
# this is currently a hack to avoid dealing with Rails cache encoding and decoding
# and it can result in a race condition as it stands
# @cache.transaction(:immediate) do
# currently transactions are not compatible with acquiring connections
# this needs fixing by storing the connection to the context once acquired
# @cache.transaction(:immediate) do
# currently transactions are not compatible with acquiring connections
# this needs fixing by storing the connection to the context once acquired
if (value = read(key, options))
value = value.to_i + amount
write(key, value, options)
Expand All @@ -52,7 +52,7 @@ def cleanup(limit = nil, time = nil)
@cache.prune(limit)
end

def clear(options=nil)
def clear(options = nil)
@cache.clear
end

Expand All @@ -74,11 +74,11 @@ def stats

private

def serialize_entrys(entry, **options)
def serialize_entries(entry, **options)
Marshal.dump(entry)
end

def deserialize_entrys(entry)
def deserialize_entries(entry)
Marshal.load(entry.to_s)
end

Expand All @@ -91,23 +91,23 @@ def read_multi_entries(names, **options)
results = {}
return results if names == []
rs = @cache.get_multi(*names.flatten)
rs.each_pair{|k, v| results[k] = deserialize_entry(v).value }
rs.each_pair { |k, v| results[k] = deserialize_entry(v).value }
results
end

# Write an entry to the cache.
def write_entry(key, entry, **options)
write_serialized_entry(key, serialize_entry(entry, **options), **options)
end

def write_multi_entries(entries, **options)
return if entries.empty?
entries.each_pair {|k,v| entries[k] = serialize_entry(v, **options)}
entries.each_pair { |k, v| entries[k] = serialize_entry(v, **options) }
expires_in = options[:expires_in].to_i if options[:expires_in]
if options[:race_condition_ttl] && expires_in > 0 && !options[:raw]
expires_in += 5.minutes
end
@cache.set_multi(entries, expires_in)
@cache.set_multi(entries, expires_in)
end

def write_serialized_entry(key, payload, **options)
Expand Down
Loading

0 comments on commit e76c76b

Please sign in to comment.