Branch Context: This documentation covers the Orka Core API on the
mainbranch. Orka uses a split-branch strategy where stable Core remains onmainand extensions are on feature branches. Ensure you're on the correct branch for your use case.
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.
Simple API: 31 functions for registration, lookup, tagging, properties, batch operations, and startup coordination. Lightweight: Zero external dependencies, pure Erlang/OTP. Fast: ~1-2µs lookups (500K-1M queries/sec). Reliable: 74 tests passing, automatic cleanup on process crash, API stability guaranteed.
✅ 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
✅ Lightweight queries — Type/tag/property enumeration
✅ Liveness validation — Process health checks
✅ Zero dependencies — Pure Erlang/OTP, no external deps
✅ Fully tested — 85 test cases, all passing
- 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 resources with properties
- User sessions — Track multi-service per-user workloads
- Process discovery — Efficient lookup by key or metadata
%% Register a service
orka:register({global, service, translator}, Pid, #{
tags => [online, critical],
properties => #{capacity => 100}
}).
%% Lookup by key
{ok, {_Key, TranslatorPid, _Meta}} = orka:lookup({global, service, translator}).
%% Lookup with liveness validation
{ok, {_Key, TranslatorPid, _Meta}} = orka:lookup_alive({global, service, translator}).
%% Handle missing service
case orka:lookup({global, service, unknown}) of
{ok, {_Key, Pid, _Meta}} -> {found, Pid};
not_found -> {error, service_unavailable}
end.%% Find all online services
OnlineServices = orka:entries_by_tag(online),
lists:foreach(fun({_Key, Pid, _Meta}) ->
gen_server:call(Pid, ping)
end, OnlineServices).
%% Count services by tag
OnlineCount = orka:count_by_tag(online).%% Find workers with specific capacity
HighCapacityWorkers = orka:find_by_property(capacity, 100).
%% Load balance across workers
AllWorkers = orka:entries_by_type(worker),
Sorted = lists:sort(fun compare_load/2, AllWorkers),
[{_Key, BestWorker, _Meta}|_] = Sorted,
gen_server:call(BestWorker, {process_item, Item}).%% Block until critical service appears (30 second timeout)
case orka:await({global, service, database}, 30000) of
{ok, {_Key, DbPid, _Meta}} ->
io:format("Database ready: ~p~n", [DbPid]);
timeout ->
io:format("Database startup timeout~n"),
init:stop()
end.
%% Subscribe to optional service (non-blocking)
orka:subscribe({global, service, cache}),
receive
{orka_registered, {global, service, cache}, {_K, CachePid, _Meta}} ->
io:format("Cache became available: ~p~n", [CachePid])
after 0 ->
io:format("Cache not available, continuing without it~n")
end.%% Register multiple services atomically - all succeed or all fail
{ok, Entries} = orka:register_batch([
{{global, portfolio, user1}, PortfolioPid, #{tags => [user1, portfolio]}},
{{global, orders, user1}, OrdersPid, #{tags => [user1, orders]}},
{{global, risk, user1}, RiskPid, #{tags => [user1, risk]}}
]).
%% Get all services for a user via tag
UserServices = orka:entries_by_tag(user1),
io:format("User has ~p services~n", [length(UserServices)]).Supervisor Registers Child
init([]) ->
{ok, Pid} = my_service:start_link(),
orka:register({global, service, my_service}, Pid,
#{tags => [service, online]}),
{ok, {specs}}.Service Self-Registers
Service registers itself during gen_server:init/1 — autonomous and testable in isolation.
Singleton Services
case orka:register_single({global, service, config_server},
#{tags => [critical]}) of
{ok, _} -> {ok, started_as_singleton};
{error, {already_registered_under_key, _}} ->
ignore %% Another instance is active
end.Per-User Service Groups
Register all user services with shared tag for atomic startup and cleanup.
For additional patterns and detailed examples, see Examples & Use Cases.
Registration (5 functions)
register/2,3— Register a processregister_with/3— Atomically start and registerregister_single/2,3— Singleton constraint (one process per key)
Lookup (3 functions)
lookup/1— Fast key-based lookup (O(1))lookup_alive/1— Lookup with liveness validationlookup_all/0— Get all entries in registry
Unregistration (2 functions)
unregister/1— Remove entry by keyunregister_batch/1— Atomically remove multiple entries
Tags (5 functions)
add_tag/2— Add category to processremove_tag/2— Remove categoryentries_by_tag/1— Get all entries with tagkeys_by_tag/1— Get keys only (lightweight)count_by_tag/1— Count entries with tag
Properties (6 functions)
register_property/3— Register queryable attributefind_by_property/2,3— Find entries by property valuefind_keys_by_property/2,3— Find keys by property (lightweight)count_by_property/2— Count entries with property valueproperty_stats/2— Get distribution of property values
Type Queries (3 functions)
entries_by_type/1— Get all entries of a key typekeys_by_type/1— Get keys only (lightweight)count_by_type/1— Count entries by type
Startup Coordination (3 functions)
await/2— Block until key registered (with timeout)subscribe/1— Receive registration notificationsunsubscribe/1— Stop receiving notifications
Batch Operations (2 functions)
register_batch/1— Atomically register multiple processes- (included:
unregister_batch/1above)
Utility (2 functions)
entries/0— Get all registry entriescount/0— Get total entry count
For complete documentation with specs and examples, see API Documentation.
| Feature | Orka | Gproc | Syn |
|---|---|---|---|
| Local Registry | ✅ | ✅ | ✅ |
| Distributed | ❌ | ❌ | ✅ |
| Lock-free Reads | ✅ Fast (~1-2µs) | ✅ Good | |
| Tags | ✅ | ✅ | ❌ |
| Properties | ✅ | ❌ | ❌ |
| Singleton Pattern | ✅ | ❌ | ❌ |
| Batch Operations | ✅ | ❌ | ❌ |
| Startup Coordination | ✅ (await/subscribe) | ❌ | ❌ |
| Global Counters | ❌ | ✅ | ❌ |
| Named Values | ❌ | ✅ | ❌ |
When to Use Orka
- Single-node service discovery with high-read workloads
- Need property-drive routing or singleton constraints
- Prefer simple, focused API over feature richness
Use Gproc for
- Global counters, named shared values
- Battle-tested, mature, complex patterns
Use Syn for
- Multi-node distributed clustering
- Event notifications on registration changes
- Eventually consistent distributed registry
Orka documentation is organized into four comprehensive guides:
- Examples & Use Cases — Real-world patterns, use cases, and working code examples
- API Documentation — Complete reference for all 31 functions with specs and examples
- Developer Reference — Design principles, architecture, testing, and extension patterns
- README.md (this file) — Project overview and quick start
┌────────────────────────────────────────────────────────────┐
│ 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
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)
Backend flexibility: Different backends offer different trade-offs:
- ETS — Local-only, ultra-fast, no persistence
- RA/Khepri — Distributed, replicated, fault-tolerant (planned extensions)
make ct # Run Common Test suite (85/85 passing)
make eunit # Run EUnit tests
make clean # Clean build artifacts
make erl # Start Erlang shell with orka loadedTest Coverage (85 tests passing):
- 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)
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.
init([]) ->
orka:register({global, service, my_service}, self(), #{tags => [online]}),
{ok, #state{}}.{ok, Pid} = orka:register_with(
{global, service, database},
#{tags => [critical]},
{db_server, start_link, []}
).{ok, _} = orka:register_single({global, service, config}, #{tags => [critical]}).{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]}}
]).%% Block on critical dependency
{ok, {_Key, DbPid, _}} = orka:await({global, service, database}, 30000).
%% Subscribe to optional service
orka:subscribe({global, service, cache}).%% Find services in specific region
WestServices = orka:find_by_property(region, "us-west").
%% Load balance by capacity
HighCapacity = orka:find_by_property(capacity, 150).| 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.
register/2,3— Register a processregister_with/3— Atomically start and registerregister_single/2,3— Singleton constraintlookup/1— Fast key lookup (no liveness check)lookup_dirty/1— Lock-free ETS lookup (no liveness check)lookup_alive/1— Key lookup with liveness checklookup_all/0— Get all entriesunregister/1— Remove entry
entries_by_type/1— Find by key typeentries_by_tag/1— Find by tagfind_by_property/2,3— Find by property valuecount_by_type/1,count_by_tag/1,count_by_property/2property_stats/2— Distribution analysis
register_batch/1— Batch atomic registrationregister_batch_with/1— Start and batch register atomicallyunregister_batch/1— Remove multiple entries atomicallyregister_property/3— Add queryable propertiesupdate_metadata/2— Update metadata on entryawait/2— Block on startupsubscribe/1— Non-blocking notificationadd_tag/2,remove_tag/2— Dynamic metadata
See API.md for complete documentation.
Add to your rebar.config:
{deps, [
{orka, {git, "https://github.com/goatrllr/orka.git", {branch, "main"}}}
]}.Then in your application supervisor:
{ok, _} = application:ensure_all_started(orka).MIT - See LICENCE file for details.
Contributions welcome! Please ensure tests pass (make ct) and update documentation for new features.
- Inspired by gproc and syn process registries
- ETS-based architecture for performance
- Erlang/OTP community for excellent patterns
Status: Production Ready | Tests: 74/74 passing | License: MIT | Erlang: OTP 24+
