smartbotic-database.service 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. [Unit]
  2. Description=Smartbotic Database Service
  3. After=network.target
  4. [Service]
  5. # v1.7.5: Type=notify so dependents block at startup until the service is
  6. # *actually* ready (recovery complete + migrations applied + gRPC listening),
  7. # not just "process spawned." Pre-1.7.5 this was Type=simple, which made
  8. # `After=smartbotic-database.service` on consumers ineffective: tools,
  9. # gateway, server etc. would launch immediately and silently fall back to
  10. # defaults when their startup DB reads timed out against an in-recovery
  11. # database. The service code already calls `sd_notify(READY=1)` at the
  12. # correct point in `service/src/main.cpp` after `service.start()`; with
  13. # Type=notify systemd actually waits for that signal before considering
  14. # the unit active and unblocking dependents.
  15. Type=notify
  16. NotifyAccess=main
  17. # v1.8.1: Cap glibc per-thread arenas to two. With Type=notify + threads
  18. # in eviction / WAL sync / snapshot / gRPC / replication, glibc allocates
  19. # `8 × num_cpus` arenas by default and small frees go on per-thread
  20. # free-lists that never `madvise(MADV_DONTNEED)`. On Zoe (22 threads) the
  21. # committed heap drifted 6.5 GB above the 5 GB the DB itself was tracking
  22. # — a fragmentation gap large enough to OOM-kill the process every ~3h
  23. # even though `estimatedMemoryBytes_` was correct. Two arenas is enough
  24. # for the writer + reader thread split; the small extra contention is
  25. # negligible vs. the RSS savings (measured 7.97 → 5.57 GB on Zoe).
  26. # Operators can override per-deployment by setting MALLOC_ARENA_MAX
  27. # elsewhere; the binary also calls `mallopt(M_ARENA_MAX, 2)` at startup
  28. # as a fallback for non-systemd deploys.
  29. Environment="MALLOC_ARENA_MAX=2"
  30. # v1.9.4: tune jemalloc (which the binary links against, overriding glibc
  31. # malloc/free). `background_thread:true` starts a dedicated thread that
  32. # decays dirty pages on a wallclock timer regardless of allocation
  33. # pressure — fixes the "trim never reclaims because the trailing chunk
  34. # is always in use" pattern we hit with glibc under sustained gRPC
  35. # load. `dirty_decay_ms:1000` says hold idle dirty pages for at most
  36. # one second before madvising them back to the OS — generous enough to
  37. # absorb workload bursts, tight enough to keep RSS close to working set.
  38. # `muzzy_decay_ms:1000` same idea for pages glibc-equivalent would call
  39. # "muzzy" (released but recoverable). Ignored on glibc.
  40. Environment="MALLOC_CONF=background_thread:true,dirty_decay_ms:1000,muzzy_decay_ms:1000"
  41. # Recovery on a large dataset (700k+ docs, multi-MB WAL) routinely takes
  42. # 60–90s. Default systemd start timeout (90s) is too tight; bump to 5 min.
  43. # The service emits `EXTEND_TIMEOUT_USEC=` during long recovery anyway, but
  44. # the static cap is the safety net.
  45. TimeoutStartSec=300
  46. User=smartbotic-db
  47. Group=smartbotic-db
  48. ExecStart=/usr/bin/smartbotic-database --config /etc/smartbotic-database/config.json
  49. WorkingDirectory=/var/lib/smartbotic-database
  50. Restart=on-failure
  51. RestartSec=3
  52. # v1.8.2: stop watchdog must cover the final-on-shutdown snapshot. On Zoe
  53. # (1.85M docs / ~5 GB tracked) serialize takes ~70 s. Pre-1.8.2 this was
  54. # 30 s, so `systemctl restart` always SIGKILLed mid-write — peak RSS hit
  55. # 11 GB during the doomed serialize, the new snapshot never made it to
  56. # disk, and the next boot fell back to WAL-only replay. The shutdown path
  57. # also calls `EXTEND_TIMEOUT_USEC` from `DatabaseService::stop()` so this
  58. # static cap is a safety net, not the primary control.
  59. TimeoutStopSec=300
  60. LimitNOFILE=65536
  61. [Install]
  62. WantedBy=multi-user.target