JSONL 데이터베이스 가져오기 & 내보내기: PostgreSQL, MongoDB 등

데이터베이스에 JSONL 파일을 가져오고 데이터베이스 레코드를 JSONL로 내보내는 종합 가이드입니다. PostgreSQL COPY 명령, MongoDB CLI 도구, MySQL JSON_TABLE, SQLite 스크립트, 프로덕션 준비 대량 가져오기 패턴을 다룹니다.

최종 업데이트: 2026년 2월

데이터베이스 가져오기 & 내보내기에 JSONL을 사용하는 이유

JSONL(JSON Lines)은 데이터베이스와 외부 시스템 간 데이터 이동에 선호되는 형식이 되었습니다. 각 줄이 독립적이고 자체 완결적인 JSON 객체이므로, 전체 데이터셋을 메모리에 로드하지 않고 한 번에 하나의 레코드를 스트리밍할 수 있습니다. 이는 PostgreSQL 인스턴스 간에 수백만 행을 마이그레이션하거나, MongoDB 컬렉션을 데이터 웨어하우스에 동기화하거나, 데이터베이스 내보내기를 머신러닝 파이프라인에 투입할 때 중요합니다.

CSV와 비교하면 JSONL은 데이터 타입을 보존하고, 중첩 객체와 배열을 지원하며, 구분자 이스케이프의 모호성을 제거합니다. JSONL 파일은 PostgreSQL jsonb 컬럼을 그대로 전달할 수 있지만, CSV에서는 중첩 구조를 평탄화하거나 이스케이프해야 합니다. 전체 JSON 배열과 비교하면 JSONL은 스트리밍이 가능합니다: 전체 파일 파싱을 기다리지 않고 읽히는 즉시 각 레코드를 처리, 검증, 변환할 수 있습니다.

모든 주요 데이터베이스 시스템은 이제 JSONL용 도구를 갖추고 있습니다. PostgreSQL은 jsonb 캐스팅을 통해 COPY 행을 처리할 수 있고, MongoDB는 JSONL을 기본으로 하는 mongoimport와 mongoexport를 제공하며, MySQL 8.0은 구조화된 추출을 위한 JSON_TABLE을 추가했고, SQLite는 JSON1 확장을 가지고 있습니다. 다음 섹션에서는 각 데이터베이스에 대한 정확한 명령, 스크립트, 모범 사례를 배웁니다.

PostgreSQL: JSONL 가져오기 & 내보내기

PostgreSQL은 JSONL 작업을 위한 여러 접근 방식을 제공합니다. COPY 명령은 가장 빠른 대량 가져오기 경로를 제공하고, jsonb 데이터 타입은 반구조화된 데이터를 네이티브로 저장하고 쿼리할 수 있게 합니다. 구조화된 테이블의 경우 가져오기 중에 JSONL 필드를 타입이 지정된 컬럼으로 파싱할 수 있습니다.

PostgreSQL에 JSONL을 가져오는 가장 빠른 방법은 COPY 명령을 사용하여 각 줄을 jsonb 값으로 로드하는 것입니다. 단일 jsonb 컬럼이 있는 테이블을 만든 다음 COPY FROM을 사용하여 파일을 데이터베이스로 직접 스트리밍합니다.

jsonb 컬럼에 JSONL 가져오기
-- Create a staging table with a single jsonb column
CREATE TABLE staging_import (data jsonb);
-- Import the JSONL file (each line becomes one row)
\COPY staging_import (data) FROM 'users.jsonl';
-- Verify the import
SELECT count(*) FROM staging_import;
SELECT data->>'name' AS name, data->>'email' AS email
FROM staging_import
LIMIT 5;
-- Move data into a structured table
INSERT INTO users (name, email, age)
SELECT
data->>'name',
data->>'email',
(data->>'age')::int
FROM staging_import;

데이터를 JSONL로 내보내려면 row_to_json() 또는 to_jsonb()를 사용하여 각 행을 JSON 객체로 변환한 다음 COPY로 파일에 결과를 출력합니다. 각 행이 출력 파일의 한 줄이 됩니다.

PostgreSQL 행을 JSONL로 내보내기
-- Export entire table as JSONL
\COPY (
SELECT row_to_json(t)
FROM (SELECT id, name, email, created_at FROM users) t
) TO 'users_export.jsonl';
-- Export with filtering and transformation
\COPY (
SELECT row_to_json(t)
FROM (
SELECT id, name, email,
created_at::text AS created_at
FROM users
WHERE active = true
ORDER BY id
) t
) TO 'active_users.jsonl';
-- Verify: each line is valid JSON
-- {"id":1,"name":"Alice","email":"alice@example.com",...}

프로덕션 워크로드의 경우 psycopg2를 사용하는 Python 스크립트로 JSONL 파일을 읽고 타입이 지정된 컬럼에 레코드를 삽입합니다. 이를 통해 검증, 오류 처리, 배치 크기에 대한 완전한 제어가 가능합니다.

구조화된 PostgreSQL 가져오기를 위한 Python 스크립트
import json
import psycopg2
from psycopg2.extras import execute_values
def import_jsonl_to_postgres(file_path, conn_string):
conn = psycopg2.connect(conn_string)
cur = conn.cursor()
batch = []
batch_size = 1000
total = 0
with open(file_path, 'r') as f:
for line_num, line in enumerate(f, 1):
line = line.strip()
if not line:
continue
try:
record = json.loads(line)
batch.append((
record['name'],
record['email'],
record.get('age'),
))
except (json.JSONDecodeError, KeyError) as e:
print(f"Skipping line {line_num}: {e}")
continue
if len(batch) >= batch_size:
execute_values(
cur,
"INSERT INTO users (name, email, age) "
"VALUES %s ON CONFLICT (email) DO NOTHING",
batch
)
total += len(batch)
batch = []
print(f"Imported {total} records...")
if batch:
execute_values(
cur,
"INSERT INTO users (name, email, age) "
"VALUES %s ON CONFLICT (email) DO NOTHING",
batch
)
total += len(batch)
conn.commit()
cur.close()
conn.close()
print(f"Done. Imported {total} records.")
# Usage
import_jsonl_to_postgres('users.jsonl', 'postgresql://localhost/mydb')

MongoDB: 네이티브 JSONL 지원

MongoDB는 커맨드라인 Database Tools를 통해 퍼스트클래스 JSONL 지원을 제공합니다. mongoimport와 mongoexport 유틸리티는 기본적으로 JSONL 형식을 사용하므로, JSONL 데이터 교환에 가장 쉽게 사용할 수 있는 데이터베이스 중 하나입니다. JSONL 파일의 각 줄은 MongoDB 문서에 직접 매핑됩니다.

mongoimport는 JSONL 파일을 읽고 각 줄을 지정된 컬렉션에 문서로 삽입합니다. 업서트 모드, 필드 타입 변환, 높은 처리량을 위한 병렬 삽입을 지원합니다.

mongoimport로 JSONL 가져오기
# Basic import: each line becomes a document
mongoimport \
--uri="mongodb://localhost:27017/mydb" \
--collection=users \
--file=users.jsonl
# Upsert mode: update existing documents by _id
mongoimport \
--uri="mongodb://localhost:27017/mydb" \
--collection=users \
--file=users.jsonl \
--mode=upsert \
--upsertFields=email
# Drop collection before import (clean slate)
mongoimport \
--uri="mongodb://localhost:27017/mydb" \
--collection=users \
--file=users.jsonl \
--drop
# Import with parallel workers for large files
mongoimport \
--uri="mongodb://localhost:27017/mydb" \
--collection=users \
--file=large_users.jsonl \
--numInsertionWorkers=4

mongoexport는 컬렉션의 각 문서를 단일 JSON 줄로 출력합니다. 출력을 필터링, 프로젝션, 정렬할 수 있습니다. 결과는 모든 다운스트림 시스템에서 처리할 수 있는 유효한 JSONL 파일입니다.

mongoexport로 MongoDB를 JSONL로 내보내기
# Export entire collection as JSONL (default format)
mongoexport \
--uri="mongodb://localhost:27017/mydb" \
--collection=users \
--out=users_export.jsonl
# Export with query filter
mongoexport \
--uri="mongodb://localhost:27017/mydb" \
--collection=users \
--query={"active": true} \
--out=active_users.jsonl
# Export specific fields only
mongoexport \
--uri="mongodb://localhost:27017/mydb" \
--collection=users \
--fields=name,email,created_at \
--out=users_partial.jsonl
# Export with sorting
mongoexport \
--uri="mongodb://localhost:27017/mydb" \
--collection=users \
--sort={"created_at": -1} \
--out=users_sorted.jsonl

MySQL: JSON_TABLE & 스크립트로 JSONL 처리

MySQL에는 MongoDB처럼 내장 JSONL 가져오기 명령이 없지만, MySQL 8.0에서 JSON 문자열에서 구조화된 데이터를 추출할 수 있는 JSON_TABLE을 도입했습니다. 대량 JSONL 가져오기의 경우 가장 안정적인 접근 방식은 원시 로딩을 위한 LOAD DATA INFILE과 추출을 위한 JSON_TABLE을 결합하거나, Python 같은 스크립팅 언어를 사용하는 것입니다.

JSONL 파일을 원시 텍스트로 스테이징 테이블에 로드한 다음 JSON_TABLE을 사용하여 필드를 구조화된 테이블로 추출합니다. 이 2단계 접근 방식은 전적으로 MySQL 내에서 작동합니다.

스테이징 테이블 & JSON_TABLE을 사용한 JSONL 가져오기
-- Step 1: Create a staging table for raw lines
CREATE TABLE jsonl_staging (
id INT AUTO_INCREMENT PRIMARY KEY,
raw_line TEXT NOT NULL
);
-- Step 2: Load the JSONL file as raw text
LOAD DATA INFILE '/var/lib/mysql-files/users.jsonl'
INTO TABLE jsonl_staging
LINES TERMINATED BY '\n'
(raw_line);
-- Step 3: Extract structured data with JSON_TABLE
INSERT INTO users (name, email, age)
SELECT jt.name, jt.email, jt.age
FROM jsonl_staging,
JSON_TABLE(
raw_line, '$'
COLUMNS (
name VARCHAR(255) PATH '$.name',
email VARCHAR(255) PATH '$.email',
age INT PATH '$.age'
)
) AS jt;
-- Step 4: Clean up staging table
DROP TABLE jsonl_staging;
-- Verify the import
SELECT * FROM users LIMIT 5;

더 유연한 처리를 위해 mysql-connector-python을 사용하는 Python 스크립트로 JSONL 파일을 읽고 레코드를 배치 삽입합니다. 이 접근 방식은 대용량 파일을 효율적으로 처리하고 상세한 오류 보고를 제공합니다.

MySQL JSONL 가져오기를 위한 Python 스크립트
import json
import mysql.connector
def import_jsonl_to_mysql(file_path, config):
conn = mysql.connector.connect(**config)
cursor = conn.cursor()
batch = []
batch_size = 1000
total = 0
insert_sql = (
"INSERT INTO users (name, email, age) "
"VALUES (%s, %s, %s) "
"ON DUPLICATE KEY UPDATE name=VALUES(name)"
)
with open(file_path, 'r') as f:
for line_num, line in enumerate(f, 1):
line = line.strip()
if not line:
continue
try:
record = json.loads(line)
batch.append((
record['name'],
record['email'],
record.get('age'),
))
except (json.JSONDecodeError, KeyError) as e:
print(f"Skipping line {line_num}: {e}")
continue
if len(batch) >= batch_size:
cursor.executemany(insert_sql, batch)
conn.commit()
total += len(batch)
batch = []
print(f"Imported {total} records...")
if batch:
cursor.executemany(insert_sql, batch)
conn.commit()
total += len(batch)
cursor.close()
conn.close()
print(f"Done. Imported {total} records.")
# Usage
import_jsonl_to_mysql('users.jsonl', {
'host': 'localhost',
'user': 'root',
'password': 'secret',
'database': 'mydb'
})

SQLite: 경량 JSONL 가져오기

SQLite는 로컬 개발과 소규모 데이터셋에 탁월한 선택입니다. SQLite에는 네이티브 JSONL 가져오기 명령이 없지만, JSON1 확장이 JSON 데이터 작업을 위한 json_extract()를 제공하며, 간단한 Python 스크립트로 SQLite의 내장 트랜잭션 지원을 사용하여 JSONL 파일을 효율적으로 가져올 수 있습니다.

Python의 내장 sqlite3 모듈을 사용하여 JSONL 파일을 읽고 SQLite 데이터베이스에 레코드를 삽입합니다. 모든 삽입을 단일 트랜잭션으로 감싸면 대용량 파일의 성능이 크게 향상됩니다.

SQLite JSONL 가져오기를 위한 Python 스크립트
import json
import sqlite3
def import_jsonl_to_sqlite(jsonl_path, db_path):
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Create table if it doesn't exist
cursor.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE,
age INTEGER,
raw_json TEXT
)
""")
total = 0
errors = 0
with open(jsonl_path, 'r') as f:
# Use a transaction for bulk insert performance
conn.execute('BEGIN TRANSACTION')
for line_num, line in enumerate(f, 1):
line = line.strip()
if not line:
continue
try:
record = json.loads(line)
cursor.execute(
"INSERT OR IGNORE INTO users "
"(name, email, age, raw_json) "
"VALUES (?, ?, ?, ?)",
(
record['name'],
record.get('email'),
record.get('age'),
line, # Store original JSON
)
)
total += 1
except (json.JSONDecodeError, KeyError) as e:
errors += 1
print(f"Line {line_num}: {e}")
conn.commit()
conn.close()
print(f"Imported {total} records, {errors} errors")
# Usage
import_jsonl_to_sqlite('users.jsonl', 'app.db')
# Query with json_extract (SQLite JSON1 extension)
# SELECT json_extract(raw_json, '$.name') FROM users;

대량 가져오기 모범 사례

대용량 JSONL 파일을 데이터베이스에 가져오려면 트랜잭션, 오류 처리, 리소스 관리에 세심한 주의가 필요합니다. 이러한 모범 사례는 모든 데이터베이스 시스템에 적용되며 안정적이고 프로덕션 준비된 가져오기 파이프라인을 구축하는 데 도움이 됩니다.

배치 트랜잭션

성능

트랜잭션당 한 행씩 삽입하지 마세요. 트랜잭션당 500-5000행의 배치로 그룹화하세요. 이렇게 하면 디스크 I/O와 락 오버헤드가 10-100배 감소합니다. 대부분의 데이터베이스는 배치 크기 1000에서 가장 큰 성능 향상을 보입니다. 더 크게 하면 수익이 체감하고 실패 시 롤백 비용이 증가합니다.

줄 단위 오류 처리

안정성

각 JSONL 줄을 개별적으로 파싱하고 검증하세요. 검증이나 삽입에 실패한 줄의 줄 번호와 오류 메시지를 로깅하세요. 전체 가져오기를 중단하는 대신 ON CONFLICT / ON DUPLICATE KEY를 사용하여 유니크 제약 조건 위반을 우아하게 처리하세요. 실패한 줄은 나중에 검토할 수 있도록 별도의 오류 파일에 저장하세요.

진행 상황 추적

관찰 가능성

수백만 줄의 파일의 경우 N개 레코드(예: 10,000개)마다 진행 상황을 로깅하세요. 총 읽은 줄 수, 성공적인 삽입, 건너뛴 중복, 오류를 추적하세요. 성능 병목 현상을 식별하기 위해 가져오기 속도(레코드/초)를 계산하고 표시하세요. 이 피드백은 장시간 실행되는 가져오기를 모니터링하는 데 필수적입니다.

이 범용 패턴은 배칭, 오류 처리, 진행 상황 추적을 결합합니다. 삽입 함수를 교체하여 모든 데이터베이스에 적용할 수 있습니다.

프로덕션 준비 배치 가져오기 패턴
import json
import time
def batch_import_jsonl(file_path, insert_fn, batch_size=1000):
"""Generic JSONL batch importer with error handling."""
batch = []
stats = {
'total_lines': 0,
'imported': 0,
'skipped': 0,
'errors': 0
}
start_time = time.time()
error_file = open(file_path + '.errors', 'w')
with open(file_path, 'r') as f:
for line_num, line in enumerate(f, 1):
stats['total_lines'] = line_num
line = line.strip()
if not line:
stats['skipped'] += 1
continue
try:
record = json.loads(line)
batch.append(record)
except json.JSONDecodeError as e:
stats['errors'] += 1
error_file.write(f"{line_num}\t{e}\t{line}\n")
continue
if len(batch) >= batch_size:
inserted = insert_fn(batch)
stats['imported'] += inserted
batch = []
if line_num % 10000 == 0:
elapsed = time.time() - start_time
rate = stats['imported'] / elapsed
print(
f"Progress: {line_num:,} lines | "
f"{stats['imported']:,} imported | "
f"{rate:,.0f} records/sec"
)
# Flush remaining batch
if batch:
inserted = insert_fn(batch)
stats['imported'] += inserted
error_file.close()
elapsed = time.time() - start_time
print(f"\nComplete in {elapsed:.1f}s")
print(f" Lines: {stats['total_lines']:,}")
print(f" Imported: {stats['imported']:,}")
print(f" Errors: {stats['errors']:,}")
return stats

무료 JSONL 도구로 데이터 준비하기

JSONL을 데이터베이스에 가져오기 전에 파일 형식을 검증하고 형식 간 변환하세요. 무료 온라인 도구는 브라우저에서 모든 것을 로컬로 처리하므로 데이터가 기기를 떠나지 않습니다.

온라인으로 JSONL 파일 작업하기

브라우저에서 최대 1GB의 JSONL 파일을 보고, 검증하고, 변환하세요. 업로드 불필요, 100% 프라이빗.

자주 묻는 질문

JSONL 가져오기/내보내기 — PostgreSQL, MongoDB, BigQuery 등 | jsonl.co