~bigbes/tarantool-etcd · events

n57cqmu7kjf0oqf11e34gufor0odjj5p · 228 rows

idissue_idevent_typeactorold_valuenew_valuecommentcreated_at
adf1fce8-4640-5c8d-b90c-4300167ebd09tarantool-etcd-70jlabel_addedEugene BlikhNULLNULLAdded label: milestone:m4-iproto-parity2026-05-21T14:37:18Z
ae83a00a-6439-5966-9658-40e00aa4292ctarantool-etcd-ozmcreatedEugene BlikhNULL2026-05-19T17:54:08Z
af3a800c-52ef-5ba7-b274-dccbc4d6fc31tarantool-etcd-ueecreatedEugene BlikhNULL2026-05-20T09:17:54Z
b48a75f5-2a8c-5d7f-8385-897dfe00e67atarantool-etcd-m0wcreatedEugene BlikhNULL2026-05-19T17:54:17Z
b4c20140-96dd-5fde-a7aa-9197af66b5a1tarantool-etcd-95dlabel_addedEugene BlikhNULLNULLAdded label: milestone:m3-performance2026-05-21T14:37:06Z
b5988b8e-2fc1-505a-a8d6-e20273e68e9ftarantool-etcd-0gvlabel_addedEugene BlikhNULLNULLAdded label: milestone:m4-iproto-parity2026-05-21T14:37:26Z
b626f9f2-b874-5719-bb14-2ce0d8ae9e87tarantool-etcd-3spupdatedEugene Blikh{"id":"tarantool-etcd-3sp","title":"LeaseKeepAlive fsyncs WAL on every renew","description":"On btrfs SSD bench (2026-05-19 disk run), LeaseKeepAlive/n_X drops from 4 234 ops/s (tmpfs) to 207 ops/s on tarantool while etcd holds at ~7 400 ops/s in both configs.\n\np50 goes from 224 µs (tmpfs) to 4 548 µs (disk) — exactly one btrfs fsync. Root cause at app/etcd/lease.lua:191:\n\n box.space.leases:update(id, {{'=', 'expiry_time', now + lease.ttl}})\n\nThe leases space is is_sync=true so every keep-alive does a full WAL append + fsync. Etcd's KeepAlive is an in-memory TTL bump with no disk write.\n\nFix sketch: split expiry into an in-memory map (refreshed on every KeepAlive) and persist only on grant/revoke. The expiry fiber should read from the in-memory map. Trade-off: leases may extend further into the future than the on-disk record after a crash, which is fine — clients have to re-keep-alive after reconnect anyway.\n\nSee docs/BENCH.md Linux x86_64 disk section (Open follow-ups).","status":"open","priority":2,"issue_type":"bug","owner":"bigbes@gmail.com","created_at":"2026-05-19T17:33:47Z","created_by":"Eugene Blikh","updated_at":"2026-05-19T17:33:47Z"}{"design":"GOAL: lease keep-alive must not touch the WAL. Mirror etcd's split — durable\nstate = {lease record, granted TTL, attached keys}; ephemeral leader-local\nstate = the expiry deadline (refreshed on every keep-alive, never persisted).\n\n=== STATE MODEL ===\nAdd a module-level in-memory map in app/etcd/lease.lua:\n local deadlines = {} -- [lease_id] = monotonic deadline (clock.monotonic()+ttl)\nThis is leader-local. Followers never keep-alive (the gRPC write gate routes\nLeaseKeepAlive to the leader), and only the leader runs the expiry fiber and\ncan revoke. So the countdown is structurally a leader concern.\n\nThe `leases` space (schema.lua:97-118) stays is_sync=true and keeps its\n{id, ttl, expiry_time, granted_ttl} format. The `expiry_time` FIELD and its\nTREE index (schema.lua:113) become vestigial — written once at grant, never\nthe source of liveness again. Leave them to avoid a space migration; an\noptional follow-up can drop the field+index. Nothing reads expiry_time after\nthis change.\n\n=== HANDLER CHANGES (app/etcd/lease.lua) ===\n1. grant() ~L70: after `box.space.leases:insert(...)`, seed\n `deadlines[id] = now + ttl`. Insert still persists (durable, required).\n2. keepalive() ~L191: REPLACE\n box.space.leases:update(id, {{'=', 'expiry_time', now + lease.ttl}})\n with\n deadlines[id] = clock.monotonic() + lease.ttl\n Keep the `box.space.leases:get(id)` existence check above it (cheap read,\n no fsync) — it supplies lease.ttl and preserves the NOT_FOUND-raises\n contract the gRPC stream handler depends on (see L169-171).\n3. revoke() ~L150: after `box.space.leases:delete(id)`, clear\n `deadlines[id] = nil`.\n4. time_to_live() ~L236: REPLACE `lease.expiry_time` read with `deadlines[id]`.\n If deadlines[id] is nil (lease loaded but not yet rebuilt), fall back to\n `clock.monotonic() + lease.granted_ttl`. remaining = max(0, floor(dl - now)).\n5. expiry_loop() L287-338: REPLACE the `box.space.leases.index.expiry_time`\n scan (L302-309) with a scan of `deadlines`: collect ids where\n `deadline \u003c= now`. Plain full-table scan is fine (numeric compares in Lua,\n runs every 500ms); a min-heap (etcd's leaseExpiredNotifier shape) is the\n optional optimization if lease counts get large. revoke() already nils the\n map entry on success.\n\n=== REBUILD ON PROMOTE ===\nAdd lease.M.rebuild_deadlines():\n clears `deadlines`, then for each tuple in box.space.leases:pairs() sets\n deadlines[tuple.id] = clock.monotonic() + tuple.ttl\nCall it in the RW branch of the box.watch('box.status', ...) callback in\napp/roles/etcd.lua (L341-353), immediately before lease.start_expiry().\nThis resets every inherited lease's deadline to a full-TTL grace period on the\nnew leader — exactly etcd's failover behavior.\n\n=== LATENT BUG THIS ALSO FIXES ===\nexpiry_time is currently computed as clock.monotonic()+ttl and REPLICATED.\nclock.monotonic() is process-local (relative to boot), so the persisted value\nis meaningless on any other node. Today a promoted replica's fiber scans\ninherited expiry_time values from a different monotonic timeline — could\nexpire leases instantly or never. Rebuild-on-promote with the new leader's own\nclock removes this entirely."}NULL2026-05-20T07:39:02Z
b6403489-01a6-5c93-bbf0-3d5892d7b3aftarantool-etcd-laclabel_addedEugene BlikhNULLNULLAdded label: milestone:m1-conformance2026-05-21T14:37:01Z
b6a99f2f-e9cc-5ff5-b1a8-807e832435f4tarantool-etcd-3c2createdEugene BlikhNULL2026-05-19T17:54:23Z
bb43477e-f05e-5f24-91b4-804a62100a69tarantool-etcd-35ylabel_addedEugene BlikhNULLNULLAdded label: milestone:m1-conformance2026-05-21T14:37:01Z
bb9963f5-e76e-5f17-b327-b7365865108btarantool-etcd-9ljlabel_addedEugene BlikhNULLNULLAdded label: milestone:m4-iproto-parity2026-05-21T14:37:28Z
bbee3101-7104-5281-be2d-363a697a6cd9tarantool-etcd-1dkcreatedEugene BlikhNULL2026-05-20T09:55:21Z
bbf149ce-2ac9-50c0-bdff-56477c0f2177tarantool-etcd-tf0createdEugene BlikhNULL2026-05-19T17:54:38Z
be2c2f97-97ea-57a7-bf62-190f95e0d293tarantool-etcd-acplabel_addedEugene BlikhNULLNULLAdded label: milestone:m4-iproto-parity2026-05-21T14:37:35Z
bee57122-e8b8-5849-b1f6-8ad55f71dff9tarantool-etcd-7kbcreatedEugene BlikhNULL2026-05-19T17:54:18Z
c294cd38-c0cc-5d5f-bc05-49ba86ef92aftarantool-etcd-8t0label_addedEugene BlikhNULLNULLAdded label: milestone:m4-iproto-parity2026-05-21T14:37:31Z
c2d692d3-e424-52b9-adb0-64d114117fbatarantool-etcd-0uscreatedEugene BlikhNULL2026-05-19T17:54:32Z
c3fabe08-1111-56b1-a5dc-6ca9ad403d7ctarantool-etcd-nwlcreatedEugene BlikhNULL2026-05-19T17:54:11Z
c5565f36-1950-57c0-b7af-460e2f17ec85tarantool-etcd-sy6label_addedEugene BlikhNULLNULLAdded label: milestone:m4-iproto-parity2026-05-21T14:37:33Z
c59c84bd-94d1-5ad4-9166-ee94910aadb8tarantool-etcd-8ssclaimedEugene Blikh{"id":"tarantool-etcd-8ss","title":"Example 2: 3-node EE replicaset self-hosting its own etcd config (file → etcd migration)","description":"Add examples/tarantool-ee-self-hosted-etcd: boot a 3-instance EE replicaset (election failover) from a local cluster-config.yaml where every node runs app.roles.etcd (replicated KV store, read_pref=any). Seed the cluster's OWN etcd store with that config, then rolling-restart each instance to bootstrap from TT_CONFIG_ETCD_* (no --config). Document the cold-boot circularity: self-hosted etcd-config is HA under rolling restart but needs the local file retained as a cold-boot seed. Justfile + README + cluster-config.yaml.","status":"open","priority":2,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-05-20T11:16:17Z","created_by":"Eugene Blikh","updated_at":"2026-05-20T11:16:17Z"}{"assignee":"Eugene Blikh","status":"in_progress"}NULL2026-05-20T14:16:28Z
c5ae6782-aad5-5691-adc3-31d2a96a7f58tarantool-etcd-35ycreatedEugene BlikhNULL2026-05-20T09:31:01Z
c5d383f7-2c80-5ebb-a284-6ac784eb37b0tarantool-etcd-dkrlabel_addedEugene BlikhNULLNULLAdded label: milestone:m5-clustering2026-05-21T14:37:36Z
c623372a-f8dd-5da8-a534-b07f01f3242atarantool-etcd-hx9label_addedEugene BlikhNULLNULLAdded label: milestone:m4-iproto-parity2026-05-21T14:37:20Z
c62dbc43-dc48-5043-bae5-0ce8d95e636ctarantool-etcd-bdflabel_addedEugene BlikhNULLNULLAdded label: milestone:m2-production2026-05-21T14:37:04Z
c69b7e72-02c3-59d6-b136-100c1d1da170tarantool-etcd-kbclabel_addedEugene BlikhNULLNULLAdded label: milestone:m1-conformance2026-05-21T14:37:00Z
c6f5d06b-8dae-59e0-a2e5-c65408ebef48tarantool-etcd-unwlabel_addedEugene BlikhNULLNULLAdded label: milestone:m2-production2026-05-21T14:37:03Z
c9410225-7f9d-5a59-a370-3a2c4b2dda88tarantool-etcd-g1alabel_addedEugene BlikhNULLNULLAdded label: milestone:m4-iproto-parity2026-05-21T14:37:19Z
c9777a9d-45a2-50fa-8071-bfc97ba0374ftarantool-etcd-tt6createdEugene BlikhNULL2026-05-20T09:17:56Z
cd90161d-49e5-5e89-9e0f-197bbd584d7dtarantool-etcd-m1nlabel_addedEugene BlikhNULLNULLAdded label: milestone:m4-iproto-parity2026-05-21T14:37:10Z
cf5dbef3-f48b-5f9b-b3da-8d431e68bb67tarantool-etcd-j1clabel_addedEugene BlikhNULLNULLAdded label: milestone:m1-conformance2026-05-21T14:37:01Z
cf901dca-197b-5b51-9755-0f738ae2a0a0tarantool-etcd-8sscreatedEugene BlikhNULL2026-05-20T14:16:16Z
d0d26b03-32e3-55e5-88d4-eff502e16069tarantool-etcd-7eylabel_addedEugene BlikhNULLNULLAdded label: milestone:m5-clustering2026-05-21T14:37:43Z
d19ea0c4-3b5c-5e52-8592-0b6fef2ccc65tarantool-etcd-w6zcreatedEugene BlikhNULL2026-05-19T17:54:44Z
d26de571-bf81-5a27-b0c2-e5968bca9515tarantool-etcd-8ssclosedEugene BlikhExample written and verified end-to-end on Tarantool EE 3.7.0.NULL2026-05-20T14:38:01Z
d2ab7f78-bda1-52e0-a415-8de848007ebbtarantool-etcd-9cdlabel_addedEugene BlikhNULLNULLAdded label: milestone:m4-iproto-parity2026-05-21T14:37:31Z
d4635d23-1422-5205-90b7-05ed702f3248tarantool-etcd-00hlabel_addedEugene BlikhNULLNULLAdded label: milestone:m5-clustering2026-05-21T14:37:36Z
d6dd3e6d-c3ef-5a61-8a41-5c6200b30570tarantool-etcd-6znlabel_addedEugene BlikhNULLNULLAdded label: milestone:m5-clustering2026-05-21T14:37:37Z
d72bc321-8c58-59d0-be96-abac8ee01e76tarantool-etcd-axslabel_addedEugene BlikhNULLNULLAdded label: milestone:m5-clustering2026-05-21T14:37:40Z
d74056a1-cd5a-55d8-a77b-038f7a4c3d21tarantool-etcd-laccreatedEugene BlikhNULL2026-05-19T16:34:01Z
d7743f7a-b995-5c17-b399-a342bab6c8f4tarantool-etcd-dmtlabel_addedEugene BlikhNULLNULLAdded label: milestone:m4-iproto-parity2026-05-21T14:37:08Z
d9a35ca3-b40e-5763-b608-9bca13e7efb3tarantool-etcd-ozmlabel_addedEugene BlikhNULLNULLAdded label: milestone:m4-iproto-parity2026-05-21T14:37:27Z
d9f13953-6e8c-5ffd-8c6b-da0cd3ba51f5tarantool-etcd-0gncreatedEugene BlikhNULL2026-05-20T09:35:53Z
da619bb2-c632-55c3-b7e7-25e992a63cd1tarantool-etcd-cnjlabel_addedEugene BlikhNULLNULLAdded label: milestone:m5-clustering2026-05-21T14:37:41Z
dc8f69d6-ab01-5b66-b0aa-13133ffde228tarantool-etcd-iz4label_addedEugene BlikhNULLNULLAdded label: milestone:m4-iproto-parity2026-05-21T14:37:23Z
de4a681f-3928-51a4-bbfe-9676bdd4ea57tarantool-etcd-3dklabel_addedEugene BlikhNULLNULLAdded label: milestone:m4-iproto-parity2026-05-21T14:37:34Z
deec1cde-92e1-56bf-beda-3ddab9481685tarantool-etcd-40slabel_addedEugene BlikhNULLNULLAdded label: milestone:m4-iproto-parity2026-05-21T14:37:30Z
dfbd732f-fa51-5493-bd6a-8773b1badee5tarantool-etcd-p3slabel_addedEugene BlikhNULLNULLAdded label: milestone:m4-iproto-parity2026-05-21T14:37:11Z
e2ea8dd7-f559-5778-8039-5ac01d6c9c02tarantool-etcd-3spclaimedEugene Blikh{"id":"tarantool-etcd-3sp","title":"LeaseKeepAlive fsyncs WAL on every renew","description":"On btrfs SSD bench (2026-05-19 disk run), LeaseKeepAlive/n_X drops from 4 234 ops/s (tmpfs) to 207 ops/s on tarantool while etcd holds at ~7 400 ops/s in both configs.\n\np50 goes from 224 µs (tmpfs) to 4 548 µs (disk) — exactly one btrfs fsync. Root cause at app/etcd/lease.lua:191:\n\n box.space.leases:update(id, {{'=', 'expiry_time', now + lease.ttl}})\n\nThe leases space is is_sync=true so every keep-alive does a full WAL append + fsync. Etcd's KeepAlive is an in-memory TTL bump with no disk write.\n\nFix sketch: split expiry into an in-memory map (refreshed on every KeepAlive) and persist only on grant/revoke. The expiry fiber should read from the in-memory map. Trade-off: leases may extend further into the future than the on-disk record after a crash, which is fine — clients have to re-keep-alive after reconnect anyway.\n\nSee docs/BENCH.md Linux x86_64 disk section (Open follow-ups).","design":"GOAL: lease keep-alive must not touch the WAL. Mirror etcd's split — durable\nstate = {lease record, granted TTL, attached keys}; ephemeral leader-local\nstate = the expiry deadline (refreshed on every keep-alive, never persisted).\n\n=== STATE MODEL ===\nAdd a module-level in-memory map in app/etcd/lease.lua:\n local deadlines = {} -- [lease_id] = monotonic deadline (clock.monotonic()+ttl)\nThis is leader-local. Followers never keep-alive (the gRPC write gate routes\nLeaseKeepAlive to the leader), and only the leader runs the expiry fiber and\ncan revoke. So the countdown is structurally a leader concern.\n\nThe `leases` space (schema.lua:97-118) stays is_sync=true and keeps its\n{id, ttl, expiry_time, granted_ttl} format. The `expiry_time` FIELD and its\nTREE index (schema.lua:113) become vestigial — written once at grant, never\nthe source of liveness again. Leave them to avoid a space migration; an\noptional follow-up can drop the field+index. Nothing reads expiry_time after\nthis change.\n\n=== HANDLER CHANGES (app/etcd/lease.lua) ===\n1. grant() ~L70: after `box.space.leases:insert(...)`, seed\n `deadlines[id] = now + ttl`. Insert still persists (durable, required).\n2. keepalive() ~L191: REPLACE\n box.space.leases:update(id, {{'=', 'expiry_time', now + lease.ttl}})\n with\n deadlines[id] = clock.monotonic() + lease.ttl\n Keep the `box.space.leases:get(id)` existence check above it (cheap read,\n no fsync) — it supplies lease.ttl and preserves the NOT_FOUND-raises\n contract the gRPC stream handler depends on (see L169-171).\n3. revoke() ~L150: after `box.space.leases:delete(id)`, clear\n `deadlines[id] = nil`.\n4. time_to_live() ~L236: REPLACE `lease.expiry_time` read with `deadlines[id]`.\n If deadlines[id] is nil (lease loaded but not yet rebuilt), fall back to\n `clock.monotonic() + lease.granted_ttl`. remaining = max(0, floor(dl - now)).\n5. expiry_loop() L287-338: REPLACE the `box.space.leases.index.expiry_time`\n scan (L302-309) with a scan of `deadlines`: collect ids where\n `deadline \u003c= now`. Plain full-table scan is fine (numeric compares in Lua,\n runs every 500ms); a min-heap (etcd's leaseExpiredNotifier shape) is the\n optional optimization if lease counts get large. revoke() already nils the\n map entry on success.\n\n=== REBUILD ON PROMOTE ===\nAdd lease.M.rebuild_deadlines():\n clears `deadlines`, then for each tuple in box.space.leases:pairs() sets\n deadlines[tuple.id] = clock.monotonic() + tuple.ttl\nCall it in the RW branch of the box.watch('box.status', ...) callback in\napp/roles/etcd.lua (L341-353), immediately before lease.start_expiry().\nThis resets every inherited lease's deadline to a full-TTL grace period on the\nnew leader — exactly etcd's failover behavior.\n\n=== LATENT BUG THIS ALSO FIXES ===\nexpiry_time is currently computed as clock.monotonic()+ttl and REPLICATED.\nclock.monotonic() is process-local (relative to boot), so the persisted value\nis meaningless on any other node. Today a promoted replica's fiber scans\ninherited expiry_time values from a different monotonic timeline — could\nexpire leases instantly or never. Rebuild-on-promote with the new leader's own\nclock removes this entirely.","acceptance_criteria":"- keepalive issues ZERO WAL writes: box.info.lsn delta == 0 across N successive\n M.keepalive() calls on the same lease.\n- BenchmarkLeaseKeepAlive/n_* on btrfs SSD (work.lab.local, real-disk run)\n recovers from 207 ops/s to within ~2x of etcd (~4000+ ops/s, near the tmpfs\n baseline). Re-run: TMPDIR=$HOME/bench-data go test -run=^$ \\\n -bench=^BenchmarkLeaseKeepAlive$ -benchtime=5s ./bench/\n- Lease still expires correctly: grant short TTL, no keep-alive, attached keys\n are deleted by the fiber after TTL elapses; keep-alive before TTL prevents it.\n- TimeToLive reflects the latest keep-alive (remaining resets to ~ttl after a\n renew).\n- Promote rebuilds deadlines: a freshly-promoted leader expires inherited\n leases using its own monotonic clock (no instant-expiry, no never-expiry).\n- All existing lease conformance cells (tarantool, tarantool_json,\n tarantool_rs3) and Lua lease tests pass.","notes":"TESTS (add Lua-side tests pinning the fix per the project's regression rule):\n- no-WAL: assert box.info.lsn unchanged across keepalive calls.\n- liveness: keepalive refreshes deadline (TimeToLive resets, lease survives).\n- promote rebuild: use the box.cfg{read_only=true}...{read_only=false}+\n box.ctl.promote() in-process follower-simulation pattern (see role_test.lua\n group role/write_gates_in_process) to verify rebuild_deadlines repopulates.\n- cross-node: optionally exercise via the replicaset harness that a promoted\n replica expires an inherited lease.\nConformance already covers cross-wire correctness; this is about the Lua unit\npins + the bench recovery number.\n\nDOCS: update docs/BENCH.md Linux x86_64 disk section once re-benched; remove\nthe LeaseKeepAlive \"bug\" annotation from the headline table.\n\nSCOPE NOTE: leave the expiry_time field + index in place (no migration). Only\nthe keepalive write path and the fiber's liveness source change.","status":"open","priority":2,"issue_type":"bug","owner":"bigbes@gmail.com","created_at":"2026-05-19T17:33:47Z","created_by":"Eugene Blikh","updated_at":"2026-05-20T04:39:23Z"}{"assignee":"Eugene Blikh","status":"in_progress"}NULL2026-05-20T15:46:48Z
e315dfd4-c556-5638-b364-ccc96f555f16tarantool-etcd-q5fcreatedEugene BlikhNULL2026-05-20T09:45:16Z
e372b6b6-bab8-579f-885b-293bd18626bdtarantool-etcd-sqvlabel_addedEugene BlikhNULLNULLAdded label: milestone:m4-iproto-parity2026-05-21T14:37:11Z