Значения указаны в секундах. Для нового прямого соединения длительность этапа можно оценить как разность соседних отметок.
Разбираем на числах
Здесь взяты условные значения для примера. Это не результат запроса к адресу выше:
Событие
От начала
Интервал
DNS разрешён
0,012 с
12 мс
TCP-соединение установлено
0,038 с
38 − 12 = 26 мс
TLS готов
0,084 с
84 − 38 = 46 мс
Первый байт
0,220 с
220 − 84 = 136 мс
Передача завершена
0,225 с
225 − 220 = 5 мс
Самый длинный интервал — между завершением TLS и первым байтом ответа. В эти 136 мс входят отправка запроса, передача по сети и работа сервера. По ним нельзя отдельно определить время выполнения кода приложения.
Границы такого расчёта
Повторное использование соединения, перенаправления, прокси и HTTP/3 меняют смысл этих интервалов. Учитывайте условия запроса при сравнении замеров. Для оценки обработки на сервере сопоставьте запрос с серверной трассировкой.
Создаём копию через backup API и проверяем результат. При копировании только app.db можно потерять изменения из WAL.
В режиме WAL уже зафиксированные изменения могут оставаться в app.db-wal. Если скопировать только app.db, пока приложение работает, эти изменения могут не попасть в копию.
Создаём снимок базы
Скрипт на Python использует backup API SQLite. Запускайте его из каталога с существующей app.db. При каждом запуске он создаёт отдельную папку для копии.
Python · backup.py
import sqlite3
from contextlib import closing
from pathlib import Path
from tempfile import mkdtemp
source = Path("app.db").resolve()
with closing(sqlite3.connect(source.as_uri() + "?mode=ro", uri=True)) as src:
folder = Path(mkdtemp(prefix="backup-", dir=".")).resolve()
backup = folder / "app.db"
with closing(sqlite3.connect(backup)) as dst:
src.backup(dst, pages=256)
dst.execute("PRAGMA journal_mode=DELETE")
with closing(sqlite3.connect(backup.as_uri() + "?mode=ro", uri=True)) as check:
result = check.execute("PRAGMA integrity_check").fetchall()
if result != [("ok",)]:
raise RuntimeError(result)
print(backup)
URI с режимом «только чтение» не даст незаметно создать пустую базу, если исходный файл отсутствует. Снимок переводится из WAL в обычный режим, чтобы его можно было читать как отдельный файл. closing() закрывает соединения: собственный контекстный менеджер соединения управляет транзакциями, но не закрывает его.
Проверяем не только структуру
integrity_check проверяет структуру базы, но не определяет, те ли данные в ней находятся. Подключите копию к тестовому экземпляру приложения и найдите известную запись. Перед заменой рабочей базы проверьте версию схемы.
Скрипт создаёт локальный снимок. Срок хранения, копирование на другой сервер и порядок восстановления нужно настроить отдельно. После неудачного запуска может остаться незавершённая копия. Используйте только путь, выведенный при успешном завершении.
Пример с argparse: отсортированные имена в stdout, ошибки в stderr и пустой вывод, если файлов нет.
Скрипт выводит файлы .md из указанного каталога, не заходя в подкаталоги. Папка с именем draft.md в список не попадёт, а имя файла с пробелами останется одной строкой.
Код скрипта
Python · list_notes.py
import argparse
from pathlib import Path
parser = argparse.ArgumentParser(
description="Список файлов с указанным расширением."
)
parser.add_argument("directory", type=Path)
parser.add_argument("--suffix", default=".md")
args = parser.parse_args()
try:
if not args.directory.is_dir():
parser.error("укажите существующий каталог")
names = sorted(
path.name for path in args.directory.iterdir()
if path.is_file() and path.suffix == args.suffix
)
except OSError as error:
parser.exit(1, f"Не удалось прочитать каталог: {error}\n")
for name in names:
print(name)
Сохраните код в list_notes.py. Команда python3 list_notes.py ./notes выведет Markdown-файлы. Для текстовых файлов добавьте --suffix .txt. Список аргументов доступен через --help.
Вывод и коды возврата
Ситуация
stdout
Код
Файлы найдены
Имена по порядку
0
Совпадений нет
Пусто
0
Неверный аргумент или каталог
Пусто
2
Перехвачена ошибка файловой системы
Пусто
1
Список собирается до вывода. Если при обходе каталога возникнет перехваченная ошибка, в stdout не останется неполного списка. Сообщения об ошибках идут в stderr.
Этот формат удобен для чтения человеком. Имена файлов могут содержать перевод строки, поэтому формат «одно имя на строку» не всегда однозначен. Для обработки другой программой лучше выбрать JSON или разделитель NUL. При проверке типа файла скрипт следует по символическим ссылкам.
Контекст сборки задаётся последним аргументом docker build. Путь к Dockerfile его не меняет.
Смотрим на команду сборки
Команда из корня репозитория
docker build -f deploy/Dockerfile .
Точка задаёт корень репозитория как контекст сборки. Именно туда нужно положить .dockerignore. Пути в COPY тоже отсчитываются от контекста, а не от deploy/.
Файл .env.example останется в контексте: разрешающее правило стоит после исключающего. Действует последнее подходящее правило. В файле примера не должно быть настоящих секретов. Для файлов окружения во вложенных каталогах нужны отдельные правила.
package.json, файл фиксации зависимостей и исходники остаются доступны. Прежде чем исключать каталоги с результатами сборки, проверьте, не копирует ли их Dockerfile.
Проверяем второй файл исключений
Для этой команды файл deploy/Dockerfile.dockerignore, если он есть, имеет приоритет над корневым .dockerignore. Если исключение не срабатывает, сначала проверьте наличие этого файла.
Исключение файла не удаляет его из истории Git или из уже собранного образа.
Сохраняем код ошибки до записи в лог и очистки. Во вспомогательных функциях возвращаем статус явно.
$? меняется после каждой команды. Если обработчик сначала напечатает сообщение, а затем завершится с $?, наружу может уйти статус printf, а не исходной ошибки.
Сохраняем статус при входе
Bash · error-status.sh
#!/usr/bin/env bash
set -Eeuo pipefail
on_error() {
local status="$1"
local line="$2"
trap - ERR
printf 'Ошибка в строке %s (код %s)\n' \
"$line" "$status" >&2
exit "$status"
}
trap 'on_error "$?" "$LINENO"' ERR
# Имитируем команду, завершившуюся с ошибкой.
bash -c 'exit 7'
Выполните bash error-status.sh; printf 'exit=%s\n' "$?". Обработчик выведет сообщение в stderr, а вызывающая оболочка — exit=7.
Явно возвращаем результат восстановления
Для return без аргумента внутри обработчика trap действуют особые правила выбора статуса. Если вспомогательная функция успешно выполнила шаг восстановления, используйте return 0. При сбое возвращайте заранее определённый ненулевой код. Результат восстановления и сохранённый код исходной ошибки — разные значения.
ERR не перехватывает все ошибки. Например, он не срабатывает для команд, чей статус проверяется в условии if, и для большинства позиций в цепочках && и ||. Проверяйте сбой при том же способе вызова, который используется в рабочем скрипте.