Skip to content

Releases: ModelsLab/modelq-php

1.1

Choose a tag to compare

@adhikjoshi adhikjoshi released this 20 Aug 07:37
e99f85d

Two performance changes since 1.0, both aimed at the same cost: producers run in us-east4 while the ModelQ Redis is in Mumbai, ~271ms away, so every reply this library waits for before sending the next command lands on a caller's request path.

One round trip per enqueue (#5)

enqueue() issued five sequential writes — rPush ml_tasks, zAdd queued_requests, setex task:, zAdd task_history, setex task_history: — waiting for each reply in turn. Measured against 5,434 production requests joined between Redis and MongoDB, that gap is a flat 1.355–1.41s regardless of payload size: exactly five round trips.

They now go out as one pipeline. Redis still executes them in order; only the waiting is removed.

Ordering is corrected at the same time: the task's own state is written before the id is advertised on ml_tasks, so a worker can no longer pop a task whose task:{id} key does not exist yet.

The result poll backs off (#5)

Task::getResult() woke every 100ms and issued two GETs per wake. On a 30s wait against a Redis 271ms away that is ~100 round trips to learn nothing, with the caller's worker pinned for all of it.

The pair of GETs is now one pipelined read, and the interval starts at 100ms and doubles to a 1s ceiling.

wait polls before polls after
3s 30 6
30s 300 9

Backoff rather than a flat 1s keeps the fast endpoints fast — realtime_turbo runs in 1.18s at p50 and 100% of its tasks finish inside 10s, so a flat 1s poll would add up to a second to a one-second job.

Task history stops storing the payload a third time (#4)

Compatibility

No API breaks. Wire format unchanged — same five keys, same values, same scores. isCancelled() semantics preserved exactly, including a stored "0" still counting as cancelled.

Task::POLL_MIN_INTERVAL_US and Task::POLL_MAX_INTERVAL_US are public if you want to pin the cadence flat.

The now-unused private addToTaskHistory() was removed.

Full Changelog: 1.0...1.1

1.0

1.0

Choose a tag to compare

@adhikjoshi adhikjoshi released this 15 Aug 07:33

Liveness release. Two independent fixes, both merged off the 2026-08-15 outage.

fix: bound the Redis read (#3)

A dropped TCP connection could park a worker forever on a blocking read.

blPop(['ml_tasks'], 1) looked safe, but that 1 is the server's timeout — how long Redis holds the pop before answering. It says nothing about how long the client waits for that answer. phpredis defaults its read timeout to 0 (wait forever) and connect() was called with no timeouts, so when a connection was silently dropped the reply could never arrive and the read never returned.

  • connect() now passes CONNECT_TIMEOUT (5s) and READ_TIMEOUT (10s)
  • OPT_TCP_KEEPALIVE (60s) so the kernel probes an idle peer, guarded by defined() — needs phpredis 5+
  • the blocking pop uses BLPOP_TIMEOUT (1s), deliberately held below READ_TIMEOUT; invert that ordering and every idle poll aborts the read before Redis replies

Callers injecting their own Redis client are unaffected.

perf: stop reading every task_result payload to prune (#2)

pruneOldTaskResults() scanned the whole task_result:* keyspace and GET the multi-MB value of every key just to read a timestamp — to delete keys Redis already expires on its own. Measured on one production shard over 26 days: 196.7M SCAN + 228.8M GET to issue 2 DELs.

Upgrading

No API changes. The new constants are public const on ModelQ and can be overridden by subclassing if your network needs different bounds.

0.6

0.6

Choose a tag to compare

@adhikjoshi adhikjoshi released this 19 Jun 11:31

Fully drain a task on removeTaskFromQueue (cancel): always zRem queued_requests + sRem processing_tasks so a cancelled/given-up task is neither counted (queue_num/queue_time) nor retried, even if it already left ml_tasks.

0.5

0.5

Choose a tag to compare

@adhikjoshi adhikjoshi released this 26 Apr 07:50

Mirrors modelq python 1.0.11 fixes for production stuck-task and queued_requests leak.

  • worker_loop: persist startedAt on the Task object and write status='processing' on task:{id} so storeFinalTaskState keeps them.
  • requeueStuckProcessingTasks: fall back to queued_at when started_at is missing. Covers workers killed in the sAdd processing_tasks → setex task:{id} window.
  • storeFinalTaskState: zRem queued_requests on terminal state. Stops unbounded zset growth.

0.4

0.4

Choose a tag to compare

@devsahmy devsahmy released this 08 Jan 08:39
0f9cb37

What's Changed

New Contributors

Full Changelog: 0.3...0.4

0.3

0.3

Choose a tag to compare

@devsahmy devsahmy released this 29 Dec 09:35

Full Changelog: 0.2...0.3

0.2

0.2

Choose a tag to compare

@adhikjoshi adhikjoshi released this 25 Dec 09:22

Full Changelog: 0.1...0.2

0.1

0.1

Choose a tag to compare

@adhikjoshi adhikjoshi released this 22 Dec 04:43