aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2021-03-15 18:18:41 +0300
committerEvgeny Zinoviev <me@ch1p.io>2021-03-16 02:15:45 +0300
commitd577d4bd5546b5e2cc9aff20617093565b5a8034 (patch)
treea284664b908c449218abb71974a4fd5c87d405e6
parentfbb9e65538e7f830996a1132c7f425c0e22bea19 (diff)
more documentation work... still not finished
-rw-r--r--README.md634
1 files changed, 430 insertions, 204 deletions
diff --git a/README.md b/README.md
index a158235..4df352e 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
# jobd
-**jobd** is a simple job queue daemon with persistent queue storage, written in
-Node.JS. It uses a MySQL table as a storage backend (for queue input and output).
+**jobd** is a simple job queue daemon that works with persistent queue storage.
+It uses a MySQL table as a storage backend (for queue input and output).
-Currently, MySQL is the only supported storage type, but other backends might be
-easily supported.
+Currently, MySQL is the only supported storage. Other backends may be easily
+supported though.
It is by design that jobd never adds nor deletes jobs from storage. It only
reads (when a certain request arrives) and updates them (during execution, when
@@ -17,7 +17,7 @@ jobd consists of 2 parts:
hosts. Each jobd instance may have unlimited number of queues (called "targets"),
each with its own concurrency limit.
-2. **jobd-master** is a "master" or "cetral" daemon that simplifies control over
+2. **jobd-master** is a "master" or "central" daemon that simplifies control over
many jobd instances. There should be only one instance of jobd-master running.
jobd-master is not required for jobd workers to work (they can work without it),
but it's very very useful.
@@ -31,158 +31,232 @@ time already, and proven to be stable and efficient.
## Table of Contents
- [How it works](#how-it-works)
-- [Protocol](#protocol)
- - [Request Message](#request-message)
- - [jobd request types](#jobd-request-types)
- - [jobd-master request types](#jobd-master-request-types)
- - [Response Message](#response-message)
- - [Ping and Pong Messages](#ping-and-pong-messages)
+ - [Targets](#targets)
+ - [Creating jobs](#creating-jobs)
+ - [Launching jobs](#launching-jobs)
+ - [Launching background jobs](#launching-background-jobs)
+ - [Launching manual jobs](#launching-manual-jobs)
+ - [Using jobd-master](#using-jobd-master)
- [Implementation example](#implementation-example)
- [Installation](#installation)
- [Usage](#usage)
- - [systemd](#systemd)
- - [supervisor](#supervisor)
- - [Other notes](#other-notes)
+ - [systemd](#systemd)
+ - [supervisor](#supervisor)
+ - [Other notes](#other-notes)
- [Configuration](#configuration)
- - [jobd](#jobd)
- - [jobd-master](#jobd-master)
- - [jobctl](#jobctl)
-- [MySQL setup](#mysql-setup)
+ - [jobd](#jobd)
+ - [jobd-master](#jobd-master)
+ - [jobctl](#jobctl)
- [Clients](#clients)
- - [PHP](#php)
+ - [PHP](#php)
+- [Protocol](#protocol)
+ - [Request Message](#request-message)
+ - [jobd requests](#jobd-requests)
+ - [poll(targets: string[])](#polltargets-string)
+ - [pause(targets: string[])](#pausetargets-string)
+ - [continue(targets: string[])](#continuetargets-string)
+ - [status()](#status)
+ - [run-manual(ids: int[])](#run-manualids-int)
+ - [add-target(target: string, concurrency: int)](#add-targettarget-string-concurrency-int)
+ - [remove-target(target: string)](#remove-targettarget-string)
+ - [set-target-concurrency(target: string, concurrency: int)](#set-target-concurrencytarget-string-concurrency-int)
+ - [jobd-master requests](#jobd-master-requests)
+ - [register-worker(targets: string[])](#register-workertargets-string)
+ - [poke(targets: string[])](#poketargets-string)
+ - [pause(targets: string[])](#pausetargets-string-1)
+ - [continue(targets: string[])](#continuetargets-string-1)
+ - [status(poll_workers=false: bool)](#statuspoll_workersfalse-bool)
+ - [run-manual(jobs: {id: int, target: string}[])](#run-manualjobs-id-int-target-string)
+ - [Response Message](#response-message)
+ - [Ping and Pong Messages](#ping-and-pong-messages)
- [TODO](#todo)
- [License](#license)
## How it works
-To be written.
-
-## Protocol
-
-By default, jobd and jobd-master listen on TCP ports 7080 and 7081 respectively,
-ports can be changed in a config.
-
-jobd has been created with an assumption that it'll be used in more-or-less
-trusted environments (LAN, or, at least, servers within one data center) so no
-encryption nor authentication mechanisms have been implemented. All traffic
-between jobd and clients flow in plain text. You can protect a jobd instance with
-a password though, so at least basic password-based authorization is supported.
-
-Both daemons receive and send Messages. Each message is followed by `EOT` (`0x4`)
-byte which indicates an end of a message. Clients may send and receive multiple
-messages over a single connection. Usually, it's the client who must close the
-connection, when it's not needed anymore. A server, however, may close the
-connection in some situations (invalid password, server error, etc).
-
-Messages are encoded as JSON arrays with at least one item, representing
-the message type:
-```
-[TYPE]
+### Targets
+
+Every jobd instance has its own set of queues, called **targets**. A name of a
+target is an arbitrary string, the length of which should be limited by the size
+of `target` field in the MySQL table.
+
+Each target has its own concurrency limit (the maximum number of jobs that may
+be executed simultaneously). Targets are loaded from the config at startup, and
+also may be added or removed at runtime, by
+[`add-target(target: string, concurrency: int)`](#add-targettarget-string-concurrency-int)
+and [`remove-target(target: string)`](#remove-targettarget-string) requests.
+
+The purpose of targets is to logically separate jobs of different kinds by putting
+them in different queues. For instance, targets can be used to simulate jobs
+priorities:
+```ini
+[targets]
+low = 5
+normal = 5
+high = 5
```
-If a message of specific type has some data, it's placed as a second item:
-```
-[TYPE, DATA]
+The config above defines three targets (or three queues), each with a concurrency
+limit of `5`.
+
+Or, let's imagine a scenario when you have two kinds of jobs: heavy,
+resource-consuming, long-running jobs (like video processing) and light, fast
+and quick jobs (like sending emails). In this case, you could define two targets,
+like so:
+```ini
+[targets]
+heavy = 3
+quick = 20
```
-Type of `TYPE` is integer. Supported types are:
-
-- `0`: Request
-- `1`: Response
-- `2`: Ping
-- `3`: Pong
-
-
-### Request Message
-
-`DATA` is a JSON object with following keys:
+This config would allow you to run at most 3 heavy and up to 20 quick jobs
+simultaneously.
+
+> :thought_balloon: The approach of having different targets (queues) for different kinds
+> of jobs is better than having a single queue with each job having a "priority".
+>
+> Imagine you had a single queue with maximum number of simultaneously running
+> jobs set to, say, 20. What would happen if you'd add a new job, even with the
+> highest priority possible, if there's already 20 slow jobs running? No matter
+> how high the priority of new job is, it would have to wait.
+>
+> By defining different targets, jobd allows you to create dedicated queues for
+> such jobs, making sure there's always a room for high-priority tasks to run
+> them as early as possible.
+
+### Creating jobs
+
+Each job is described by one record in the MySQL table. Here is a table scheme
+with a minimal required set of fields:
+```mysql
+CREATE TABLE `jobs` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `target` char(16) NOT NULL,
+ `time_created` int(10) UNSIGNED NOT NULL,
+ `time_started` int(10) UNSIGNED NOT NULL DEFAULT 0,
+ `time_finished` int(10) UNSIGNED NOT NULL DEFAULT 0,
+ `status` enum('waiting','manual','accepted','running','done','ignored') NOT NULL DEFAULT 'waiting',
+ `result` enum('ok','fail') DEFAULT NULL,
+ `return_code` tinyint(3) UNSIGNED DEFAULT NULL,
+ `sig` char(10) DEFAULT NULL,
+ `stdout` mediumtext DEFAULT NULL,
+ `stderr` mediumtext DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `status_target_idx` (`status`, `target`, `id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+```
-- **`no`** *(**required**, int)* — unique (per connection) request number. Clients
- can start counting request numbers from one (`1`) or from any other random number.
- Each subsequent request should increment this number by 1. Note that zero (`0`)
- is reserved.
-- **`type`** *(**required**, string)* — request type. Supported request types for
- jobd and jobd-master are listed below.
-- **`data`** *(object)* — request arguments (if needed): an object, whose keys
- and values represent argument names and values.
-- **`password`** *(string)* — a password, for password-protected instances. Only
- needed for first request.
-
-Example (w/o trailing `EOT`):
+As you can see:
+1. Each job has a unique ID. You don't need to care about assigning IDs because
+ `AUTO_INCREMENT` is used.
+2. Each job is associated with some target, or, in other words, is put to
+ some queue. More about targets in the [Targets](#targets) section.
+3. There are `time_created`, `time_started` and `time_finished` fields, and it's
+ not hard to guess what their meaning. When creating a job, you should fill
+ the `time_created` field with a UNIX timestamp. jobd will update the two other
+ fields while executing the job.
+4. Each job has a `status`.
+ - A job must be created with status set to `waiting` or `manual`.
+ - A status becomes `accepted` when jobd reads the job from the table and
+ puts it to a queue, or it might become `ignored` in case of some error, like
+ invalid `target`, or invalid `status` when processing a [run-manual(ids: int)](#run-manualids-int)
+ request.
+ - Right before a job is getting started, its status becomes `running`.<br>
+ - Finally, when it's done, it is set to `done`.
+5. The `result` field indicates whether a job completed successfully or not.
+ - It is set to `ok` if the return code of launched command was `0`.
+ - Otherwise, it is set to `fail`.
+6. The `return_code` field is filled with the actual return code.
+7. If the job process was killed by a POSIX signal, the signal name is written
+ to the `sig` field.
+8. stdout and stderr of the process are written to `stdout` and `stderr` fields,
+ accordingly.
+
+> :bulb: Maybe you noticed that there are no `priority` field and wonder how to create
+> jobs with different priorities. Indeed, jobd doesn't support "priorities".
+> Instead, you should create different targets for jobs of different priorities
+> and assign your jobs to corresponding targets. Learn more about it in the
+> [Targets](#targets) section.
+
+> :warning: In a real world, you'll want to have a few more additional fields,
+> like `job_name` or `job_data`.<br>
+> Check out the [implementation example](#implementation-example).
+
+To create a new job (or multiple jobs), they must be added the table. As mentioned
+earlier, adding or removing to/from the table is outsize the jobd's area of
+responsibility and this is by design. User must add jobs to the table manually.
+
+There are two kinds of jobs, in terms of how they are executed: **background** and
+**manual** (foreground).
+
+* Background jobs are created with `waiting` status. When jobd gets new
+ jobs from the table (after receiving a [`poll(target: strings[])`](#polltargets-string)
+ request: this process is described in the [launching background jobs](#launching-background-jobs)
+ section), such jobs are added to their queues and are executed at some point,
+ depending on the current queue status and concurrency limit. User does not
+ have control of the execution flow, the only feedback it has over the job
+ executing is the fields in the table that are updated during the execution. At
+ some point, `status` will become `done`, `result` and other fields will have
+ their values filled too, and that's it.
+* Manual, or foreground jobs, is a different story. They must be created with
+ `status` set to `manual`. These jobs are processed only upon a
+ [`run-manual(ids: int[])`](#run-manualids-int) request. When jobd receives such
+ request, it reads and launches the specified jobs, waits for the results and
+ sends them back to the client in a response.
+
+### Launching jobs
+
+**Launching** (or **executing**) a job means **running a command** specified in
+the config as the `launcher`, replacing the `{id}` template with current job id.
+
+For example, if you have this in the config:
```
-[0,{no:0,type:'poll',data:{'targets':['target_1','target_2']}}]
+launcher = php /home/user/job-launcher.php {id}
```
+and jobd is currently executing a job with id 123, it will launch
+`php /home/user/job-launcher.php 123`.
+
+### Launching background jobs
+
+After jobs have been added to storage, jobd must be notified about it. This is
+done by a [`poll(targets: string[])`](#polltargets-string) request that a user (a client) sends to the
+jobd instance. The `targets` argument is an array (a list) of `targets` to poll.
+It can be omitted; in that case jobd will query for jobs for all targets it is
+serving.
+
+When jobd receives a [`poll(targets: string[])`](#polltargets-string) request and specified targets are
+not full (haven't reached their concurrency limit), it performs a `SELECT` query
+with `status='manual'` condition and `LIMIT` set according to the `mysql_fetch_limit`
+config value.
+
+For example, after receiving the [`poll(['1/low', '1/normal'])`](#polltargets-string) request, assuming
+`mysql_fetch_limit` is set to `100`, jobd will query jobs roughly like this:
+```mysql
+SELECT id, status, target FROM jobs WHERE status='manual' AND target IN ('1/low', '1/normal') ORDER BY id LIMIT 0, 100 FOR UPDATE
+```
+However, if all specified targets are full at the time of jobd receiving the
+[`poll(targets: string[])`](#polltargets-string) request, the query will be delayed until at least one
+of the targets becomes available for new jobs.
-Here is the list of supported requests, using `type(arguments)` notation.
-
-#### jobd request types
-
-* **`poll(targets: string[])`** — get new tasks for specified `targets` from database.
- If `targets` argument is not specified, get tasks for all serving targets.
-
-* **`pause(targets: string[])`** — pause execution of tasks of specified targets.
- If `targets` argument is not specified, pauses all targets.
-
-* **`continue(targets: string[])`** — continue execution of tasks of specified targets.
- If `targets` argument is not specified, continues all targets.
-
-* **`status()`** — returns status of internal queues and memory usage.
-
-* **`run-manual(ids: int[])`** — enqueue and run jobs with specified IDs and
- `status` set to `manual`, and return results.
-
-* **`add-target(target: string, concurrency: int)`** — add target
-
-* **`remove-target(target: string, concurrency: int)`** — remove target
-
-* **`set-target-concurrency(target: string, concurrency: int)`** — set concurrency
- of target `target`.
-
-#### jobd-master request types
-
-* **`register-worker(targets: string[])`** — used by a jobd instance to register
- itself with master. You don't need it.
-
-* **`poke(targets: string[])`** — send `poll` requests to all registered workers
- that serve specified `targets`.
-
-* **`pause(targets: string[])`** — send `pause(targets)` requests to workers
- serving specified `targets`. If `targets` argument is not specified, sends
- `pause()` to all workers.
-
-* **`continue(targets: string[])`** — send `continue(targets)` requests to workers
- serving specified `targets`. If `targets` argument is not specified, sends
- `continue()` to all workers.
-
-* **`status(poll_workers=false: bool)`** — returns list of registered workers and
- memory usage. If `poll_workers` is true, sends `status()` request to all registered
- workers and includes their responses.
-
-* **`run-manual(jobs: {id: int, target: string}[])`** — send `run-manual`
- requests to registered jobd instances serving specified targets, aggregate an
- return results.
-
-### Response Message
-
-`DATA` is a JSON object with following keys:
-
-- **`no`** *(**required**, int)* — `no` of request this response is related to.
-- **`data`** *(array *|* object *|* string *|* int)* — data, if request succeeded.
-- **`error`** *(string)* — error message, if request failed.
+### Launching manual jobs
-Example (w/o trailing `EOT`):
-```
-[1,{no:0,data:'ok'}]
-```
+To be written.
-### Ping and Pong Messages
+### Using jobd-master
-No `DATA`.
+To be written.
-Example (w/o trailing `EOT`):
```
-[2]
+Fig. 1
+
+┌────────┐ ┌────────┐ ┌────────┐
+│ jobd │ │ jobd │ │ jobd │
+└───┬────┘ └────┬───┘ └────┬───┘
+ │ │ │
+ │ ┌───────▼───────┐ │
+ └────►│ jobd-master │◄───┘
+ └───────────────┘
```
## Implementation example
@@ -200,18 +274,18 @@ Then install jobd using npm:
npm i -g jobd
```
-
## Usage
### systemd
One of possible ways of launching jobd and jobd-master daemons is via systemd.
-This repository contains basic examples of `jobd.service` and `jobd-master.service`
-unit files. Note that jobs will be launched as the same user the jobd worker is
-running, so you might want to change that.
+This repository contains basic examples of [`jobd.service`](systemd/jobd.service)
+and [`jobd-master.service`](systemd/jobd-master.service) unit files. Note that
+jobs will be launched as the same user the jobd worker is running, so you might
+want to change that.
Copy `.service` file(s) to `/etc/systemd/system`, then do:
-```
+```shell
systemctl daemon-reload
systemctl enable jobd
systemctl start jobd
@@ -220,9 +294,10 @@ systemctl start jobd
### supervisor
-If you don't like systemd, supervisor might be an option. Create a configuration
-file in `/etc/supervisor/conf.d` with following content:
-```
+If you don't like systemd, [supervisor](http://supervisord.org/) might be an
+option. Create a configuration file in `/etc/supervisor/conf.d` with following
+content:
+```ini
[program:jobd]
command=/usr/bin/jobd --config /etc/jobd.conf
numprocs=1
@@ -236,18 +311,17 @@ stopsignal=TERM
Then use `supervisorctl` to start or stop jobd.
-
### Other notes
-Don't forget to filter access to jobd and jobd-master ports using your favorite
-firewall.
+:exclamation: Don't forget to filter access to jobd and jobd-master ports using
+a firewall. See a note [here](#protocol) for more info.
## Configuration
Configuration files are written in ini format. All available options for both
daemons, as well as a command-line utility, are described below. You can copy
-`jobd.conf.example` and `jobd-master.conf.example` and use them as a template
-instead of writing configs from scratch.
+[`jobd.conf.example`](jobd.conf.example) and [`jobd-master.conf.example`](jobd-master.conf.example)
+and use them as a template instead of writing configs from scratch.
### jobd
@@ -256,39 +330,39 @@ a different path.
Without section:
-- **`host`** *(required, string)* — jobd server hostname
-- **`port`** *(required, int)* — jobd server port
-- **`password`** *(string)* — password for requests
-- **`always_allow_localhost`** *(boolean, default: `false`)* — when set to `1`
+- `host` *(required, string)* — jobd server hostname
+- `port` *(required, int)* — jobd server port
+- `password` *(string)* — password for requests
+- `always_allow_localhost` *(boolean, default: `false`)* — when set to `1`
or `true`, allows accepting requests from clients connecting from localhost
without password
-- **`master_host`** *(string)* — master hostname
-- **`master_port`** *(int)* — master port. If hostname or port is omitted, jobd
+- `master_host` *(string)* — master hostname
+- `master_port` *(int)* — master port. If hostname or port is omitted, jobd
will not connect to master.
-- **`master_reconnect_timeout`** *(int, default: `10`)* — if connection to master
+- `master_reconnect_timeout` *(int, default: `10`)* — if connection to master
failed, jobd will be constantly trying to reconnect. This option specifies a
delay between connection attempts, in seconds.
-- **`log_file`** *(string)* — path to a log file
-- **`log_level_file`** *(string, default: `warn`)* — minimum level of logs that
+- `log_file` *(string)* — path to a log file
+- `log_level_file` *(string, default: `warn`)* — minimum level of logs that
are written to the file.<br>
Allowed values: `trace`, `debug`, `info`, `warn`, `error`
-- **`log_level_console`** *(string, default: `warn`)* — minimum level of logs
+- `log_level_console` *(string, default: `warn`)* — minimum level of logs
that go to stdout.
-- **`mysql_host`** *(required, string)* — database host
-- **`mysql_port`** *(required, int)* — database port
-- **`mysql_user`** *(required, string)* — database user
-- **`mysql_password`** *(required, string)* — database password
-- **`mysql_database`** *(required, string)* — database name
-- **`mysql_table`** *(required, string)* — table name
-- **`mysql_fetch_limit`** *(int, default: `100`)* — a number of new jobs to fetch
+- `mysql_host` *(required, string)* — database host
+- `mysql_port` *(required, int)* — database port
+- `mysql_user` *(required, string)* — database user
+- `mysql_password` *(required, string)* — database password
+- `mysql_database` *(required, string)* — database name
+- `mysql_table` *(required, string)* — table name
+- `mysql_fetch_limit` *(int, default: `100`)* — a number of new jobs to fetch
in every request
-- **`launcher`** *(required, string)* — a template of shell command that will be launched
+- `launcher` *(required, string)* — a template of shell command that will be launched
for every job. `{id}` will be replaced with job id
-- **`max_output_buffer`** *(int, default: `1048576`)*
+- `max_output_buffer` *(int, default: `1048576`)*
Under the `[targets]` section, targets are specified. Each target is specified on
a separate line in the following format:
-```
+```ini
{target_name} = {n}
```
where:
@@ -300,60 +374,212 @@ where:
Default config path is `/etc/jobd-master.conf`. Use the `--config` option to use
a different path.
-- **`host`** *(required, string)*
-- **`port`** *(required, int)*
-- **`password`** *(string)*
-- **`always_allow_localhost`** *(boolean, default: `false`)*
-- **`ping_interval`** *(int, default: `30`)* — specifies interval between workers
+- `host` *(required, string)*
+- `port` *(required, int)*
+- `password` *(string)*
+- `always_allow_localhost` *(boolean, default: `false`)*
+- `ping_interval` *(int, default: `30`)* — specifies interval between workers
pings.
-- **`poke_throttle_interval`** *(int, default: `0.5`)*
-- **`log_file`** *(string)*
-- **`log_level_file`** *(string, default: `warn`)*
-- **`log_level_console`** *(string, default: `warn`)*
+- `poke_throttle_interval` *(int, default: `0.5`)*
+- `log_file` *(string)*
+- `log_level_file` *(string, default: `warn`)*
+- `log_level_console` *(string, default: `warn`)*
### jobctl
Default config path is `~/.jobctl.conf`.
-- **`master`** (boolean) — same as `--master`.
-- **`host`** *(string)* — same as `--host`.
-- **`port`** *(int)* — same as `--port`.
-- **`password`** *(string)*
-- **`log_level`** *(string, default: `warn`)*
+- `master` (boolean) — same as `--master`.
+- `host` *(string)* — same as `--host`.
+- `port` *(int)* — same as `--port`.
+- `password` *(string)*
+- `log_level` *(string, default: `warn`)*
+
+## Clients
+
+### PHP
+
+[php-jobd-client](https://github.com/gch1p/php-jobd-client) (official)
+
+## Protocol
+
+By default, jobd and jobd-master listen on TCP ports 7080 and 7081 respectively,
+ports can be changed in a config.
-## MySQL setup
+> :exclamation: jobd has been created with an assumption that it'll be used in
+> more-or-less trusted environments (like LAN or, at least, servers within one
+> data center) so **no encryption nor authentication mechanisms have been
+> implemented.** All traffic between jobd and clients flow **in plain text**.
+>
+> You can protect a jobd instance with a password though, so at least basic
+> password-based authorization is supported.
-Minimal table scheme:
+Both daemons receive and send Messages. Each message is followed by `EOT` (`0x4`)
+byte which indicates an end of a message. Clients may send and receive multiple
+messages over a single connection. Usually, it's the client who must close the
+connection, when it's not needed anymore. A server, however, may close the
+connection in some situations (invalid password, server error, etc).
+Messages are encoded as JSON arrays with at least one item, representing
+the message type:
```
-CREATE TABLE `jobs` (
- `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
- `target` char(16) NOT NULL,
- `time_created` int(10) UNSIGNED NOT NULL,
- `time_started` int(10) UNSIGNED NOT NULL DEFAULT 0,
- `time_finished` int(10) UNSIGNED NOT NULL DEFAULT 0,
- `status` enum('waiting','manual','accepted','running','done','ignored') NOT NULL DEFAULT 'waiting',
- `result` enum('ok','fail') DEFAULT NULL,
- `return_code` tinyint(3) UNSIGNED DEFAULT NULL,
- `sig` char(10) DEFAULT NULL,
- `stdout` mediumtext DEFAULT NULL,
- `stderr` mediumtext DEFAULT NULL,
- PRIMARY KEY (`id`),
- KEY `status_target_idx` (`status`, `target`, `id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+[TYPE]
```
-In a real world, you need some additional fields such as `job_name` or `job_data`.
+If a message of specific type has some data, it's placed as a second item:
+```
+[TYPE, DATA]
+```
-For optimization purposes, you can turn `target` into `ENUM`. Also, if 16 characters
-for the `target` field is not enough for you, change it to fit your needs.
+Type of `TYPE` is integer. Supported types are:
+- `0`: Request
+- `1`: Response
+- `2`: Ping
+- `3`: Pong
-## Clients
-### PHP
+### Request Message
-[php-jobd-client](https://github.com/gch1p/php-jobd-client) (official)
+`DATA` is a JSON object with following keys:
+
+- ##### `no` (required, int)
+ Unique (per connection) request number. Clients can start counting request
+ numbers from one (`1`) or from any other random number. Each subsequent request
+ should increment this number by 1. Note that zero (`0`) is reserved.
+
+- ##### `type` (required, string)
+ Request type. Supported request types for [jobd](#jobd-requests) and
+ [jobd-master](#jobd-master-requests) are listed below.
+
+- ##### `data` (object)
+ Request arguments (if needed): an object, whose keys and values represent
+ argument names and values.
+
+- ##### `password` (string)
+ A password, for password-protected instances. Only needed for first request.
+
+Example (w/o trailing `EOT`):
+```
+[0,{no:1,type:'poll',data:{'targets':['target_1','target_2']}}]
+```
+
+Here is the list of supported requests, using `type(arguments)` notation.
+
+#### jobd requests
+
+* ##### `poll(targets: string[])`
+ Get new tasks for specified `targets` from database. If `targets` argument is
+ no specified, get tasks for all serving targets.
+
+ Response [data](#data-array--object--string--int) type: **string** ('ok').
+
+* ##### `pause(targets: string[])`
+ Pause execution of tasks of specified targets. If `targets` argument is
+ omitted, pauses all targets.
+
+ Response [data](#data-array--object--string--int) type: **string** ('ok').
+
+* ##### `continue(targets: string[])`
+ Continue execution of tasks of specified targets. If `targets` argument is
+ omitted, continues all targets.
+
+ Response [data](#data-array--object--string--int) type: **string** ('ok').
+
+* ##### `status()`
+ Returns status of internal queues and memory usage.
+
+ Response [data](#data-array--object--string--int) type: **object** with following keys:
+ - `targets` (object<target: string, {paused: bool, concurrency: int, length: int}>)
+ - `jobPromisesCount` (int)
+ - `memoryUsage` (NodeJS.MemoryUsage)
+
+* ##### `add-target(target: string, concurrency: int)`
+ Add target.
+
+ Response [data](#data-array--object--string--int) type: **string** ('ok').
+
+* ##### `remove-target(target: string)`
+ Remove target.
+
+ Response [data](#data-array--object--string--int) type: **string** ('ok').
+
+* ##### `set-target-concurrency(target: string, concurrency: int)`
+ Set concurrency limit of target `target`.
+
+ Response [data](#data-array--object--string--int) type: **string** ('ok').
+
+* ##### `run-manual(ids: int[])`
+ Enqueue and run jobs with specified IDs and `status` set to `manual`, and
+ return results.
+
+ Response [data](#data-array--object--string--int) type: **object** with following keys:
+ - `jobs` (object<int, object>)
+
+ An object whose keys represent succeeded job IDs and whose values are objects
+ with following keys:
+ - `result` (string)
+ - `code` (int)
+ - `signal` (string|null)
+ - `stdout` (string)
+ - `stderr` (string)
+ - `errors` (object<int, string>)
+ An object whose keys represent failed job IDs and whose values are error
+ messages.
+
+#### jobd-master requests
+
+* ##### `register-worker(targets: string[])`
+ Used by a jobd instance to register itself with master. Clients don't need it.
+
+* ##### `poke(targets: string[])`
+ Send [`poll(targets)`](#polltargets-string) requests to all registered workers that serve specified
+ `targets`.
+
+* ##### `pause(targets: string[])`
+ Send [`pause(targets)`](#pausetargets-string) requests to workers serving
+ specified `targets`. If `targets` argument is omitted, sends [`pause()`](#pausetargets-string)
+ to all workers.
+
+* ##### `continue(targets: string[])`
+ Send [`continue(targets)`](#continuetargets-string) requests to workers serving
+ specified `targets`. If `targets` argument is omitted, sends
+ [`continue()`](#continuetargets-string) to all workers.
+
+* ##### `status(poll_workers=false: bool)`
+ Returns list of registered workers and memory usage. If `poll_workers` argument
+ is true, sends [`status()`](#status) request to all workers and includes their responses.
+
+* ##### `run-manual(jobs: {id: int, target: string}[])`
+ Send [`run-manual()`](#run-manualids-int) requests to registered jobd instances
+ serving specified targets, aggregate and return results.
+
+### Response Message
+
+`DATA` is a JSON object with following keys:
+
+- ##### `no` (required, int)
+ [`no`](#no-required-int) of request this response is related to.
+
+- ##### `data` (array | object | string | int)
+ Data, if request succeeded.
+
+- ##### `error` (string)
+ Error message, if request failed.
+
+Example (w/o trailing `EOT`):
+```
+[1,{no:1,data:'ok'}]
+```
+
+### Ping and Pong Messages
+
+No `DATA`.
+
+Example (w/o trailing `EOT`):
+```
+[2]
+```
## TODO
@@ -361,4 +587,4 @@ for the `target` field is not enough for you, change it to fit your needs.
## License
-BSD-2c \ No newline at end of file
+BSD-2c