Qwe - pure ruby framework for stuff

It might be useful if you have:

  • Extra high loads
  • Extensive interaction
  • Complexity in fitting into database

Qwe is built upon DRb, which provides a way to directly access ruby objects from different processes, thus avoiding latency, JSON or XML conversions, and disk I/O limits.

Qwe manages:

  • Serving and accessing objects over DRb
  • Persisting them on disk
  • Loading them back into RAM upon access
  • Tracing every change

Full Documentation

Multiplayer paint example - Qwe is fast enough to process mouse events on server side.

Installation

Qwe requires native zstd support.

Otherwise it's regular bundle add qwe or gem install qwe

Features

Zero-config startup

bundle exec qwe

And you are ready to go. Puma plugin is also available.

Object persistence

id = Qwe::DB.create(Hash)

The hash is now accessible from any ruby process with Qwe::DB[], considering you know that id.

Qwe::DB[id][:one_two_three] = 123

After a period of inactivity, or manually if configured, hash will get dumped on disk with Marshal and RAM freed. Once you call Qwe::DB[id] again, object gets un-marshalled and lives in RAM the same way.

Attributes API

class Person
  include Qwe::Mixins::Thing

  attribute :age, min: 0, init: 18, convert: :to_i
end

is equivalent to

class Person
  def initialize
    @age = 18
  end

  attr_reader :age

  def age=(val)
    val = val.to_i
    val = 0 if val < 0
    @age = val
  end
end

Despite the fact that attribute method accepts many arguments and can be extended, performance of both variants is the same.

Transactions

Every attribute change can be tracked, so you can scroll back and forth your object state throughout it's lifecycle. Changes are stored as ruby code in plaintext file.

requirements.rb

class Person
  include Qwe::Mixins::Root

  attribute :age, init: 0, convert: :to_i
end

Server startup

qwe -r requirements.rb

ruby console

id = Qwe::DB.create(:Person)
Qwe::DB[id].age = 1
Qwe::DB[id].age += 2
Qwe::DB[id].age += 3
puts Qwe::DB[id].record.commits

Outputs:

self.instance_variable_set(:@age, 1)
self.instance_variable_set(:@age, 3)
self.instance_variable_set(:@age, 6)

While commit "logs" are not handcrafted ruby code, they are still readable. Most standard types are supported and subject to extension.

Requirements

ruby 3+ is required, but since long-running workers greatly benefit from YJIT compiler, and compiler is greatly improved in subsequent releases, it is better to use latest ruby whenever possible.

Notes

Is it production ready?

It is extracted from production and backported there, but your case may be different. Feedback is welcome.

Does it scale?

It depends, but mostly yes.

On MRI one object lives in one thread. Qwe can work with any amount of worker threads, but it's for increasing amount of simultaneously served objects. Other ruby implementations are not tested.

The primary limitation and at the same time strength is the fact that "root" objects (the ones created with Qwe::DB.create) are fully loaded into RAM upon access. It makes working with them faster after a short startup delay, but does not allow to effectively perform SQL-like requests across multiple root objects.

If root object are independent from each other, system scales easily by running independent Qwe servers.

Note on security

Qwe::DB[] provides access to object from another thread with DRb, ruby gem from standard library. DRb does not provide any access control, and opens separate port for each object it serves. It is safe to run Qwe in docker container or other isolated network, and unsafe to run on virtual or physical device with all ports open.

In web applications Qwe may be accessed though websockets without providing direct access to ruby methods.

See DRb docs for a more detailed explanation.

License

The gem is available as open source under the terms of the MIT License.