Feature tplog - #124
Open
ascottDI wants to merge 3 commits into
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
di.tplog
Summary
Adds
di.tplog, adi.*module for tickerplant log files: create/open a log, append to it, roll tothe next day, replay it on startup, and repair a corrupt one. It is the modular replacement for the
inline log handling in TorQ's
code/processes/tickerplant.q(.u.ld/.u.endofday) together withthe recovery utilities in
code/common/tplogutils.q, folded into a single import surface.Self-contained — no hard
usedependencies; the one runtime dependency, a logger, is injected viainit, so the module loads and is tested standalone.Motivation
TorQ keeps tickerplant log handling in two places: the create/append/roll/replay code inline in
tickerplant.q, and the corruption check/repair intplogutils.q. Both the tickerplant (whichwrites and rolls the log) and a replaying subscriber (which reads and, if needed, repairs it) need
these operations, so the extraction brings them under one module rather than leaving consumers to
import two partial pieces. The logic is re-expressed on the framework's conventions — an injected
logger, module-local state, strict input validation — while preserving the observable behaviour.
Design
No handler involvement
di.tplogregisters no.z.*handlers. It is called directly by the tickerplant(
open/write/roll) and by subscribers on startup (replay/replayupto), so it sits outside thedi.handlersobserver/decider model and takes no handler dependency.Corruption detection on KDB-X
The corruption handling was written against measured KDB-X
-11!behaviour, which differs fromclassic kdb+ and from the assumptions in the TorqX POC:
-11!(-2;logfile)counts a clean log without runningupd, and throws on any corruption — it doesnot return the classic
(goodcount;bytes)pair. Corruption is detected by trapping that throw(
corruptp), andopencounts with-11!(-2)before replaying, so a corrupt log fails fast beforeany partial replay mutates state.
-11!(-1;logfile)counts too but executesupd, so it is unsafe for detection and is not used.replayandreplayuptocheck with the non-executingcorruptp, repair to a.goodfile ifrequired (a byte-scan that never calls
upd), then replay once — so a good message before acorruption point is never processed twice. A naive trap-and-retry would partially replay before
throwing and then replay again.
Dependencies (injected via
init)`log{[ctx;msg]}functions with at leastinfo,warn,errorinitvalidates the dependency strictly and signals immediately if it is missing or malformed; thereis no fallback.
di.log'slogdictlog(aninfo..fataldict) is a conforming superset of the required keys and is passed through without adaptation. There are no config keys and no hardusedependencies, sodeps.q` is empty.Public API
init[deps]logname[dir;date]dirstring anddate(<dir>/tp<date>).open[dir;date]upd, or fail fast if corrupt. Returns(handle;count).write[handle;msg]roll[handle;dir;olddate]olddate+1log. Returns(handle;count).replay[logfile]upd, repairing first if corrupt. Returns the count.replayupto[logfile;n]nmessages only (repair-aware).check[logfile]logfileif usable, else a repaired`<logfile>.good.repair[logfile]`<logfile>.good.version"0.1.0").Public functions validate their argument types and route failures through a log-then-signal helper.
getapimeta[]exposes the callable API for central registration withdi.api; the framework plumbing(
init,getapimeta,version) is intentionally excluded.Changes from the TorQ implementation
tickerplant.q(inline) andtplogutils.q; consumers need both, so they are folded into one import surface.-11!opencounts with the non-executing-11!(-2)before replaying.checkdropslastmsgtoreplay-11!(-2)no longer returns; the argument would be dead weight, sochecktakes just the logfile and repairs on any corruption..lg.*calls; thelogdependency is required, with no stdout fallback..goodwrite handle open; it is closed after the scan.Testing
test.csv/test.q(k4unit), 25 checks, hermetic (temp log files under/tmp, cleaned upafterwards). A capturing logger is injected so the log contract is asserted end to end.
Coverage: the metadata/version contract; strict
initdependency validation (afailrow per guard);public-input validation;
logname; the open/write/roll lifecycle; fail-fastopenon a corrupt log;replayrecovering a corrupt log while processing each recovered message exactly once (thedouble-processing regression);
replayuptoreplaying only the firstn; andcheck/repairon cleanand corrupt logs, asserting the warning is logged.
Limitations
repairis tuned to the(upd;trade;…)message shape inherited fromtplogutils; logs of othertables are recovered only if their messages share that prefix. Corruption detection is
schema-agnostic — only the repair byte-scan is trade-specific.
<dir>/tp<date>, one log per date); sharing a directory betweenmultiple logical logs would need a prefix argument on
logname.Files
The module version lives in a plain-text
VERSIONfile, read ininit.q(version:trim first read0\:::VERSION) and exported fordi.depcheck, matching the convention used bydi.serversanddi.clienttracking`.