
2026/04/23 20:53
展示 HN:Honker – SQLite に PostgreSQL の NOTIFY/LISTEN セマンティクスを実装
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
Honker は、Rust をベースとしたライブラリで、外部サーバーやポーリングなしに耐障害なパブ/サブ、タスクキューイング、およびイベントストリームを提供するように SQLite を拡張します。その核心的な利点は、非効率な time-wait ループを SQLite の Write-Ahead Log (WAL) 上の直接のファイル通知で置き換えることにあり、これにはデータベース毎に専用
stat(2) スレッドを使用する軽量アーキテクチャが用いられ、1ms ごとの変更ポーリングを可能にします。これにより、単一の桁のミリ秒でのクロスプロセスメッセージ配信が即座に行えながら、メモリ使用量を最小限に抑え、アイドル状態のリスナーはトリガーされるまで SQL クエリを実行しないままにできます。「トランザクショナルアウブックス」パターン(notify() および stream.publish() などの機能を通じて)を活用することで、Honker はイベントの公開とタスクの処理の間での一貫性を確保し、言語境界を越えてもデータ損失を防ぎます。
Rust クレートとして提供され、ロード可能な拡張機能 (
_honker_live, _honker_dead, _honker_notifications) および Python、Node.js、Bun、Ruby、Go、Elixir、C++ 用のバインディングを持ち、シームレスなマルチランゲージ相互運用性を可能にします。しかし、すべてのデータベースが WAL モードで構成されることを要求し(WAL 非ファイルは拒否)、特定のアーキテクチャ制約により複雑なワークフローオーケストレーション、タスクパイプライン、またはマルチライターレプリケーションシナリオには適していません。保持ポリシーは呼び出し元によって定義され (db.prune_notifications())、かつクラッシュ回復は安全で、SIGKILL のような障害後にクレームを回収するために可視化タイムアウト(デフォルト 300s)を利用します。DAG ベースのオーケストレーションをサポートしないものの、手動での保持とデッドレターテーブルが長期データ管理を処理するイベント駆動システム向けの堅牢で低オーバーヘッドな解決策を提供します。本文
Honker は、SQLite を拡張し、PostgreSQL 風の NOTIFY/LISTEN セマンティクスを追加する言語バインディングおよび SQLite 拡張モジュールです。クライアントからのポーリングやデーモン/ブローカーを必要とせず、堅牢なパブリッシュ・サブスクライブ(pub/sub)、タスクキュー、そしてイベントストリームを組み込んでいます。
SELECT load_extension('honker') をサポートできる任意の言語が、同じ機能を享受できます。
Honker は以下の形態で提供されます:
- Rust クレイト (honker, 加えて honker-core/honker-extension)
- SQLite にロード可能な拡張モジュール
- ライブラリパッケージ:Python (honker), Node (@russellthehippo/honker-node), Bun (@russellthehippo/honker-bun), Ruby (honker), Go, Elixir, C++
ディスク上のレイアウトは Rust で一度定義され、各言語バインディングはこのロード可能な拡張モジュールの薄いラッパーとして機能します。
Honker の仕組み: Honker は、ポーリングインターバルを SQLite の WAL ファイル(Write-Ahead Log)におけるイベント通知に置き換えることで動作し、プッシュ型セマンティクスを実現します。これにより、クロスプロセス間での通知が可能となり、単一桁のミリ秒単位の配信遅延を実現します。
実験的機能。API は変更される可能性があります。
課題と解決策
SQLite は現在、多数のプロジェクトで採用されているデータベースです。しかし、それらには必然的に pub/sub やタスクキューが必要です。従来の対応策として「Redis + Celery の追加」という方法がありますが、それは動作はしますが、追加のデータストレージ(独自のバックアップ戦略が必要)を導入し、ビジネステーブルとキュー間の二重書き込み問題を引き起こします。さらに、ブローカーを運用するオーバーヘッドも伴います。
Honker は、「SQLite が主データベースであれば、タスクキューも同じファイルに存在させるべきである」というアプローチを採用しています。 これはつまり、
INSERT INTO orders と queue.enqueue(...) を同一のトランザクションでコミットできることを意味します。ロールバック時には両方が削除されます。キューは単なる表内の行、部分インデックスを持つものです。
先行技術:
- pg_notify(高速なトリガーだが、再試行や可視性に関する保証がない)
- Huey(SQLite を基盤とする Python ライブラリ)
- pg-boss および Oban(Postgres 側での事実上のゴールドスタンダードであり、Honker が SQLite で実現を目指しているもの) もしすでに Postgres を利用している場合は、これらの優れたライブラリを使用することを推奨します。
概要 (At a Glance)
import honker db = honker.open("app.db") emails = db.queue("emails") # キューイング (Queueing) emails.enqueue({"to": "alice@example.com"}) # クライアントによる消費 (Worker プロセス) async for job in emails.claim("worker-1"): send(job.payload) job.ack()
任意のキューイング操作は、ビジネスロジックの書き込みと原子性を持って実行できます(コミットするかロールバックするか)。
with db.transaction() as tx: tx.execute("INSERT INTO orders (user_id) VALUES (?)", [42]) emails.enqueue({"to": "alice@example.com"}, tx=tx)
特徴 (Features)
現在のバージョン:
- 一つの .db ファイル上でクロスプロセス間での notify/listen
- 再試行、優先度、遅延ジョブ、デッドレターテーブルを備えたワークキュー
- 送信操作がビジネスロジックの書き込みと原子性を持って実行可能(共にコミットまたはロールバック)
- ポーリングなしで、単一桁のミリ秒単位のクロスプロセス反応時間
- ハンドラータイムアウト、指数関数的な退避を伴う宣言的再試行機能
- 遅延ジョブ、タスクの有効期限、名前付きロック、レート制限
- リーダー選出スケジューラによる Crontab 風のパériオディックタスク
- オプトインのタスク結果保存機能(enqueue は ID を返す。ワーカーは戻り値を永続化し、コールラーは queue.wait_result(id) で待つ)
- パーコンシューマーオフセットと設定可能なフラッシュ間隔を備えた堅牢なストリーム
- 任意の SQLite クライアントが同じテーブルを読み取れるようにする SQLite ロード可能拡張モジュール
- バインディング:Python, Node.js, Rust, Go, Ruby, Bun, Elixir
意図的に構築されていない機能: タスクパイプライン/チェーン/グループ/コード、マルチライターレプリケーション、DAG を使ったワークフローオーケストレーション。
クイックスタート (Quick Start)
Python: キュー(永続的アトミックスリムスワンス・ワーク)
import honker db = honker.open("app.db") emails = db.queue("emails") with db.transaction() as tx: tx.execute("INSERT INTO orders (user_id) VALUES (?)", [42]) emails.enqueue({"to": "alice@example.com"}, tx=tx) # オーダーと原子性を持ってキューイング # 次にワーカー側で実行: async for job in emails.claim("worker-1"): # WAL コミットで目覚める try: send(job.payload); job.ack() except Exception as e: job.retry(delay_s=60, error=str(e))
claim() は非同期イテレーターです。各反復は claim_batch(worker_id, 1) に相当します。WAL コミットで目覚め、WAL ワッチャが発火できない場合にのみ 5 秒の過保護なポーリングにフォールバックします。バッチ処理の場合は明示的に claim_batch(worker_id, n) を呼び出し、queue.ack_batch(ids, worker_id) で承認してください。デフォルト:可視性タイムアウトは 300 秒です。
Python: タスク (Huey スタイルの装飾子)
手動で
queue.enqueue をラップする代わりに、関数呼び出しを自動的にキューイングされたジョブに変換したい場合:
@emails.task(retries=3, timeout_s=30) def send_email(to: str, subject: str) -> dict: ... return {"sent_at": time.time()} # コーラー側 r = send_email("alice@example.com", "Hi") # キューイングし、TaskResult を返す print(r.get(timeout=10)) # ワーカーが実行するまでブロック # ワーカー側(同一プロセスまたは独立したプロセスとして) python -m honker worker myapp.tasks:db --queue=emails --concurrency=4
自動名前は
{module}.{qualname} です(Huey/Celery の慣習)。本番環境では @emails.task(name="...") で明示的な名前を指定することを推奨します。リネームによって未解決のジョブが孤立するのを防ぐためです。定期的なタスクには @emails.periodic_task(crontab("0 3 * * *")) を使用します。詳細は packages/honker/examples/tasks.py を参照してください。
Python: ストリーム(永続的なパブリッシュ・サブスクライブ)
stream = db.stream("user-events") with db.transaction() as tx: tx.execute("UPDATE users SET name=? WHERE id=?", [name, uid]) stream.publish({"user_id": uid, "change": "name"}, tx=tx) async for event in stream.subscribe(consumer="dashboard"): await push_to_browser(event)
各名前付きコンシューマーは
_honker_stream_consumers テーブル内で独自のオフセットを管理します。subscribe は保存されたオフセット以降の行を再生し、WAL で目覚めるとライブ配信に切り替わります。イテレーターは、最大 1000 イベントごとか 1 秒ごと(どちらか先)にオフセットを自動的に保存するため、高スループットストリームが単一ライターのスロットを叩き続けるのを防ぎます。save_every_n= または save_every_s= で上書きしたり、両方を 0 に設定して自動保存を無効化し、自分で stream.save_offset(consumer, offset, tx=tx) を呼び出したりできます(そのトランザクション内で行った操作と原子性を持って)。少なくとも一度:クラッシュした場合は、最後のフラッシュされたオフセットまで飛行中(in-flight)のイベントを再配信します。
Python: 通知 (Ephemeral pub/sub)
async for n in db.listen("orders"): print(n.channel, n.payload) with db.transaction() as tx: tx.execute("INSERT INTO orders (id, total) VALUES (?, ?)", [42, 99.99]) tx.notify("orders", {"id": 42})
リスナーは現在の MAX(id) でアタッチされ、履歴は再生されません。永続的な再生が必要な場合は
db.stream() を使用してください。通知テーブルは自動で剪定(prune)されません。計画的なタスクから db.prune_notifications(older_than_s=…, max_keep=…) を呼び出してください。タスクペイロードは有効な JSON でなければならず、Python のライターと Node リーダーが同じチャンネルを共有できるようにする必要があります。
Node.js
const { open } = require('@russellthehippo/honker-node'); const db = open('app.db'); // 原子性:ビジネス書き込み + 通知が一緒にコミット const tx = db.transaction(); tx.execute('INSERT INTO orders (id) VALUES (?)', [42]); tx.notify('orders', { id: 42 }); tx.commit(); // Listen は WAL コミットで目覚め、チャンネルでフィルタリング for await (const n of db.listen('orders')) { handle(n.payload); }
SQLite 拡張モジュール (任意の SQLite 3.9+ クライアント)
.load ./libhonker_ext SELECT honker_bootstrap(); INSERT INTO _honker_live (queue, payload) VALUES ('emails', '{"to":"alice"}'); SELECT honker_claim_batch('emails', 'worker-1', 32, 300); -- JSON アレイ SELECT honker_ack_batch('[1,2,3]', 'worker-1'); -- 削除;件数を返す SELECT honker_sweep_expired('emails'); -- デッドに移動する件数 SELECT honker_lock_acquire('backup', 'me', 60); -- 1 = 取得成功、0 = 保持中 SELECT honker_lock_release('backup', 'me'); -- 1 = 解放 SELECT honker_rate_limit_try('api', 10, 60); -- 1 = リミット以下、0 = リミット到達 SELECT honker_rate_limit_sweep(3600); -- 1 時間以上前のウィンドウを削除 SELECT honker_cron_next_after('0 3 * * *', unixepoch()); -- 次の発火の UNIX タイムスタンプ SELECT honker_scheduler_register('nightly', 'backups', '0 3 * * *', '"go"', 0, NULL); -- パーイオディックタスクを登録 SELECT honker_scheduler_tick(unixepoch()); -- JSON: 発火すべきもの SELECT honker_scheduler_soonest(); -- 次の最小発火時刻 SELECT honker_scheduler_unregister('nightly'); -- 1 = 削除成功 SELECT honker_stream_publish('orders', 'k', '{"id":42}'); -- オフセットを返す SELECT honker_stream_read_since('orders', 0, 1000); -- JSON アレイ SELECT honker_stream_save_offset('worker', 'orders', 42); -- 単調増加アップsert SELECT honker_stream_get_offset('worker', 'orders'); -- オフセットまたは 0 SELECT honker_result_save(42, '{"ok":true}', 3600); -- 1 時間の TTL で保存 SELECT honker_result_get(42); -- 値または NULL SELECT honker_result_sweep(); -- 有効期限切れたものを剪定 SELECT notify('orders', '{"id":42}');
この拡張モジュールは、Python バインディングと同じ
_honker_live, _honker_dead, および _honker_notifications を共有するため、Python ワーカーが拡張モジュール経由で別の言語からプッシュされたジョブを主張(claim)できます。スキーマ互換性は tests/test_extension_interop.py で固定されています。
アーキテクチャ (Design)
このリポジトリには Honker SQLite ロード可能拡張モジュールと、Python, Node, Rust, Go, Ruby, Bun および Elixir 用のバインディングが含まれています。ほとんどのアプリケーションでは、SQLite のみで十分です。すでに SQLite を基盤とした堅牢なメッセージングを行う優れたライブラリが存在します。Huey は Honker が最も多く参照するものです。このプロジェクトは Huey に触発され、パッケージロジックを SQLite 拡張モジュールに移動させることで、言語やフレームワークを横断して類似の機能を提供することを目指しています。Postgres を基盤とするアプリケーションには pg_notify + pg-boss または Oban が同等の役割を果たします。このライブラリは、SQLite が主データベースであるアプリケーション向けです。
拡張モジュールにはそれを結び付ける 3 つのプリミティブがあります:一時的な pub/sub (
notify()), パーコンシューマーオフセットを備えた永続的な pub/sub (stream()), および少なくとも一度(at-least-once)の実行保証を持つワークキュー (queue()). これら三つはすべて、あなたのトランザクション内での INSERT であり、タスクの「送信」がビジネスロジックの書き込みと原子性を持って実行でき、ロールバックすると全て削除されることを可能にします。明確な目標は、ポーリングなしで NOTIFY/LISTEN セマンティクスを実現することであり、単一桁のミリ秒単位の反応時間を達成することです。既存の SQLite ファイル(ビジネスロジックを含む)を使用する場合、それは WAL 各コミットでワーカーを通知します。つまり、ほとんどのトリガーは何も起こらなくなります:代わりに、ワーカーは結果なしにメッセージ/キューを読み取りますのみになります。この「過剰なトリガー(overtriggering)」は意図的であり、プッシュ型セマンティクスと高速反応時間のためのトレードオフです。
WAL 専用設計 (WAL-only by design)
Honker は管理するすべてのデータベースに対して
journal_mode = WAL を必要とします。honker_bootstrap() は WAL モードではないファイルベースの DB では実行を拒否し、言語バインディングはデフォルトのオープンパスで PRAGMA journal_mode = WAL を設定します。
ワーカーはライフタイム全体にわたって読み取りビュー(WAL サブスクリプションチャンネル、リスナーイテレーター)を開いたままに保ちます。DELETE/TRUNCATE モードでは、ライターの独占ロックを取得し、アクティブなリーダは解放されるまでブロックされます。単一のワーカーが積極的に主張(claim)すると、システム全体の
enqueue() / notify() をシリアライズしてしまいます。WAL はリーダーとライターを共存させることができます。
.db-wal サイドカーは各コミットで成長し、チェックポイント時にのみ縮小します。ステータスポーリングにより、単調で曖昧さのない変化シグナルを得られます。DELETE モードの .db-journal サイドカーはトランザクション中に現れ、コミットすると消滅するため、ステータスポーリングのターゲットとしては不適切です。
wal_autocheckpoint = 10000 を使用すれば、WAL は各コミットごとにではなく、1 万ページごとに 1 回の fsync を実行します。スループットの向上の大部分はこのためです。
SQLite データベースが WAL モードに入らないことを必要とする場合(例:バックアップターゲットや、共有ファイルシステムでの .db-wal/.db-shm サイドカーを避けるため)、Honker は適切なツールではありません。通常の SQLite を使用し、NOTIFY/LISTEN セマンティクスなしで使用してください。ライブラリ/拡張モジュールは、SQLite の特性とシングルサーバーアーキテクチャに構築された小さな調整層です。
一つの .db + 一つの .db-wal がシステム全体です。あなたが既に利用している SQLite の利点(埋め込み、ローカル、堅牢、スナップショット可能)をすべて受け取ります。WAL モードは書き込み一人と並列リーダを可能にします。主張(Claim)は部分インデックスを使用した
UPDATE … RETURNING 一つです。承認(ack)は DELETE 一つです。WAL ファイルは各コミットで成長するため、(size, mtime) がクロスプロセスのコミットシグナルとなります。SQLite はワイヤプロトコルを持っていません。消費者は読み取りを主導しなければならず、サーバープッシュは不可能です。目覚めシグナル = ファイル変化 → SELECT です。トランザクションは安価であるため、ジョブ、イベント、通知は、db.transaction() ブロック内での「アウトバックス」型パターンの行として呼び出し側で存在します。
クロスプラットフォームで
stat(2) を使用し、技術的には優れた FSEvents/inotify/kqueue 代わりに使用しています。FSEvents は macOS ではサマードプロセスの書き込みをドロップするため、同じ Python プロセス内のリスナーとエンキューアーは互いを見ることができません。stat(2) は Linux/macOS/Windows で約 1ms の粒度で動作し、無視できる CPU コストをもたらします。コスト:カーネル通知との遅延比較で約 0.5ms です。単一マシーン、単一ライター。SQLite のロックは単一ホスト向けに設計されています。NFS を介して二つのサーバーが一つの .db に書き込むと破損します。ファイル単位でのシャーディングを行うか、Postgres に切り替えてください。
アーキテクチャ:目覚み経路 (Wake Path)
- データベースあたり一つ
スレッドが、.db-wal を 1ms ごとの(サイズ、更新時刻)でポーリングします。stat(2) - 変化 → バウンデッドチャンネルを各サブスクライバーに扇出(fan out)するティック。
- 各サブスクライバーは部分インデックスに対する
を実行し、行を返し、待機状態に戻ります。SELECT … WHERE id > last_seen - サブスクライバー 100 は 1 つの stat スレッドで対応可能。アイドルリスナーは SQL クエリを実行しません。
アイドルコスト はデータベースごとに 1ms あたり一つの
stat(2) です。目覚めシグナルがポーリングクエリではなくファイルステータス(file stat)であるため、サブスクライバーの数が無料でスケーリングします。SharedWalWatcher (honker-core) がポーリングスレッドを所有し、サブスクライバー ID でキー付けられたバウンデッド SyncSender<>() チャンネルを通じて N つのサブスクライバーに扇出します。各 db.wal_events() 呼び出しはサブスクライバーを登録し、ドロップハンドルを返します。したがって、ドロップされたリスナーはブリッジスレッドの rx.recv() -> Err を引き起こし、クリーンな状態で終了します。
キュースキーマ (Queue Schema)
- _honker_live: 待機中および処理中の行。
- 部分インデックス:
で(queue, priority DESC, run_at, id)
のもの。state IN ('pending','processing') - 主張(Claim)= このインデックスを使用した一つの
.UPDATE … RETURNING - 承認(Ack)= 一つの DELETE.
- 再試行枯渇 → _honker_dead (claim パスで永不読み込まれる).
状態に対する部分インデックスは、claim ホットパスが履歴サイズではなくワーキングセットサイズによって制限されることを意味します。10 万のデッド行を持つキューでもゼロの行列持ったキューと同じくらい速く主張できます。
主張イテレーター (Claim Iterator)
は、async for job in q.claim(id)
を介して一回に一つのジョブを返し。claim_batch(id, 1)
は独自のトランザクションの内の DELETE です。返り値は正直な bool です:主張が無効かどうか(True)、可視性ウィンドウが経過し、別のワーカーが回収した場合(False)。Job.ack()- 任意のプロセスからの WAL コミットで目覚め、5 秒のパラノイアポーリングのみフォールバック。
バッチワークの場合は、
claim_batch(worker_id, n) を明示的に呼び出し、queue.ack_batch(ids, worker_id) で承認してください。ライブラリはイテレーターの背後にバッチ処理を隠しません。API が知能(clever)を装おうとしない場合、トランザクションあたりのコストと最多一度(at-most-once)可視性セマンティクスがより理解しやすくなります。
トランザクショナルカップリング (Transactional Coupling)
はライターコネクションに登録された SQL スカラー関数です。notify()- 呼び出し側のオープントランザクション下の
への INSERTs._honker_notifications
およびqueue.enqueue(…, tx=tx)
も同様です。stream.publish(…, tx=tx)- ロールバックは、残りのトランザクションとともにジョブ/イベント/通知をドロップします。
これはデフォルトで、インストールする必要のないライブラリによるトランザクショナルなアウトバックスパターンです。ビジネス書き込みと副作用のエンキューが共にコミットまたはロールバックします。別々のディスパッチテーブルや別々のディスパッチャープロセスはありません:サイドエフェクト行はコミットされた行であり、WAL を監視するあらゆるプロセスは約 1ms の内にそれを拾い上げます。
過剰なトリガーが迅速であれば、ポーリングによる過剰なトリガーよりも良い。
- WAL 変化はそのデータベースのすべてのサブスクライバーを目覚めさせ、そのチャンネルがコミットしたものだけでなく。
- 各無駄な目覚め = 一つのインデックス付き SELECT (マイクロ秒).
- 見逃された目覚め = 沈黙した正しさのバグ.
ライブラリは、無視するリスナー十個を目覚めることを好みます。チャンネルフィルタリングはトリガー通知ではなく、SELECT パスで行われます。多くの小さなクエリは SQLite で効率的です。
リテンション (Retention)
- キュージョブは ack まで永続し、再試行枯渇行は _honker_dead に移動します。
- ストリームイベントは永続し、各名前付きコンシューマーは独自のオフセットを追跡します。
- Notify は fire-and-forget であり、自動剪定されません。
呼び出し側が各プリミティブのリテンションを選択します。
db.prune_notifications(older_than_s=…, max_keep=…) は、ライブラリのデフォルトからではなく、呼び出し側のコードでリテンションポリシーを可視化するツールです。
クラッシュ回復 (Crash Recovery)
- ロールバックはあなたのビジネス書き込みとともにジョブ/イベント/通知をドロップします(SQLite ACID)。
- トランザクション中の SIGKILL は安全です。WAL ロールバックにより、次のオープンで陳腐な状態が残りません。tests/test_crash_recovery.py で検証済み(サバプロセスが COMMIT 前の殺害、PRAGMA integrity_check == 'ok', 新しい通知の流れ依然として)。
- ワーカーがジョブ中クラッシュした場合、claim は visibility_timeout_s (デフォルト 300s) 後に有効期限切れになり、別のワーカーが回収します。attempts がカウントされます。max_attempts (デフォルト 3) 後、行は _honker_dead に移動します。
- プルン中にオフラインのリスナーは剪断されたイベントを見逃します。永続的な再生には
を使用し、パーコンシューマーオフセットを追跡します。db.stream()
ウェブフレームワークへの統合 (Wiring Into Your Web Framework)
Honker はフレームワークプラグインを提供しません。API は小さく、統合は数行の接着部分で済みます:
# FastAPI: リクエスト中にエンキューし、lifespan でワーカーを実行。 @app.on_event("startup") async def _start_workers(): async def worker_loop(): async for job in db.queue("emails").claim("worker"): await honker._worker.run_task( job, send_email, timeout=30, retries=3, backoff=2.0 ) app.state._worker = asyncio.create_task(worker_loop()) @app.post("/orders") async def create_order(order: dict): with db.transaction() as tx: tx.execute("INSERT INTO orders (user_id) VALUES (?)", [order["user_id"]]) db.queue("emails").enqueue({"to": order["email"]}, tx=tx) return {"ok": True}
SSE エンドポイントは
db.listen(channel) または db.stream(name).subscribe(...) を使用した ~30 行の async def stream(...): yield f"data: ...\n\n" です。Django/Flask の場合は、ワーカーを専用 CLI プロセスとして実行してください(Celery/RQ と同じパターン)。
パフォーマンス
最新のラップトップで毎秒数千ものメッセージを取り扱い、クロスプロセス目覚め遅延は 1ms ステータスポーリング間隔 (~M シリーズでの中央値 1–2ms) に制限されます。bench/wake_latency_bench.py と bench/real_bench.py を実行して自分のハードウェアで測定してください。
開発 (Development)
レイアウト:
- honker-core/: すべてのバインディングに共有される Rust rlib (in-tree, crates.io で公開)
- honker-extension/: SQLite ロード可能拡張モジュール (cdylib, crates.io で公開)
- packages/
- honker/: Python パッケージ (PyO3 cdylib + Queue/Stream/Outbox/Scheduler)
- honker-node/: napi-rs Node.js バインディング [git submodule]
- honker-rs/: エルゴノミック Rust ラッパー [git submodule]
- honker-go/: Go バインディング [git submodule]
- honker-ruby/: Ruby バインディング [git submodule]
- honker-bun/: Bun バインディング [git submodule]
- honker-ex/: Elixir バインディング [git submodule]
- honker-cpp/: C++ バインディング [git submodule]
- tests/: インテグレーションテスト (クロスパッケージ)
- bench/: ベンチマーク
- site/: honker.dev (Astro) [git submodule]
各バインディングリポジトリは独立して公開され (PyPI / npm / crates.io / Hex / RubyGems), ここでは git submodule としてピン止めされています。honker-core + honker-extension はすべてのバインディングが依存する共有基礎となるため、in-tree に存在します。
git clone --recursive でクローンするか、通常のクローン後に git submodule update --init --recursive を実行してください。
: デフォルト:rust + python + node (高速,~10 秒)make test
: ソーク + リアルタイム cron テスト (~2 分)make test-python-slow
: すべて(遅いマークを含む)make test-all
: PyO3 maturin develop + ロード可能拡張make build
python bench/wake_latency_bench.py --samples 500 python bench/real_bench.py --workers 4 --enqueuers 2 --seconds 15 python bench/ext_bench.py
カバレッジ (Coverage)
- ワンタイム:
(coverage.py + cargo-llvm-cov をインストール).make install-coverage-deps
: HTML レポートの両方を coverage/ に。make coverage
: honker Python パス.make coverage-python
: honker-core Rust ユニットテスト.make coverage-rust
Python カバレッジは Honker テストスイートの全体 (~92% of packages/honker/) を反映します。Rust カバレッジは cargo test のみ反映します。多くの honker_ops.rs パス (honker_enqueue, honker_claim_batch など) は Python テストスイート経由でしか行使されず、Rust レポートには現れません。組み合わせクロス言語カバレッジは非自明(PyO3 境界を越えた LLVM profile-data メルジング)であり、延期されています。
ライセンス
Apache 2.0。LICENSE を参照してください。