litestack 0.4.1 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.standard.yml +3 -0
  3. data/BENCHMARKS.md +23 -7
  4. data/CHANGELOG.md +35 -0
  5. data/Gemfile +1 -7
  6. data/README.md +124 -6
  7. data/ROADMAP.md +45 -0
  8. data/Rakefile +3 -1
  9. data/WHYLITESTACK.md +1 -1
  10. data/assets/litecache_metrics.png +0 -0
  11. data/assets/litedb_metrics.png +0 -0
  12. data/assets/litemetric_logo_teal.png +0 -0
  13. data/assets/litesearch_logo_teal.png +0 -0
  14. data/bench/bench.rb +17 -10
  15. data/bench/bench_cache_rails.rb +45 -14
  16. data/bench/bench_cache_raw.rb +44 -28
  17. data/bench/bench_jobs_rails.rb +18 -12
  18. data/bench/bench_jobs_raw.rb +17 -10
  19. data/bench/bench_queue.rb +4 -6
  20. data/bench/rails_job.rb +5 -7
  21. data/bench/skjob.rb +4 -4
  22. data/bench/uljob.rb +6 -6
  23. data/bin/liteboard +2 -1
  24. data/lib/action_cable/subscription_adapter/litecable.rb +5 -8
  25. data/lib/active_job/queue_adapters/litejob_adapter.rb +6 -8
  26. data/lib/active_record/connection_adapters/litedb_adapter.rb +72 -84
  27. data/lib/active_support/cache/litecache.rb +61 -41
  28. data/lib/generators/litestack/install/install_generator.rb +3 -3
  29. data/lib/generators/litestack/install/templates/cable.yml +0 -3
  30. data/lib/generators/litestack/install/templates/database.yml +7 -1
  31. data/lib/litestack/liteboard/liteboard.rb +269 -149
  32. data/lib/litestack/litecable.rb +41 -37
  33. data/lib/litestack/litecable.sql.yml +22 -11
  34. data/lib/litestack/litecache.rb +118 -93
  35. data/lib/litestack/litecache.sql.yml +83 -22
  36. data/lib/litestack/litecache.yml +1 -1
  37. data/lib/litestack/litedb.rb +35 -40
  38. data/lib/litestack/litejob.rb +30 -29
  39. data/lib/litestack/litejobqueue.rb +63 -65
  40. data/lib/litestack/litemetric.rb +80 -92
  41. data/lib/litestack/litemetric.sql.yml +244 -234
  42. data/lib/litestack/litemetric_collector.sql.yml +38 -41
  43. data/lib/litestack/litequeue.rb +39 -41
  44. data/lib/litestack/litequeue.sql.yml +39 -31
  45. data/lib/litestack/litescheduler.rb +24 -18
  46. data/lib/litestack/litesearch/index.rb +93 -63
  47. data/lib/litestack/litesearch/model.rb +66 -65
  48. data/lib/litestack/litesearch/schema.rb +53 -56
  49. data/lib/litestack/litesearch/schema_adapters/backed_adapter.rb +46 -50
  50. data/lib/litestack/litesearch/schema_adapters/basic_adapter.rb +44 -35
  51. data/lib/litestack/litesearch/schema_adapters/contentless_adapter.rb +3 -6
  52. data/lib/litestack/litesearch/schema_adapters/standalone_adapter.rb +7 -9
  53. data/lib/litestack/litesearch/schema_adapters.rb +4 -9
  54. data/lib/litestack/litesearch.rb +6 -9
  55. data/lib/litestack/litesupport.rb +78 -87
  56. data/lib/litestack/railtie.rb +1 -1
  57. data/lib/litestack/version.rb +2 -2
  58. data/lib/litestack.rb +6 -4
  59. data/lib/railties/rails/commands/dbconsole.rb +16 -20
  60. data/lib/sequel/adapters/litedb.rb +16 -21
  61. data/lib/sequel/adapters/shared/litedb.rb +168 -168
  62. data/scripts/build_metrics.rb +91 -0
  63. data/scripts/test_cable.rb +30 -0
  64. data/scripts/test_job_retry.rb +33 -0
  65. data/scripts/test_metrics.rb +60 -0
  66. data/template.rb +2 -2
  67. metadata +115 -7
@@ -1,56 +1,53 @@
1
1
  schema:
2
2
  1:
3
-
4
3
  create_events: >
5
4
  CREATE TABLE IF NOT EXISTS local_events(
6
5
  topic TEXT NOT NULL,
7
- name TEXT DEFAULT('___') NOT NULL ON CONFLICT REPLACE,
8
- key TEXT DEFAULT('___') NOT NULL ON CONFLICT REPLACE,
9
- count INTEGER DEFAULT(0) NOT NULL ON CONFLICT REPLACE,
10
- value REAL,
11
- minimum REAL,
12
- maximum REAL,
13
- created_at INTEGER DEFAULT((unixepoch()/300*300)) NOT NULL ON CONFLICT REPLACE,
14
- resolution TEXT DEFAULT('minute') NOT NULL,
6
+ name TEXT DEFAULT('___') NOT NULL ON CONFLICT REPLACE,
7
+ key TEXT DEFAULT('___') NOT NULL ON CONFLICT REPLACE,
8
+ count INTEGER DEFAULT(0) NOT NULL ON CONFLICT REPLACE,
9
+ value REAL,
10
+ minimum REAL,
11
+ maximum REAL,
12
+ created_at INTEGER DEFAULT((unixepoch()/300*300)) NOT NULL ON CONFLICT REPLACE,
13
+ resolution TEXT DEFAULT('minute') NOT NULL,
15
14
  PRIMARY KEY(resolution, created_at, topic, name, key)
16
- ) STRICT;
15
+ );
17
16
 
18
17
  stmts:
19
-
20
18
  capture_event: >
21
- INSERT INTO local_events(topic, name, key, created_at, count, value, minimum, maximum)
22
- VALUES
23
- (?1, ?2, ?3, ?4, ?5, ?6, ?6, ?6),
19
+ INSERT INTO local_events(topic, name, key, created_at, count, value, minimum, maximum)
20
+ VALUES
21
+ (?1, ?2, ?3, ?4, ?5, ?6, ?6, ?6),
24
22
  (?1, ?2, '___', ?4, ?5, ?6, ?6, ?6),
25
- (?1, '___', '___', ?4, ?5, ?6, ?6, ?6)
26
- ON CONFLICT DO UPDATE
27
- SET
28
- count = count + EXCLUDED.count,
29
- value = value + EXCLUDED.value,
30
- minimum = min(minimum, EXCLUDED.minimum),
31
- maximum = max(maximum, EXCLUDED.maximum)
32
-
33
- migrate_events:
34
- INSERT INTO m.events(topic, name, key, created_at, count, value, minimum, maximum)
35
- SELECT topic, name, key, created_at, count, value, minimum, maximum
36
- FROM local_events
37
- ORDER BY resolution, created_at ASC, topic, name, key
23
+ (?1, '___', '___', ?4, ?5, ?6, ?6, ?6)
24
+ ON CONFLICT DO UPDATE
25
+ SET
26
+ count = count + EXCLUDED.count,
27
+ value = value + EXCLUDED.value,
28
+ minimum = min(minimum, EXCLUDED.minimum),
29
+ maximum = max(maximum, EXCLUDED.maximum);
30
+
31
+ migrate_events: >
32
+ INSERT INTO m.events(topic, name, key, created_at, count, value, minimum, maximum)
33
+ SELECT topic, name, key, created_at, count, value, minimum, maximum
34
+ FROM local_events
35
+ ORDER BY resolution, created_at ASC, topic, name, key
38
36
  LIMIT ?
39
- ON CONFLICT DO UPDATE
40
- SET
41
- count = count + EXCLUDED.count,
42
- value = value + EXCLUDED.value,
43
- minimum = min(minimum, EXCLUDED.minimum),
44
- maximum = max(maximum, EXCLUDED.maximum)
37
+ ON CONFLICT DO UPDATE
38
+ SET
39
+ count = count + EXCLUDED.count,
40
+ value = value + EXCLUDED.value,
41
+ minimum = min(minimum, EXCLUDED.minimum),
42
+ maximum = max(maximum, EXCLUDED.maximum);
45
43
 
46
- delete_migrated_events:
44
+ delete_migrated_events: >
47
45
  DELETE FROM local_events WHERE rowid IN (
48
- SELECT rowid
49
- FROM local_events
50
- ORDER BY resolution, created_at ASC, topic, name, key
46
+ SELECT rowid
47
+ FROM local_events
48
+ ORDER BY resolution, created_at ASC, topic, name, key
51
49
  LIMIT ?
52
- )
53
-
54
- event_count:
50
+ );
51
+
52
+ event_count: >
55
53
  SELECT count(*) FROM local_events;
56
-
@@ -1,27 +1,26 @@
1
1
  # frozen_stringe_literal: true
2
2
 
3
3
  # all components should require the support module
4
- require_relative 'litesupport'
4
+ require_relative "litesupport"
5
5
 
6
- #require 'securerandom'
6
+ # require 'securerandom'
7
7
 
8
8
  ##
9
- #Litequeue is a simple queueing system for Ruby applications that allows you to push and pop values from a queue. It provides a straightforward API for creating and managing named queues, and for adding and removing values from those queues. Additionally, it offers options for scheduling pops at a certain time in the future, which can be useful for delaying processing until a later time.
9
+ # Litequeue is a simple queueing system for Ruby applications that allows you to push and pop values from a queue. It provides a straightforward API for creating and managing named queues, and for adding and removing values from those queues. Additionally, it offers options for scheduling pops at a certain time in the future, which can be useful for delaying processing until a later time.
10
10
  #
11
- #Litequeue is built on top of SQLite, which makes it very fast and efficient, even when handling large volumes of data. This lightweight and easy-to-use queueing system serves as a good foundation for building more advanced job processing frameworks that require basic queuing capabilities.
11
+ # Litequeue is built on top of SQLite, which makes it very fast and efficient, even when handling large volumes of data. This lightweight and easy-to-use queueing system serves as a good foundation for building more advanced job processing frameworks that require basic queuing capabilities.
12
12
  #
13
-
14
- class Litequeue
15
13
 
14
+ class Litequeue
16
15
  # the default options for the queue
17
- # can be overriden by passing new options in a hash
16
+ # can be overriden by passing new options in a hash
18
17
  # to Litequeue.new
19
18
  # path: "./queue.db"
20
19
  # mmap_size: 128 * 1024 * 1024 -> 128MB to be held in memory
21
20
  # sync: 1 -> sync only when checkpointing
22
-
21
+
23
22
  include Litesupport::Liteconnection
24
-
23
+
25
24
  DEFAULT_OPTIONS = {
26
25
  path: Litesupport.root.join("queue.sqlite3"),
27
26
  mmap_size: 32 * 1024,
@@ -35,70 +34,70 @@ class Litequeue
35
34
  # queue.pop # => nil
36
35
  # sleep 2
37
36
  # queue.pop # => "somevalue"
38
-
37
+
39
38
  def initialize(options = {})
40
39
  init(options)
41
40
  end
42
-
41
+
43
42
  # push an item to the queue, optionally specifying the queue name (defaults to default) and after how many seconds it should be ready to pop (defaults to zero)
44
43
  # a unique job id is returned from this method, can be used later to delete it before it fires. You can push string, integer, float, true, false or nil values
45
44
  #
46
- def push(value, delay=0, queue='default')
45
+ def push(value, delay = 0, queue = "default")
47
46
  # @todo - check if queue is busy, back off if it is
48
47
  # also bring back the synchronize block, to prevent
49
48
  # a race condition if a thread hits the busy handler
50
49
  # before the current thread proceeds after a backoff
51
- #id = SecureRandom.uuid # this is somehow expensive, can we improve?
50
+ # id = SecureRandom.uuid # this is somehow expensive, can we improve?
52
51
  run_stmt(:push, queue, delay, value)[0]
53
52
  end
54
-
55
- def repush(id, value, delay=0, queue='default')
53
+
54
+ def repush(id, value, delay = 0, queue = "default")
56
55
  run_stmt(:repush, id, queue, delay, value)[0]
57
56
  end
58
-
59
- alias_method :"<<", :push
57
+
58
+ alias_method :<<, :push
60
59
  alias_method :"<<<", :repush
61
-
60
+
62
61
  # pop an item from the queue, optionally with a specific queue name (default queue name is 'default')
63
- def pop(queue='default', limit = 1)
64
- res = run_stmt(:pop, queue, limit)
65
- return res[0] if res.length == 1
66
- return nil if res.empty?
67
- res
62
+ def pop(queue = "default", limit = 1)
63
+ res = run_stmt(:pop, queue, limit)
64
+ return res[0] if res.length == 1
65
+ return nil if res.empty?
66
+ res
68
67
  end
69
-
68
+
70
69
  # delete an item from the queue
71
70
  # queue = Litequeue.new
72
71
  # id = queue.push("somevalue")
73
72
  # queue.delete(id) # => "somevalue"
74
73
  # queue.pop # => nil
75
74
  def delete(id)
76
- result = run_stmt(:delete, id)[0]
75
+ run_stmt(:delete, id)[0]
77
76
  end
78
-
77
+
79
78
  # deletes all the entries in all queues, or if a queue name is given, deletes all entries in that specific queue
80
- def clear(queue=nil)
79
+ def clear(queue = nil)
81
80
  run_sql("DELETE FROM queue WHERE iif(?1 IS NOT NULL, name = ?1, TRUE)", queue)
82
81
  end
83
82
 
84
83
  # returns a count of entries in all queues, or if a queue name is given, reutrns the count of entries in that queue
85
- def count(queue=nil)
84
+ def count(queue = nil)
86
85
  run_sql("SELECT count(*) FROM queue WHERE iif(?1 IS NOT NULL, name = ?1, TRUE)", queue)[0][0]
87
86
  end
88
-
87
+
89
88
  # return the size of the queue file on disk
90
- #def size
91
- # run_sql("SELECT size.page_size * count.page_count FROM pragma_page_size() AS size, pragma_page_count() AS count")[0][0]
92
- #end
93
-
89
+ # def size
90
+ # run_sql("SELECT size.page_size * count.page_count FROM pragma_page_size() AS size, pragma_page_count() AS count")[0][0]
91
+ # end
92
+
94
93
  def queues_info
95
94
  run_stmt(:info)
96
95
  end
97
-
96
+
98
97
  def snapshot
99
98
  queues = {}
100
99
  queues_info.each do |qc|
101
- #queues[qc[0]] = {count: qc[1], time_in_queue: {avg: qc[2], min: qc[3], max: qc[4]}}
100
+ # queues[qc[0]] = {count: qc[1], time_in_queue: {avg: qc[2], min: qc[3], max: qc[4]}}
102
101
  queues[qc[0]] = qc[1]
103
102
  end
104
103
  {
@@ -109,15 +108,15 @@ class Litequeue
109
108
  size: size,
110
109
  jobs: count
111
110
  },
112
- queues: queues
111
+ queues: queues
113
112
  }
114
113
  end
115
114
 
116
- private
117
-
115
+ private
116
+
118
117
  def create_connection
119
118
  super("#{__dir__}/litequeue.sql.yml") do |conn|
120
- conn.wal_autocheckpoint = 10000
119
+ conn.wal_autocheckpoint = 10000
121
120
  # check if there is an old database and convert entries to the new format
122
121
  if conn.get_first_value("select count(*) from sqlite_master where name = '_ul_queue_'") == 1
123
122
  conn.transaction(:immediate) do
@@ -127,5 +126,4 @@ class Litequeue
127
126
  end
128
127
  end
129
128
  end
130
-
131
- end
129
+ end
@@ -1,45 +1,53 @@
1
1
  schema:
2
- 1:
3
- create_table_queue: >
2
+ 1:
3
+ create_table_queue: >
4
4
  CREATE TABLE IF NOT EXISTS queue(
5
- id TEXT PRIMARY KEY DEFAULT(hex(randomblob(32))) NOT NULL ON CONFLICT REPLACE,
6
- name TEXT DEFAULT('default') NOT NULL ON CONFLICT REPLACE,
5
+ id TEXT PRIMARY KEY DEFAULT(hex(randomblob(32))) NOT NULL ON CONFLICT REPLACE,
6
+ name TEXT DEFAULT('default') NOT NULL ON CONFLICT REPLACE,
7
7
  fire_at INTEGER DEFAULT(unixepoch()) NOT NULL ON CONFLICT REPLACE,
8
8
  value TEXT,
9
9
  created_at INTEGER DEFAULT(unixepoch()) NOT NULL ON CONFLICT REPLACE
10
- ) WITHOUT ROWID
11
-
10
+ ) WITHOUT ROWID;
11
+
12
12
  create_index_queue_by_name: >
13
- CREATE INDEX IF NOT EXISTS idx_queue_by_name ON queue(name, fire_at ASC)
13
+ CREATE INDEX IF NOT EXISTS idx_queue_by_name ON queue(name, fire_at ASC);
14
14
 
15
15
  stmts:
16
16
 
17
- push: INSERT INTO queue(id, name, fire_at, value) VALUES (hex(randomblob(32)), $1, (unixepoch() + $2), $3) RETURNING id, name
18
-
19
- repush: INSERT INTO queue(id, name, fire_at, value) VALUES (?, ?, (unixepoch() + ?), ?) RETURNING name
17
+ push: >
18
+ INSERT INTO queue(id, name, fire_at, value)
19
+ VALUES (hex(randomblob(32)), $1, (unixepoch() + $2), $3)
20
+ RETURNING id, name;
21
+
22
+ repush: >
23
+ INSERT INTO queue(id, name, fire_at, value)
24
+ VALUES (?, ?, (unixepoch() + ?), ?)
25
+ RETURNING name;
20
26
 
21
27
  pop: >
22
- DELETE FROM queue
23
- WHERE (name, fire_at, id)
28
+ DELETE FROM queue
29
+ WHERE (name, fire_at, id)
24
30
  IN (
25
- SELECT name, fire_at, id FROM queue
26
- WHERE name = ifnull($1, 'default')
27
- AND fire_at <= (unixepoch())
28
- ORDER BY fire_at ASC
29
- LIMIT ifnull($2, 1)
30
- )
31
- RETURNING id, value
32
-
33
- delete: DELETE FROM queue WHERE id = $1 RETURNING value
34
-
31
+ SELECT name, fire_at, id FROM queue
32
+ WHERE name = ifnull($1, 'default')
33
+ AND fire_at <= (unixepoch())
34
+ ORDER BY fire_at ASC
35
+ LIMIT ifnull($2, 1)
36
+ )
37
+ RETURNING id, value;
38
+
39
+ delete: >
40
+ DELETE FROM queue
41
+ WHERE id = $1
42
+ RETURNING value;
43
+
35
44
  info: >
36
- SELECT
37
- name,
38
- count(*) AS count,
39
- avg(unixepoch() - created_at) AS avg,
40
- min(unixepoch() - created_at) AS min,
45
+ SELECT
46
+ name,
47
+ count(*) AS count,
48
+ avg(unixepoch() - created_at) AS avg,
49
+ min(unixepoch() - created_at) AS min,
41
50
  max(unixepoch() - created_at) AS max
42
- FROM queue
43
- GROUP BY name
44
- ORDER BY count DESC
45
-
51
+ FROM queue
52
+ GROUP BY name
53
+ ORDER BY count DESC;
@@ -10,40 +10,40 @@ module Litescheduler
10
10
  elsif defined? Polyphony
11
11
  :polyphony
12
12
  elsif defined? Iodine
13
- :iodine
13
+ :iodine
14
14
  else
15
15
  :threaded
16
16
  end
17
17
  end
18
-
18
+
19
19
  # spawn a new execution context
20
20
  def self.spawn(&block)
21
21
  if backend == :fiber
22
22
  Fiber.schedule(&block)
23
23
  elsif backend == :polyphony
24
24
  spin(&block)
25
- elsif backend == :threaded or backend == :iodine
25
+ elsif (backend == :threaded) || (backend == :iodine)
26
26
  Thread.new(&block)
27
27
  end
28
28
  # we should never reach here
29
29
  end
30
-
30
+
31
31
  def self.storage
32
- if backend == :fiber || backend == :poylphony
32
+ if fiber_backed?
33
33
  Fiber.current.storage
34
34
  else
35
35
  Thread.current
36
36
  end
37
37
  end
38
-
38
+
39
39
  def self.current
40
- if backend == :fiber || backend == :poylphony
40
+ if fiber_backed?
41
41
  Fiber.current
42
42
  else
43
43
  Thread.current
44
44
  end
45
45
  end
46
-
46
+
47
47
  # switch the execution context to allow others to run
48
48
  def self.switch
49
49
  if backend == :fiber
@@ -54,31 +54,37 @@ module Litescheduler
54
54
  Thread.current.switch_fiber
55
55
  true
56
56
  else
57
- #Thread.pass
57
+ # Thread.pass
58
58
  false
59
- end
59
+ end
60
60
  end
61
-
61
+
62
62
  # bold assumption, we will only synchronize threaded code!
63
63
  # If some code explicitly wants to synchronize a fiber
64
64
  # they must send (true) as a parameter to this method
65
65
  # else it is a no-op for fibers
66
66
  def self.synchronize(fiber_sync = false, &block)
67
- if backend == :fiber or backend == :polyphony
67
+ if fiber_backed?
68
68
  yield # do nothing, just run the block as is
69
69
  else
70
- self.mutex.synchronize(&block)
70
+ mutex.synchronize(&block)
71
71
  end
72
72
  end
73
-
73
+
74
74
  def self.max_contexts
75
- return 50 if backend == :fiber || backend == :polyphony
76
- 5
75
+ return 50 if fiber_backed?
76
+ 5
77
77
  end
78
-
78
+
79
79
  # mutex initialization
80
80
  def self.mutex
81
81
  # a single mutex per process (is that ok?)
82
82
  @@mutex ||= Mutex.new
83
83
  end
84
- end
84
+
85
+ private
86
+
87
+ def self.fiber_backed?
88
+ backend == :fiber || backend == :polyphony
89
+ end
90
+ end