제7부 미들웨어·데이터 계층
7.4 SQLite 설치와 활용
7.4.1 설치 — 표준 절차
SQLite는 서버 프로세스가 없는 파일 기반 임베디드 DB다. Debian에서 CLI 도구와 라이브러리를 apt로 설치한다.
sudo apt update
sudo apt install -y sqlite3
sqlite3 --version
Python은 표준 라이브러리에 sqlite3 모듈이 기본 내장돼 있어 대부분의 경우 별도 설치가 필요 없다 — App Lab의 Python 환경(4.2)에서도 마찬가지다. 동작만 확인하면 된다.
python3 -c "import sqlite3; print(sqlite3.sqlite_version)"
만약 시스템에 libsqlite3가 누락되어 있어 위 명령이 실패한다면(드문 경우) sudo apt install libsqlite3-0으로 라이브러리만 따로 설치할 수 있다.
7.4.2 PostgreSQL 대비 트레이드오프
같은 제7부 안에서 7.3(PostgreSQL)과 SQLite 중 무엇을 쓸지는 상황에 따라 달라진다 — 어느 한쪽이 항상 우월한 것이 아니라 용도가 다르다.
| 기준 | SQLite | PostgreSQL |
|---|---|---|
| 실행 모델 | 서버 프로세스 없음 — 라이브러리를 앱에 링크, DB는 파일 하나 | 별도 서버 프로세스(postgresql 서비스)가 상시 구동 |
| 동시 접속 | 단일 프로세스 중심. WAL 모드로 "쓰기 1 + 읽기 다수" 정도는 무난 | 다중 클라이언트·다중 프로세스 동시 접속을 기본 전제로 설계 |
| 네트워크 접근 | 기본적으로 로컬 파일 접근만(원격 접근하려면 별도 계층 필요) | TCP로 원격 접속 기본 지원 |
| 설정·운영 부담 | 거의 없음 — 설치=끝, 파일 하나 백업하면 됨 | 계정·권한·튜닝·서비스 관리 필요(7.3) |
| 자원 사용(유휴 시) | 사실상 0(프로세스가 없으므로) | 상시 프로세스가 메모리를 점유 |
| 데이터 규모·복잡도 | 소~중규모, 단순~중간 복잡도 쿼리에 적합 | 대규모, 복잡한 쿼리·트랜잭션·확장기능(PostGIS 등)에 강함 |
| UNO Q에서 전형적 용도 | 단일 App Lab 앱의 로컬 로그·설정 저장, 오프라인 우선 앱 | 여러 클라이언트(웹 대시보드+ROS 2 노드 등)가 함께 보는 공유 DB |
견해: UNO Q 프로젝트가 "이 보드 하나에서 실행되는 앱 한 개가 자기 데이터를 쌓는" 수준이라면 SQLite로 시작하는 것을 권한다 — 부팅 즉시 쓸 수 있고 백업도 파일 복사 한 번이면 된다. 나중에 "다른 기기·다른 프로세스가 같은 데이터를 봐야 한다"는 요구가 생기면 그때 PostgreSQL로 옮기는 편이 초기 복잡도를 줄인다. 반대로 처음부터 멀티 클라이언트 접근이 확실하다면 바로 PostgreSQL을 택하는 것이 이중 작업을 피하는 방법이다.
7.4.3 실습 — 센서 로그 테이블
CLI로 스키마를 만든다.
sqlite3 /home/arduino/data/sensors.db <<'SQL'
CREATE TABLE IF NOT EXISTS sensor_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sensor_name TEXT NOT NULL,
value REAL NOT NULL,
ts TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_sensor_log_name_ts
ON sensor_log (sensor_name, ts DESC);
SQL
Python으로 삽입·조회:
import sqlite3
conn = sqlite3.connect("/home/arduino/data/sensors.db")
# WAL 모드 — 아래 7.4.4에서 설명
conn.execute("PRAGMA journal_mode = WAL;")
conn.execute(
"INSERT INTO sensor_log (sensor_name, value) VALUES (?, ?)",
("temp_c", 24.7),
)
conn.commit()
for row in conn.execute(
"SELECT sensor_name, value, ts FROM sensor_log "
"WHERE sensor_name = ? ORDER BY ts DESC LIMIT 5",
("temp_c",),
):
print(row)
conn.close()
CLI에서 바로 확인:
sqlite3 /home/arduino/data/sensors.db \
"SELECT sensor_name, avg(value), count(*) FROM sensor_log GROUP BY sensor_name;"
7.4.4 WAL 모드와 eMMC 고려사항
기본(rollback journal) 모드는 쓰기 도중 DB 파일 전체를 잠그기 때문에, 로거 프로세스가 쓰는 동안 대시보드 프로세스가 읽으려 하면 대기하게 된다. WAL(Write-Ahead Log) 모드는 쓰기와 읽기를 분리해 "쓰기 1개 + 읽기 여러 개" 조합에서 훨씬 매끄럽다 — 센서 로거(쓰기)와 대시보드(읽기)가 같은 보드에서 동시에 실행되는 UNO Q의 전형적 구성에 잘 맞는다.
conn.execute("PRAGMA journal_mode = WAL;")
한 번 설정하면 DB 파일에 남으므로 이후 연결에서는 별도 지정이 없어도 유지된다.
eMMC를 쓰는 UNO Q에서는 두 가지를 추가로 고려한다.
- 체크포인트 빈도: WAL은 주기적으로 메인 DB 파일에 병합(체크포인트)되며 이 과정이 쓰기를 유발한다. 매우 잦은 삽입(초당 수십~수백 회) 워크로드라면
PRAGMA wal_autocheckpoint로 병합 주기를 조정해 쓰기 패턴을 완만하게 만들 수 있다. synchronous설정: 기본값(FULL)은 안전하지만 fsync 호출이 잦다. 로그성 데이터로 약간의 유실을 감수할 수 있다면PRAGMA synchronous = NORMAL(WAL 모드와 함께 쓸 때 일반적으로 권장되는 조합)로 쓰기 부담을 줄일 수 있다 — 이 또한 PostgreSQL 절(7.3.3)과 같은 내구성 대 쓰기량의 트레이드오프이므로 데이터 중요도에 맞게 결정한다.
7.4.5 다음 장으로
7.3(PostgreSQL)과 이 장의 SQLite는 이후 12.1(종합 프로젝트 ① IoT 대시보드)에서 실제로 활용된다 — 센서 로거는 SQLite(또는 PostgreSQL)에 데이터를 쌓고, 웹 대시보드가 이를 읽어 시각화하는 구성으로 이어진다.