Back to Feed
article 3m read

One drag on my kanban board, two writes that disagree about history

The new board moves tracker tickets and planning-document tasks with the same drag, but tickets get a status change with an audit row while tasks rewrite their whole JSON document, with no history and a lost-update window I can't find a guard for.

Article DepthPractice

Flow

Gaps I could close

  1. 1Revision check

    Save the document only if nobody changed it since the read; otherwise reload.

  2. 2Save ticket changes together

    Status and history row land as one unit or not at all.

  3. 3Check the status on the server

    Reject values that are not one of the columns.

  4. 4Clear stamps on reopen

    Decide what a ticket moved back out of done should look like.

  5. 5Rows for tasks

    The bigger move that gives tasks the same history as tickets.

Every card on my new kanban board moves the same way. You drop it in a column and it jumps there. But behind that one drag are two server writes that work in very different ways. I only saw it when I read the commit back.

Tickets are rows, tasks live in JSON

Tickets come from outside trackers. Each one is its own row in the database, with a status and a few timestamps. Tasks come from my own planning documents. A document stores all its tasks inside one JSON column, and this commit gave each task an optional status. No status means backlog.

The board treats both as the same kind of card. When you drop one, the screen moves it right away and then calls the server. If the call fails, the card jumps back.

Tickets keep a history

When a ticket moves, the server writes the new status. It also stamps the first time the ticket starts, finishes, or gets canceled. Then it adds a separate row that says "moved from this column to that one." That row is the history I'd want for any time-in-review numbers.

Tasks rewrite the whole document

When a task moves, the server loads the whole document, changes one status, and saves the whole thing back. No history row. No stamps. Nothing says when a task got to done.

That full save can lose work, as far as I can tell. Say two people move two different tasks in the same document at about the same time. Both load the old copy. Each changes its own task. The second save was built from the old copy, so it puts the first task back where it was. Both people saw a success message. I haven't seen this happen, but nothing in the code stops it.

The cheapest guard I know is a revision number: save only if nobody changed the document since you read it. If they did, refuse and reload. The bigger answer is to give each task its own row, like tickets have.

There's a smaller oddity too. Task cards borrow their dates from the document, so moving one task makes every other task in it look just updated.

Tickets aren't perfect either

The ticket path has gaps of its own:

  • The status change and the history row aren't saved together, so the status can change with no history row.
  • The "finished" stamp never clears, even if a ticket moves back out of done.
  • Nothing checks the status value on the server.
  • Nothing tells the outside tracker about the move.

The commit has no tests, so everything here comes from reading the code.

Keep reading

Pick up a connected idea or branch into a nearby one.

5 paths forward
Related idea

Device limit counts first and saves later, and the test never sends two at once

The licensing code I shipped checks how many devices a key already has, then inserts the new one in a separate step, so two activations racing for the last slot can both get in. The test activates devices one at a time and can't see it, a device racing itself gets a server error, and a key-only check passes for keys never bound anywhere. I found this by reading, not by running anything concurrent.

via #concurrency · #state-management