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을 사용하여 파일을 데이터베이스로 직접 스트리밍합니다.
-- Create a staging table with a single jsonb columnCREATE TABLE staging_import (data jsonb);-- Import the JSONL file (each line becomes one row)\COPY staging_import (data) FROM 'users.jsonl';-- Verify the importSELECT count(*) FROM staging_import;SELECT data->>'name' AS name, data->>'email' AS emailFROM staging_importLIMIT 5;-- Move data into a structured tableINSERT INTO users (name, email, age)SELECTdata->>'name',data->>'email',(data->>'age')::intFROM staging_import;
데이터를 JSONL로 내보내려면 row_to_json() 또는 to_jsonb()를 사용하여 각 행을 JSON 객체로 변환한 다음 COPY로 파일에 결과를 출력합니다. 각 행이 출력 파일의 한 줄이 됩니다.
-- 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_atFROM usersWHERE active = trueORDER BY id) t) TO 'active_users.jsonl';-- Verify: each line is valid JSON-- {"id":1,"name":"Alice","email":"alice@example.com",...}
프로덕션 워크로드의 경우 psycopg2를 사용하는 Python 스크립트로 JSONL 파일을 읽고 타입이 지정된 컬럼에 레코드를 삽입합니다. 이를 통해 검증, 오류 처리, 배치 크기에 대한 완전한 제어가 가능합니다.
import jsonimport psycopg2from psycopg2.extras import execute_valuesdef import_jsonl_to_postgres(file_path, conn_string):conn = psycopg2.connect(conn_string)cur = conn.cursor()batch = []batch_size = 1000total = 0with open(file_path, 'r') as f:for line_num, line in enumerate(f, 1):line = line.strip()if not line:continuetry: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}")continueif 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.")# Usageimport_jsonl_to_postgres('users.jsonl', 'postgresql://localhost/mydb')
MongoDB: 네이티브 JSONL 지원
MongoDB는 커맨드라인 Database Tools를 통해 퍼스트클래스 JSONL 지원을 제공합니다. mongoimport와 mongoexport 유틸리티는 기본적으로 JSONL 형식을 사용하므로, JSONL 데이터 교환에 가장 쉽게 사용할 수 있는 데이터베이스 중 하나입니다. JSONL 파일의 각 줄은 MongoDB 문서에 직접 매핑됩니다.
mongoimport는 JSONL 파일을 읽고 각 줄을 지정된 컬렉션에 문서로 삽입합니다. 업서트 모드, 필드 타입 변환, 높은 처리량을 위한 병렬 삽입을 지원합니다.
# Basic import: each line becomes a documentmongoimport \--uri="mongodb://localhost:27017/mydb" \--collection=users \--file=users.jsonl# Upsert mode: update existing documents by _idmongoimport \--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 filesmongoimport \--uri="mongodb://localhost:27017/mydb" \--collection=users \--file=large_users.jsonl \--numInsertionWorkers=4
mongoexport는 컬렉션의 각 문서를 단일 JSON 줄로 출력합니다. 출력을 필터링, 프로젝션, 정렬할 수 있습니다. 결과는 모든 다운스트림 시스템에서 처리할 수 있는 유효한 JSONL 파일입니다.
# Export entire collection as JSONL (default format)mongoexport \--uri="mongodb://localhost:27017/mydb" \--collection=users \--out=users_export.jsonl# Export with query filtermongoexport \--uri="mongodb://localhost:27017/mydb" \--collection=users \--query={"active": true} \--out=active_users.jsonl# Export specific fields onlymongoexport \--uri="mongodb://localhost:27017/mydb" \--collection=users \--fields=name,email,created_at \--out=users_partial.jsonl# Export with sortingmongoexport \--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 내에서 작동합니다.
-- Step 1: Create a staging table for raw linesCREATE TABLE jsonl_staging (id INT AUTO_INCREMENT PRIMARY KEY,raw_line TEXT NOT NULL);-- Step 2: Load the JSONL file as raw textLOAD DATA INFILE '/var/lib/mysql-files/users.jsonl'INTO TABLE jsonl_stagingLINES TERMINATED BY '\n'(raw_line);-- Step 3: Extract structured data with JSON_TABLEINSERT INTO users (name, email, age)SELECT jt.name, jt.email, jt.ageFROM 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 tableDROP TABLE jsonl_staging;-- Verify the importSELECT * FROM users LIMIT 5;
더 유연한 처리를 위해 mysql-connector-python을 사용하는 Python 스크립트로 JSONL 파일을 읽고 레코드를 배치 삽입합니다. 이 접근 방식은 대용량 파일을 효율적으로 처리하고 상세한 오류 보고를 제공합니다.
import jsonimport mysql.connectordef import_jsonl_to_mysql(file_path, config):conn = mysql.connector.connect(**config)cursor = conn.cursor()batch = []batch_size = 1000total = 0insert_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:continuetry: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}")continueif 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.")# Usageimport_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 데이터베이스에 레코드를 삽입합니다. 모든 삽입을 단일 트랜잭션으로 감싸면 대용량 파일의 성능이 크게 향상됩니다.
import jsonimport sqlite3def import_jsonl_to_sqlite(jsonl_path, db_path):conn = sqlite3.connect(db_path)cursor = conn.cursor()# Create table if it doesn't existcursor.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 = 0errors = 0with open(jsonl_path, 'r') as f:# Use a transaction for bulk insert performanceconn.execute('BEGIN TRANSACTION')for line_num, line in enumerate(f, 1):line = line.strip()if not line:continuetry: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 += 1except (json.JSONDecodeError, KeyError) as e:errors += 1print(f"Line {line_num}: {e}")conn.commit()conn.close()print(f"Imported {total} records, {errors} errors")# Usageimport_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 jsonimport timedef 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_numline = line.strip()if not line:stats['skipped'] += 1continuetry:record = json.loads(line)batch.append(record)except json.JSONDecodeError as e:stats['errors'] += 1error_file.write(f"{line_num}\t{e}\t{line}\n")continueif len(batch) >= batch_size:inserted = insert_fn(batch)stats['imported'] += insertedbatch = []if line_num % 10000 == 0:elapsed = time.time() - start_timerate = stats['imported'] / elapsedprint(f"Progress: {line_num:,} lines | "f"{stats['imported']:,} imported | "f"{rate:,.0f} records/sec")# Flush remaining batchif batch:inserted = insert_fn(batch)stats['imported'] += insertederror_file.close()elapsed = time.time() - start_timeprint(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을 데이터베이스에 가져오기 전에 파일 형식을 검증하고 형식 간 변환하세요. 무료 온라인 도구는 브라우저에서 모든 것을 로컬로 처리하므로 데이터가 기기를 떠나지 않습니다.