Skip to content

goatrllr/orka

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

26 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Orka: Fast Process Registry for Erlang

Orka Logo

Tests Status License

Orka is a high-performance, pluggable process registry for Erlang/OTP applications. It provides fast lookups via pluggable backends (ETS-based by default), automatic process lifecycle management, rich metadata querying, and startup coordination features. Built with a pathway to distribution-aware backends like RA, Khepri, or Riak-core.

What Orka Provides

Fast registration & lookup — O(1) lookups via pluggable backends
Pluggable backends — ETS (default), extensible to RA, Khepri, Riak-core
Automatic cleanup — Process monitors handle crashes
Rich metadata — Tags and properties for flexible querying
Startup coordination — Await/subscribe for service dependencies
Batch operations — Atomic multi-process registration
Singleton pattern — One-key-per-process constraint
Zero dependencies — Pure Erlang/OTP, no external deps
Fully tested — 85 test cases, all passing

Use Cases

  • Service registries — Find services by name, type, or category
  • Connection pools — Track workers and distribute load by capacity
  • Startup coordination — Wait for dependencies before proceeding
  • Health monitoring — Query process status and distribution
  • Resource tracking — Manage distributed resources with properties
  • User sessions — Track multi-service per-user workloads
  • Process discovery — Efficient lookup by key or metadata

Quick Start

%% Register a service
orka:register({global, service, translator}, Pid, #{
    tags => [online, critical],
    properties => #{capacity => 100, version => "2.1"}
}).

%% Lookup by key (checks liveness)
{ok, {Key, Pid, Metadata}} = orka:lookup_alive({global, service, translator}).

%% Query by tag
OnlineServices = orka:entries_by_tag(online).

%% Query by property
HighCapacity = orka:find_by_property(capacity, 100).

%% Await service startup
{ok, Entry} = orka:await({global, service, database}, 30000).

%% Batch register (atomic, explicit Pids)
{ok, Entries} = orka:register_batch([
    {{global, portfolio, user1}, Pid1, #{tags => [portfolio, user1]}},
    {{global, orders, user1}, Pid2, #{tags => [orders, user1]}}
]).

Documentation

Start with API.md for complete documentation, then explore:

Document Content
API.md Complete API reference with examples
docs/usage_patterns.md 8 fundamental patterns
docs/singleton_examples.md Single-instance services
docs/property_examples.md Rich metadata querying
docs/await_examples.md Startup coordination
docs/comparison.md Orka vs gproc vs syn
docs/extensions/ Future extension ideas

Architecture

┌────────────────────────────────────────────────────────────┐
│         Orka Gen_Server                                    │
│  (registration, monitors, notify)                          │
└────────────────────┬─────────────────────────────────────┘
                     │
      ┌──────────────┴──────────────┐
      │                             │
      ▼                             ▼
┌──────────────────┐      ┌────────────────┐
│ orka_store       │      │  State Maps    │
│  (pluggable)     │      │ {PidSingleton  │
│                  │      │  PidKeyMap     │
│  ┌────────────┐  │      │  Subscribers   │
│  │  Backend   │  │      │  MonitorMap}   │
│  │            │  │      └────────────────┘
│  │ • ETS(dflt)│  │
│  │ • RA       │  │
│  │ • Khepri   │  │
│  │ • custom   │  │
│  └────────────┘  │
└──────────────────┘

Key features:

  • Pluggable backends — Swap implementations for different guarantees
    • orka_store_ets — Default, local-node with separate local/global stores
    • Extensible for ra, khepri, riak-core, etc. (see docs/extensions)
  • Gen_server for atomic writes with monitors
  • Process monitors for automatic cleanup
  • Efficient indices for tags and properties

Performance

ETS backend (default):

  • Lookup: ~1-2 microseconds (ETS public table)
  • Registration: ~10-20 microseconds (gen_server call + monitor)
  • Tag query: O(n) where n = entries with tag (usually small)

Suitable for: Service discovery, startup coordination, process tracking (<10K lookups/sec)

Not suitable for: Per-message routing in high-throughput systems (>100K msg/sec). See docs/extensions/ for distributed backends and caching patterns.

Backend flexibility: Different backends offer different trade-offs:

  • ETS — Local-only, ultra-fast, no persistence
  • RA/Khepri — Distributed, replicated, fault-tolerant (planned extensions)

Testing

All features are thoroughly tested:

make ct        # Run Common Test suite (85/85 passing)
make clean     # Clean build artifacts
make erl       # Start Erlang shell with orka loaded

Test coverage includes:

  • Registration, unregistration, lookup
  • Process cleanup on crash
  • Tags and properties
  • Singleton constraint
  • Await/subscribe coordination
  • Batch operations
  • Concurrent subscribers
  • Backend implementations (ETS, store abstraction)
  • Property-based testing
  • Scope isolation (local/global)
  • Concurrency and regression tests

Tests are organized in 9 test suites:

  • orka_SUITE.erl — Core functionality (57 tests)
  • orka_app_SUITE.erl — Application startup (5 tests)
  • orka_concurrency_SUITE.erl — Concurrent operations (2 tests)
  • orka_extra_SUITE.erl — Extended features (4 tests)
  • orka_gpt_regression_SUITE.erl — Regression tests (3 tests)
  • orka_issue_regression_SUITE.erl — Issue regression tests (3 tests)
  • orka_property_SUITE.erl — Property-based tests (2 tests)
  • orka_scope_SUITE.erl — Scope isolation tests (3 tests)
  • orka_store_SUITE.erl — Store backend tests (7 tests)

Design Principles

1. Backend-Specific Reads
Lookup operations go through the configured store backend (ETS by default).

2. Automatic Cleanup
Process crashes are detected via monitors and entries are automatically removed.

3. Three Levels of Metadata

  • Key structure — Hierarchical: {Scope, Type, Name}
  • Tags — For categorization: online, critical, translator
  • Properties — For rich attributes: region: "us-west", capacity: 100

4. Pluggable Backends
Orka's core is backend-agnostic. The default ETS backend handles single-node registries. Extensions can provide distributed backends (RA, Khepri, etc.) for cluster-wide registries. See docs/extensions/ for planned backend options.

Key Patterns

Supervisor Registration

init([]) ->
    orka:register({global, service, my_service}, self(), #{tags => [online]}),
    {ok, #state{}}.

Atomic Startup

{ok, Pid} = orka:register_with(
    {global, service, database},
    #{tags => [critical]},
    {db_server, start_link, []}
).

Singleton Services

{ok, _} = orka:register_single({global, service, config}, #{tags => [critical]}).

Batch Registration

{ok, Entries} = orka:register_batch([
    {{global, portfolio, user1}, Pid1, #{tags => [user1, portfolio]}},
    {{global, orders, user1}, Pid2, #{tags => [user1, orders]}},
    {{global, risk, user1}, Pid3, #{tags => [user1, risk]}}
]).

Startup Coordination

%% Block on critical dependency
{ok, {_Key, DbPid, _}} = orka:await({global, service, database}, 30000).

%% Subscribe to optional service
orka:subscribe({global, service, cache}).

Property-Based Queries

%% Find services in specific region
WestServices = orka:find_by_property(region, "us-west").

%% Load balance by capacity
HighCapacity = orka:find_by_property(capacity, 150).

Comparison with Alternatives

Feature Orka gproc syn
Speed ⚡⚡⚡ Fast ⚡⚡ Medium ⚡⚡ Medium
Local registry
Distributed
Tags
Properties
Await/subscribe
Singleton pattern
Batch registration
Zero dependencies

See docs/comparison.md for detailed comparison.

API Overview

Core Functions

  • register/2,3 — Register a process
  • register_with/3 — Atomically start and register
  • register_single/2,3 — Singleton constraint
  • lookup/1 — Fast key lookup (no liveness check)
  • lookup_dirty/1 — Lock-free ETS lookup (no liveness check)
  • lookup_alive/1 — Key lookup with liveness check
  • lookup_all/0 — Get all entries
  • unregister/1 — Remove entry

Querying

  • entries_by_type/1 — Find by key type
  • entries_by_tag/1 — Find by tag
  • find_by_property/2,3 — Find by property value
  • count_by_type/1, count_by_tag/1, count_by_property/2
  • property_stats/2 — Distribution analysis

Advanced

  • register_batch/1 — Batch atomic registration
  • register_batch_with/1 — Start and batch register atomically
  • unregister_batch/1 — Remove multiple entries atomically
  • register_property/3 — Add queryable properties
  • update_metadata/2 — Update metadata on entry
  • await/2 — Block on startup
  • subscribe/1 — Non-blocking notification
  • add_tag/2, remove_tag/2 — Dynamic metadata

See API.md for complete documentation.

Installation

Add to your rebar.config:

{deps, [
    {orka, {git, "https://github.com/goatrllr/orka.git", {branch, "main"}}}
]}.

Or manually:

git clone <repo> deps/orka
make -C deps/orka

Then in your application:

{ok, _} = application:ensure_all_started(orka).

Project Structure

orca/
├── src/
│   ├── orka.erl              # Main registry module
│   └── orka_app.erl          # Application callback
├── test/
│   ├── orka_SUITE.erl                  # Core registry tests (main test suite)
│   ├── orka_app_SUITE.erl              # Application lifecycle tests
│   ├── orka_concurrency_SUITE.erl      # Concurrent operations tests
│   ├── orka_extra_SUITE.erl            # Extended feature tests
│   ├── orka_property_SUITE.erl         # Property-based tests
│   ├── orka_gpt_regression_SUITE.erl   # GPT-identified issue regressions
│   └── orka_issue_regression_SUITE.erl # Known issue regressions
├── ebin/                      # Compiled BEAM files
│
├── API.md                     # API reference (complete documentation)
├── README.md                  # This file
├── DOCMAP.md                  # Documentation navigation guide
├── rebar.config               # Build configuration
├── Makefile                   # Build targets
│
└── docs/
    ├── README.md              # Documentation guide
    ├── usage_patterns.md      # 8 fundamental patterns
    ├── singleton_examples.md  # Single-instance services
    ├── property_examples.md   # Rich metadata querying
    ├── await_examples.md      # Startup coordination
    ├── comparison.md          # vs gproc/syn
    │
    └── extensions/            # Extension ideas (not yet implemented)
        ├── README.md          # Extension overview
        ├── orka_syn.md        # Orka + Syn hybrid architecture
        ├── groups_examples.md # Process groups patterns
        ├── partial_match_options.md  # Query patterns
        └── message_systems.md # Kafka/RabbitMQ clone architectures

Common Tasks

Register a service

See API.md or usage_patterns.md Pattern 1

Find services by attribute

See property_examples.md

Wait for startup

See await_examples.md

Build singleton service

See singleton_examples.md

Compare with alternatives

See comparison.md

FAQ

Q: What's the difference between tags and properties?

  • Tags: Categories (online, critical, translator)
  • Properties: Attributes (region, capacity, version)

See API.md FAQ

Q: Should I use await or subscribe?

  • await: Blocking on critical dependencies
  • subscribe: Non-blocking optional dependencies

See API.md FAQ

Q: Can I use Orka in a distributed system? Orka is local-node only. For multi-node, see Orka + Syn patterns.

See API.md FAQ for more.

Examples

Trading Application

%% Register user workspace with 5 services
create_user_workspace(UserId) ->
    {ok, Entries} = orka:register_batch([
        {{global, portfolio, UserId}, Pid1, #{tags => [UserId, portfolio]}},
        {{global, technical, UserId}, Pid2, #{tags => [UserId, technical]}},
        {{global, orders, UserId}, Pid3, #{tags => [UserId, orders]}},
        {{global, risk, UserId}, Pid4, #{tags => [UserId, risk]}},
        {{global, monitoring, UserId}, Pid5, #{tags => [UserId, monitoring]}}
    ]),
    {ok, Entries}.

%% Query all services for user
get_user_services(UserId) ->
    orka:entries_by_tag(UserId).

%% Broadcast to all user services
broadcast_to_user(UserId, Message) ->
    Services = get_user_services(UserId),
    [Pid ! {user_message, UserId, Message} || {_, Pid, _} <- Services].

Health Monitoring

check_system_health() ->
    OnlineServices = orka:count_by_tag(online),
    CriticalServices = orka:count_by_tag(critical),
    
    case {OnlineServices, CriticalServices} of
        {_, 0} -> {error, critical_services_down};
        {N, M} when N < 2 -> {warning, low_availability};
        _ -> ok
    end.

Startup Coordination

%% In application start
start(normal, _Args) ->
    application:ensure_all_started(orka),
    my_sup:start_link(),
    
    %% Wait for critical services
    case wait_for_services([database, cache, queue], 30000) of
        ok -> io:format("System ready~n", []);
        {error, timeout} -> {error, startup_timeout}
    end.

Development

Build

make                # Compile
make clean          # Clean
make ct             # Run tests
make erl            # Shell

Testing

make ct                              # All tests
make erl                             # Interactive shell
ct:run_test("test/orka_SUITE").      # Specific suite

Code Style

Follows standard Erlang conventions. See modules for examples.

Contributing

Contributions welcome! Please:

  1. Add tests for new features
  2. Update documentation
  3. Follow existing code style
  4. Ensure all 71 tests pass

License

MIT - See LICENSE file

Acknowledgments

  • Inspired by gproc and syn process registries
  • ETS-based architecture for performance
  • Common Test, PropEr for comprehensive testing

Support


Latest Version: 1.0 | Status: Production Ready ✅ | Tests: 71/71 passing | Code: 650 lines (1,481 total with docs) | Erlang: OTP 24+ | License: MIT

About

Orka: Fast Process Registry for Erlang

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published