Skip to content

modify depends reads a database fully per each non-pending task #4178

Description

@9484e0dd

Not sure how much this is related to #4121, and if this will be of any interest to @ashprice.

modify depends reads a database fully per each non-pending task.

dependencyIsCircular() calls tdb2.get(dep, current) for each depends edge.

Since #4127, TDB2::get() resolves pending UUIDs through _pending_index, but a non-pending UUID requires all_task_data().

My database has 1217 such edges from completed tasks out of 2087. Total size is 5578 tasks, 54 MB. Adding one dependency to a task with 69 completed tasks referenced through depends peaks at ~1 GB RSS over ~9.5 s. I suppose nothing is freed between dependency modifications in a command: one dependency 1005 MB, two 1935 MB, three 2945 MB. Fifty-four dependencies in a single modify therefore requires ~50 GB, which bricks my machine.

The script below creates a database with completed chains and a pending control, then measures a modify depends: against each.

import json
import os
import subprocess
import tempfile
import time
import uuid

TASK_BIN = os.environ.get("TASK", "task")
FILLER_COUNT = int(os.environ.get("FILLER", 2000))
ENTRY_DATE = "20200101T000000Z"


def new_uuid():
    return str(uuid.uuid4())


all_tasks = []
chain_heads = []


# Linear chain
def add_chain(length, status, name):
    link_uuids = []
    for i in range(length):
        link_uuids.append(new_uuid())

    for i in range(length):
        task = {
            "uuid": link_uuids[i],
            "description": name + str(i),
            "status": status,
            "entry": ENTRY_DATE,
        }
        if status == "completed":
            task["end"] = ENTRY_DATE
        if i + 1 < length:
            task["depends"] = [link_uuids[i + 1]]
        all_tasks.append(task)

    head_uuid = new_uuid()
    all_tasks.append({
        "uuid": head_uuid,
        "description": name,
        "status": "pending",
        "entry": ENTRY_DATE,
        "depends": [link_uuids[0]],
    })
    chain_heads.append((name, length, status, head_uuid))


for length in (10, 20, 40, 80):
    add_chain(length, "completed", "done" + str(length))
add_chain(80, "pending", "live80")

spare_uuid = new_uuid()
all_tasks.append({
    "uuid": spare_uuid,
    "description": "SPARE",
    "status": "pending",
    "entry": ENTRY_DATE,
})

# Filler tasks
for i in range(FILLER_COUNT):
    all_tasks.append({
        "uuid": new_uuid(),
        "description": "f" + str(i),
        "status": "pending",
        "entry": ENTRY_DATE,
    })


def run_task(args):
    started = time.monotonic()
    pid = os.fork()
    if pid == 0:
        devnull = os.open(os.devnull, os.O_WRONLY)
        os.dup2(devnull, 1)
        os.dup2(devnull, 2)
        try:
            os.execvp(TASK_BIN, [TASK_BIN, "rc.hooks=0", "rc.confirmation=off"] + args)
        except Exception:
            os._exit(127)
    _, _, usage = os.wait4(pid, 0)
    elapsed = time.monotonic() - started
    peak_rss_mb = usage.ru_maxrss / 1024.0
    return elapsed, peak_rss_mb


with tempfile.TemporaryDirectory() as data_dir, tempfile.TemporaryDirectory() as rc_dir:
    taskrc_path = os.path.join(rc_dir, "taskrc")
    os.environ["TASKDATA"] = data_dir
    os.environ["TASKRC"] = taskrc_path
    with open(taskrc_path, "w") as f:
        f.write("verbose=nothing\n")

    import_path = os.path.join(rc_dir, "db.json")
    with open(import_path, "w") as f:
        json.dump(all_tasks, f)
    run_task(["import", import_path])

    version = subprocess.run(
        [TASK_BIN, "--version"], capture_output=True, text=True
    ).stdout.strip()
    print(version + ": " + str(len(all_tasks)) + " tasks in DB")

    print("chain,len,status,time s,peak RSS MB")
    for name, length, status, head_uuid in chain_heads:
        elapsed, peak_rss_mb = run_task([head_uuid, "modify", "depends:" + spare_uuid])
        print("%s,%d,%s,%.2f,%.1f" % (name, length, status, elapsed, peak_rss_mb))

Metadata

Metadata

Assignees

No one assigned

    Labels

    topic:performanceIssues related to performancetype:bugSomething isn't working

    Type

    No type

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions