Продвинутый

3 & Done: система задач для разработчика

Алексей Кузнецов
Алексей Кузнецов
Системный администратор20 октября 2026 г.12 мин чтения

Разбор системы задач 3 & Done — хранение дел, голосовой ввод, приватность и инфраструктура на PostgreSQL. Как убрать рутину и сосредоточиться на важном.

3 & Done: система задач для разработчика

Разбор системы задач 3 & Done — хранение дел, голосовой ввод, приватность и инфраструктура на PostgreSQL. Как убрать рутину и сосредоточиться на важном.

Зачем нужна своя система задач

Проблема избытка инструментов

Сейчас на рынке столько таск-менеджеров, что кажется — каждый захотел построить свой замок Писателя. Notion, ClickUp, Todoist, Trello, Asana, Monday.com, Linear, Jira… Перебороть их все — целая работа. Я сам с этим боролся пять лет: каждый новый сервис обещал «сделать жизнь проще», а на деле просто добавлял ещё одну вкладку в браузер, где нужно было логиниться, помнить пароль и постоянно синхронизировать состояние.

Ситуация ровно такая же, как с хранилищами. Помните, как в 2010-х каждая контора качала свой облачный S3-совместимый бакет? Через пять лет все сели на MinIO или сами поднимали [развертывание DeepSQL](/guides/razvertyvanie-deepsql-размещение на домашних серверах-dba-agent-dlya-postgresql-i-mysql-uf6iti). С таск-менеджерами та же история — избыток приводит к хаосу.

Я остановился на принципе: один источник правды, один интерфейс, один способ добавлять задачу. Ни одного «а вдруг тут круто». Если что-то не вписывается в эту схему — оно не нужное. Особенно когда речь идёт о личных данных.

Как рутина отвлекает от работы

Рутина — это не про «лень». Это про расход мышечного внимания. Каждая задача, которую нужно вручную распределять по проектам, помечать тегами, перетаскивать между колонками — это микроскопический удар по тем же самым нейронам, что отвечают за концентрацию на коде.

Мне кажется, вы знаете это ощущение: сел писать фичу, а через полчаса понимаешь, что вместо реализации ты раз в пятом пытаешь вспомнить, где та задача про рефакторинг старого эндпоинта. Или наоборот — забыл, что нужно срочно ответить на письмо клиенту, потому что оно «где-то там» в системе, которая не напомнит.

Вот почему я сделал ставку на предсказуемость. Задачи попадают в PostgreSQL через один и тот же канал — CLI, Telegram-бот или голосовой ввод. Никаких «а я её в Notion записал, а потом забыл синхронизировать». Один движок, одна модель данных, один набор правил. Остальное — только шум.

Архитектура 3 & Done

Архитектура 3 & Done

Система построена вокруг трёх принципов: данные должны быть предсказуемы, доступ к ним — быстрый, а инфраструктура — минимальной. Вот как это выглядит на практике.

Хранилище на PostgreSQL

PostgreSQL здесь не просто база — он становится единой точкой синхронизации между веб-интерфейсом, мобильным клиентом и CLI-утилитами. Все задачи, метаданные и ссылки на вложения хранятся в одной таблице tasks, что упрощает резервное копирование и миграции.

Схема таблицы выглядит так:

sql
CREATE TABLE tasks (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    title TEXT NOT NULL,
    description TEXT,
    status SMALLINT DEFAULT 0,
    priority SMALLINT DEFAULT 1,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    due_date TIMESTAMP WITH TIME ZONE,
    tags TEXT[]
);

Индекс по status и priority даёт предсказуемость при фильтрации:

sql
CREATE INDEX idx_tasks_status_priority ON tasks (status, priority);

Для резервного копирования используем стандартный pg_dump с сжатием:

bash
pg_dump -U postgres -h localhost -F c -Z 9 3anddone > backup_$(date +%F).dump

А вот как подключаемся из приложения через psycopg2 в Python:

python
import psycopg2

conn = psycopg2.connect(
    host="localhost",
    database="3anddone",
    user="app",
    password="secure_password"
)

Если нужна отказоустойчивость — смотрите [развёртывание DeepSQL](/guides/razvertyvanie-deepsql-размещение на домашних серверах-dba-agent-dlya-postgresql-i-mysql-uf6iti) для настройки репликации и мониторинга.

Голосовой ввод и текстовые заметки

Голосовой ввод работает через локальный Whisper-модуль, а не через облако. Это важно для приватности: ни одно аудио не покидает устройство.

Конфиг для whisper.cpp в Docker Compose:

yaml
services:
  stt:
    image: ghcr.io/ggerganov/whisper.cpp:latest
    volumes:
      - ./models:/models
      - ./audio:/audio
    command: -m /models/ggml-base.en-q5_0.bin -f /audio/input.wav -otxt

Запуск распознавания:

bash
docker compose run --rm stt

Результат сохраняется в /audio/output.txt, откуда скрипт на Python парсит текст и отправляет в PostgreSQL:

python
with open('/audio/output.txt', 'r') as f:
    text = f.read().strip()

cur.execute(
    "INSERT INTO tasks (title, description) VALUES (%s, %s)",
    (text[:100], text)
)
conn.commit()

Для текстовых заметок используется простой Markdown-парсер. Пример заметки:

markdown
# Встреча с командой
- Обсудить дедлайны
- Распределить задачи
- Согласовать бюджет

Эта заметка автоматически разбивается на подзадачи и сохраняется в PostgreSQL через тот же pipeline.

Если интересна временная шкала событий — смотрите масштабируемую временную шкалу, где мы решали похожую задачу для 4 млн записей.

А для тех, кто хочет всё это развернуть локально — [локальный стек DB, Ollama и SQL](/articles/размещение на домашних серверах-ai-workflow-db-ollama-sql) покажет, как собрать полный стек без облака.

Приватность и локальное хранение

Почему данные не в облаке

Скажу сразу — я не против облаков. Работал с AWS, GCP, Azure, где удобно — там оставлял. Но личные задачи — это не проект, где можно «потом поднять». Это каждый день, и если облако ляжет, вы потеряете не просто данные — вы потеряете ритм.

В 2020 году у меня был планировщик на базе Notion. Работало как часы три года. Потом — аутсайдерский апдейт, и на два дня все данные превратились в кашу из дубликатов. Восстанавливать из бэкапа пришлось вручную, а потом ещё разобраться, что изменилось за выходные. С тех пор принцип: если это мои данные и я ими пользуюсь каждый день — они лежат у меня.

Говоря о предсказуемости: локальный PostgreSQL на Пятачке (да, так я назвал ноду, где дома стоит R730) даёт гарантию, что кроме меня никто не лезет в базу. Ни администратор, ни сисадмин из Notion, ни сбой в их инфраструктуре.

bash
# Проверка, что PostgreSQL слушает только localhost
ss -tlnp | grep 5432
# Ожидаем: 127.0.0.1:5432

Если порт открыт наружу — это уже не ваш домашний сервер, это сервер с доступом из интернета. И тогда «приватность» становится формальностью.

Связанная идея: [размещение на домашних серверах AI: локальный стек DB, Ollama и SQL](/articles/размещение на домашних серверах-ai-workflow-db-ollama-sql) — когда всё вместе, в одной сети, без пересечения с внешними сервисами.

Контроль доступа и шифрование

Доступ к базе делится на два уровня: кто может подключиться, и кто может что делать внутри.

Уровень подключения. PostgreSQL по умолчанию слушает только localhost. Если вы хотите, чтобы приложение 3 & Done подключалось с другой машины — не открывайте порт наружу. Настраивайте SSH-туннель или WireGuard-сеть между нодами.

bash
# SSH-туннель с клиентской машины к серверу с PostgreSQL
ssh -L 5432:localhost:5432 alexey@пятачок.local

Теперь клиент подключается к localhost:5432, а запросы идут через туннель. Никто извне не видит порт.

Уровень прав. В PostgreSQL создаю отдельную роль для приложения:

sql
CREATE ROLE three_and_done WITH LOGIN PASSWORD 'сгенерированный_пароль_из_256_символов';
GRANT CONNECT ON DATABASE tasks TO three_and_done;
GRANT USAGE ON SCHEMA public TO three_and_done;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO three_and_done;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO three_and_done;

Пароль храню в pass — шифруется GPG-ключом, синхронизируется через приватный Git-репозиторий. Никогда не в .env-файле рядом с кодом.

Шифрование данных. База лежит на диске с LUKS-шифрованием. Это не защита от злоумышленника, который уже в системе — это защита от кражи диска. Если вынесете SSD из сервера — без ключа он бесполезен.

bash
# Проверка статуса LUKS
cryptsetup status /dev/sdb3

Для бэкапов использую pg_dump с --compress и сразу шифрую gpg:

bash
pg_dump -U postgres tasks | gzip | gpg --encrypt --recipient alexey@домашний.сервер > backup_$(date +%F).sql.gz.gpg

Бэкапы кладутся на отдельный том TrueNAS, который реплицируется на вторую ноду. Если вдруг Пятачок загорится — данные не пропадут.

Система не идеальна, но она предсказуема. Вы знаете, где ваши данные, кто к ним имеет доступ, и как их восстановить. В отличие от облака, где «всё работает, пока работает».

Инфраструктура и развёртывание

Docker Compose для сервисов

Собирать 3 & Done вручную на каждой машине — это путь в тот же подход, который я критикую в статье. Инфраструктура должна быть предсказуемой, а значит — описана в коде.

Вот docker-compose.yml, который я использую в продакшене. База данных, бэкенд и веб-интерфейс идут в отдельных контейнерах, а важные детали — в комментариях.

yaml
version: "3.9"

services:
  db:
    image: postgres:16-alpine
    container_name: 3done-db
    restart: unless-stopped
    volumes:
      - ./pgdata:/var/lib/postgresql/data
      - ./init:/docker-entrypoint-initdb.d
    environment:
      POSTGRES_DB: thrivedone
      POSTGRES_USER: thrivedone
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    networks:
      - backend

  backend:
    build:
      context: .
      dockerfile: Dockerfile.backend
    container_name: 3done-backend
    restart: unless-stopped
    depends_on:
      - db
    env_file: .env.backend
    networks:
      - backend
      - frontend

  web:
    image: nginx:1.25-alpine
    container_name: 3done-web
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./static:/usr/share/nginx/html:ro
    depends_on:
      - backend
    networks:
      - frontend

networks:
  backend:
  frontend:

Пара слов о выборе. PostgreSQL 16 — актуальная LTS на сегодняшний день, а Alpine-образы экономят мегагигабайты по сравнению с Debian-версиями. Разделение сетей на backend и frontend гарантирует, что веб-сервер не «заглянет» в базу напрямую — только через бэкенд.

Переменные окружения вынесены в .env-файлы. Это не просто удобно — это безопасность. Файл .env.backend выглядит так:

env
DB_HOST=db
DB_PORT=5432
DB_NAME=thrivedone
JWT_SECRET=здесь_только_длинная_строка_из_32_символов
LOG_LEVEL=info

А пароль к базе данных — в .env в корне проекта:

env
DB_PASSWORD=сгенерированный_пароль_из_64_символов

Совет: генерировать пароль один раз и хранить в менеджере паролей. Я использую pass на своей машине — [связанная статья про размещение на домашних серверах AI-стек](/articles/размещение на домашних серверах-ai-workflow-db-ollama-sql) тоже опирается на локальную инфраструктуру, так что подходы схожи.

Сам бэкенд собирается из Dockerfile, который я тоже пишу руками. Никаких готовых образов с чужими зависимостями — только тот софт, который действительно нужен:

dockerfile
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]

Если вы работаете с PostgreSQL регулярно, можете также посмотреть мой разбор [локального DBA-агента DeepSQL](/guides/razvertyvanie-deepsql-размещение на домашних серверах-dba-agent-dlya-postgresql-i-mysql-uf6iti) — там есть примеры настройки мониторинга и алертинга прямо внутри контейнера.


Резервное копирование и миграции

База данных — это не просто файлы в pgdata. Это состояние, которое нужно сохранять предсказуемо. Я делаю два уровня бэкапа: логический и физический.

Логический бэкап — pg_dump с флагом --clean, чтобы при восстановлении не было конфликтов со старыми объектами:

bash
pg_dump -U thrivedone -h db -Fc thrivedone > backup_$(date +%Y%m%d_%H%M%S).dump

Флаг -Fc создаёт кастомный формат, который потом удобно восстанавливать частично:

bash
pg_restore -U thrivedone -h db --clean --if-exists -d thrivedone backup_20240115_120000.dump

Физический бэкап — pg_basebackup. Он нужен, когда вы хотите восстановить кластер целиком, включая все WAL-файлы:

bash
pg_basebackup -U replicator -h db -D /backup/pg_base/ -P -R -X stream

Для автоматизации я использую systemd-таймер, а не cron. Почему? Потому что systemd даёт предсказуемость: вы видите в journalctl, когда именно задача запустилась, с каким результатом, и можете повесить алерт, если что-то пошло не так.

Вот юнит /etc/systemd/system/3done-backup.service:

ini
[Unit]
Description=3 & Done database backup
After=docker.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/3done-backup.sh
User=backup

И таймер /etc/systemd/system/3done-backup.timer:

ini
[Unit]
Description=Daily 3 & Done backup

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target

Активируем:

bash
systemctl daemon-reload
systemctl enable --now 3done-backup.timer

Скрипт 3done-backup.sh прост — он делает логический дамп, копирует его на NFS-хранилище и удаляет старые бэкапы старше 30 дней:

bash
#!/bin/bash
set -euo pipefail

DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/mnt/nfs/backups/3done"
CONTAINER="3done-db"

mkdir -p "$BACKUP_DIR"

docker exec "$CONTAINER" pg_dump -U thrivedone thrivedone | gzip > "$BACKUP_DIR/thrivedone_$DATE.sql.gz"

# Удаляем файлы старше 30 дней
find "$BACKUP_DIR" -name "thrivedone_*.sql.gz" -mtime +30 -delete

Миграции — это тоже часть инфраструктуры. Я не использую миграции в классическом Django-смысле. Вместо этого всё схемы хранятся в SQL-файлах в каталоге migrations/, и применяются последовательно через psql:

bash
psql -U thrivedone -d thrivedone -f migrations/001_create_tables.sql
psql -U thrivedone -d thrivedone -f migrations/002_add_indexes.sql

Это даёт полный контроль. Вы видите каждый шаг, можете откатить на любом этапе, и никто не «навязывает» вам свою ORM-специфику.

Если у вас уже есть данные и вы хотите перенести их из старой системы — эта статья про временные метки с 4 млн событий покажет, как оптимизировать миграцию больших объёмов данных в PostgreSQL без простоев.

Главное правило: если вы не можете восстановить сервис из бэкапа за 30 минут, ваша стратегия резервного копирования не работает. Проверяйте восстановление регулярно — не реже месяца.

Часто задаваемые вопросы

Почему именно PostgreSQL, а не SQLite или облачный сервис?

Потому что PostgreSQL даёт предсказуемость, которой не хватает ни SQLite, ни облачным таск-менеджерам. SQLite не выдерживает конкурентных запросов, если вы работаете с задачами из Telegram-бота, CLI и веб-интерфейса одновременно — блокировки начинают каскадировать. Облачные сервисы, даже если они и «бесплатны», требуют синхронизации состояния, а значит — задержек и потерь данных при плохом соединении. PostgreSQL с JSONB-полями для метаданных и транзакциями для атомарности операций работает локально, без задержек, и не требует выхода в интернет. Если вы уже знакомы с SQL — вы сможете писать запросы для анализа своей продуктивности без сторонних API.

Как система обрабатывает голосовые заметки без передачи данных третьим лицам?

Голосовой ввод работает через локальный распознаватель речи — Vosk или Whisper.cpp — прямо на вашем сервере. Аудио не покидает устройство: запись захватывается микрофоном, отправляется в локальный процесс распознавания, результат преобразуется в текст и сразу записывается в PostgreSQL. Никаких запросов к Google Speech API, Яндекс.Распознаванию или другим облачным сервисам. Это значит, что даже если вы говорите «переписать роутер на /api/v1/users», эта информация не попадает ни в какую базу данных третьих сторон. Для большинства задач достаточно Vosk — он легче, быстрее и не требует GPU.

Что делать, если нужно срочно добавить задачу, не садясь за компьютер?

Telegram-бот решает эту задачу. Вы пишете сообщение в чат с ботом — «срочно: проверить логи на Проде» — и задача автоматически попадает в PostgreSQL с пометкой «срочно» и временной меткой. Бот работает через webhook или long-polling, не требует постоянного соединения с вашим ноутбомом, и может быть развёрнут на той же ноде, где лежит база данных. Главное — не нужно помнить, в каком из пяти сервисов вы оставляли напоминание. Один канал ввода, один источник правды.

Как система помогает не терять задачи в потоке дел?

Принцип «3 & Done» прост: вы добавляете задачу, система автоматически назначает ей приоритет на основе ключевых слов и времени создания, а через три дня — если задача не завершена — она попадает в сегодняшний список с пометкой «просрочено». Никаких колонок «в процессе», «на паузе», «отложено» — только актуальные задачи, которые реально нужно сделать сегодня. Это убирает когнитивную нагрузку на выбор: вы видите три самые важные задачи и начинаете с них. Остальное — в архиве, но с доступом по одному клику, если вдруг вспомните, что-то упустили.

Можно ли использовать эту систему в команде, а не только для себя?

Да, но с оговоркой. PostgreSQL поддерживает роли и права доступа, так что вы можете создать отдельные схемы или таблицы для каждого участника команды, а также общие представления для совместных задач. Однако система 3 & Done изначально проектировалась как персональная — без сложных workflow, уведомлений и интеграций с CI/CD. Если вам нужна командная коллаборация с аппрувами, ревью и распределением нагрузки — лучше рассмотреть Linear или Jira. А если цель — убрать рутину из личной работы и сосредоточиться на коде, эта система работает как часы.

Поделиться:TelegramX / TwitterVK

Читайте также