Advanced JSONL Techniques
Master compression algorithms, parallel processing, memory optimization, and distributed systems for production-scale JSONL deployments

Enterprise-Scale JSONL
Why Advanced Techniques Matter
When processing terabytes of JSONL data daily, basic approaches fail. Compression, streaming and parallelism cut storage, raise throughput and let you process datasets that do not fit in memory - how much depends on your records, so measure on your own data.
- Process files far larger than RAM by streaming one line at a time
- Cut storage costs with compression - repetitive keys compress well
- Spread CPU-bound parsing across every core
- Handle schema evolution without breaking changes
- Scale horizontally across distributed clusters
What Each Technique Buys
The trade-offs - ratios and speeds depend on your data, so benchmark before you choose. Measured single-machine figures are on the performance page.
- gzip: readable everywhere, moderate speed and ratio
- zstd: the project's own lzbench table shows it several times faster than zlib at a comparable ratio
- brotli: strong ratios at high levels, slow to compress there; native to HTTP
- Parallel: scales with cores while parsing, not disk, is the bottleneck
- Streaming: memory bounded by the largest record, not the file size
Compression Algorithms
gzip - Universal Standard
gzip is the most widely supported compression format with excellent balance of speed and compression ratio.
# Python: gzip compression and decompression
import gzip
import json
# Compress JSONL file
def compress_jsonl(input_file, output_file, compression_level=6):
"""
Compress JSONL with gzip
compression_level: 1 (fastest) to 9 (best compression)
"""
with open(input_file, 'rb') as f_in:
with gzip.open(output_file, 'wb', compresslevel=compression_level) as f_out:
# Copy in chunks to handle large files
chunk_size = 1024 * 1024 # 1MB chunks
while True:
chunk = f_in.read(chunk_size)
if not chunk:
break
f_out.write(chunk)
# Decompress and stream
def stream_gzip_jsonl(filepath):
"""Stream decompress gzipped JSONL"""
with gzip.open(filepath, 'rt', encoding='utf-8') as f:
for line in f:
yield json.loads(line)
# Usage
compress_jsonl('data.jsonl', 'data.jsonl.gz', compression_level=9)
# Process compressed file without decompressing to disk
for record in stream_gzip_jsonl('data.jsonl.gz'):
process(record)
# Append to gzipped file (requires decompression)
def append_to_gzip(filepath, new_records):
"""Append records to gzipped JSONL"""
# Read existing records
existing = []
try:
with gzip.open(filepath, 'rt') as f:
existing = [json.loads(line) for line in f]
except FileNotFoundError:
pass
# Combine and rewrite
all_records = existing + new_records
with gzip.open(filepath, 'wt') as f:
for record in all_records:
f.write(json.dumps(record) + '\n')
# Benchmark different compression levels
import time
def benchmark_compression(input_file):
"""Test all gzip compression levels"""
results = []
for level in range(1, 10):
start = time.time()
compress_jsonl(input_file, f'test_{level}.gz', level)
duration = time.time() - start
size = os.path.getsize(f'test_{level}.gz')
original_size = os.path.getsize(input_file)
ratio = (1 - size / original_size) * 100
results.append({
'level': level,
'duration': duration,
'size': size,
'ratio': ratio,
'speed_mb_s': os.path.getsize(input_file) / 1024 / 1024 / duration
})
print(f"Level {level}: {ratio:.1f}% reduction, {duration:.2f}s, {results[-1]['speed_mb_s']:.1f} MB/s")
return results
Pro Tip: Level 6 offers the best speed/ratio tradeoff for most use cases. Use level 1 for maximum speed, level 9 for archival storage.
zstd - Modern High Performance
Zstandard (zstd) offers better compression ratios than gzip with significantly faster compression/decompression speeds.
import zstandard as zstd
import json
# Compress with zstd
def compress_zstd(input_file, output_file, level=3):
"""
Compress JSONL with zstd
level: 1-22 (3 is default, 19+ for archival)
"""
cctx = zstd.ZstdCompressor(level=level)
with open(input_file, 'rb') as f_in:
with open(output_file, 'wb') as f_out:
f_out.write(cctx.compress(f_in.read()))
# Stream compression (better for large files)
def stream_compress_zstd(input_file, output_file, level=3):
"""Stream compress for memory efficiency"""
cctx = zstd.ZstdCompressor(level=level)
with open(input_file, 'rb') as f_in:
with open(output_file, 'wb') as f_out:
# Compress in chunks
compressor = cctx.stream_writer(f_out)
for chunk in iter(lambda: f_in.read(1024 * 1024), b''):
compressor.write(chunk)
compressor.flush(zstd.FLUSH_FRAME)
# Decompress and parse
def read_zstd_jsonl(filepath):
"""Read and parse zstd compressed JSONL"""
dctx = zstd.ZstdDecompressor()
with open(filepath, 'rb') as f:
with dctx.stream_reader(f) as reader:
text_stream = io.TextIOWrapper(reader, encoding='utf-8')
for line in text_stream:
yield json.loads(line)
# Train dictionary for better compression
def train_zstd_dictionary(sample_files, dict_size=100 * 1024):
"""Train custom dictionary on sample data"""
samples = []
for filepath in sample_files:
with open(filepath, 'rb') as f:
samples.append(f.read())
# Train dictionary
dict_data = zstd.train_dictionary(dict_size, samples)
return dict_data
# Compress with trained dictionary
def compress_with_dict(input_file, output_file, dict_data, level=3):
"""Compress using trained dictionary for better ratio"""
cctx = zstd.ZstdCompressor(level=level, dict_data=dict_data)
with open(input_file, 'rb') as f_in:
with open(output_file, 'wb') as f_out:
f_out.write(cctx.compress(f_in.read()))
# Comparison: zstd vs gzip
def compare_compression():
"""Compare different algorithms"""
import time
import gzip
input_file = 'large_data.jsonl'
original_size = os.path.getsize(input_file)
# Test gzip
start = time.time()
with open(input_file, 'rb') as f_in:
with gzip.open('test.gz', 'wb') as f_out:
f_out.write(f_in.read())
gzip_time = time.time() - start
gzip_size = os.path.getsize('test.gz')
# Test zstd
start = time.time()
compress_zstd(input_file, 'test.zst', level=3)
zstd_time = time.time() - start
zstd_size = os.path.getsize('test.zst')
print(f"Original: {original_size / 1024 / 1024:.1f} MB")
print(f"gzip: {gzip_size / 1024 / 1024:.1f} MB ({gzip_time:.2f}s) - {(1 - gzip_size/original_size)*100:.1f}% reduction")
print(f"zstd: {zstd_size / 1024 / 1024:.1f} MB ({zstd_time:.2f}s) - {(1 - zstd_size/original_size)*100:.1f}% reduction")
print(f"zstd is {gzip_time / zstd_time:.1f}x faster")
# Output example:
# Original: 1024.0 MB
# gzip: 102.4 MB (12.5s) - 90.0% reduction
# zstd: 81.9 MB (2.5s) - 92.0% reduction
# zstd is 5.0x faster
brotli - Maximum Compression
Brotli achieves the highest compression ratios, ideal for web delivery and archival storage.
import brotli
import json
# Compress with brotli
def compress_brotli(input_file, output_file, quality=11):
"""
Compress JSONL with brotli
quality: 0-11 (11 is maximum compression)
"""
with open(input_file, 'rb') as f_in:
data = f_in.read()
compressed = brotli.compress(data, quality=quality)
with open(output_file, 'wb') as f_out:
f_out.write(compressed)
# Stream decompress
def read_brotli_jsonl(filepath):
"""Read brotli compressed JSONL"""
with open(filepath, 'rb') as f:
compressed = f.read()
decompressed = brotli.decompress(compressed)
text = decompressed.decode('utf-8')
for line in text.split('\n'):
if line.strip():
yield json.loads(line)
# HTTP response compression
from flask import Flask, Response
import io
app = Flask(__name__)
@app.route('/api/data.jsonl')
def get_data_brotli():
"""Serve brotli-compressed JSONL over HTTP"""
def generate():
for record in get_records():
yield json.dumps(record) + '\n'
# Compress response
output = io.BytesIO()
content = ''.join(generate()).encode('utf-8')
compressed = brotli.compress(content, quality=6)
return Response(
compressed,
mimetype='application/x-ndjson',
headers={
'Content-Encoding': 'br',
'Content-Length': len(compressed)
}
)
LZ4 - Extreme Speed
import lz4.frame
import json
# LZ4 compression
def compress_lz4(input_file, output_file):
"""Ultra-fast LZ4 compression"""
with open(input_file, 'rb') as f_in:
with lz4.frame.open(output_file, 'wb') as f_out:
f_out.write(f_in.read())
# Stream processing
def process_lz4_jsonl(filepath):
"""Process LZ4 compressed JSONL"""
with lz4.frame.open(filepath, 'rt', encoding='utf-8') as f:
for line in f:
record = json.loads(line)
process(record)
# Benchmark: LZ4 vs others
def benchmark_all():
"""Compare all compression algorithms"""
import time
algorithms = {
'gzip': lambda i, o: compress_jsonl(i, o, 6),
'zstd': lambda i, o: compress_zstd(i, o, 3),
'brotli': lambda i, o: compress_brotli(i, o, 6),
'lz4': lambda i, o: compress_lz4(i, o)
}
results = {}
input_file = 'test.jsonl'
original_size = os.path.getsize(input_file)
for name, compress_fn in algorithms.items():
output = f'test.{name}'
start = time.time()
compress_fn(input_file, output)
duration = time.time() - start
size = os.path.getsize(output)
ratio = (1 - size / original_size) * 100
speed = original_size / 1024 / 1024 / duration
results[name] = {
'size_mb': size / 1024 / 1024,
'ratio': ratio,
'time': duration,
'speed_mb_s': speed
}
print(f"{name:8} {ratio:5.1f}% reduction, {speed:6.1f} MB/s")
return results
Streaming Compression
Compress While Streaming
import gzip
import json
from typing import Iterator
class StreamingCompressor:
"""Compress JSONL records as they're generated"""
def __init__(self, output_file, compression='gzip', level=6):
self.output_file = output_file
self.compression = compression
self.level = level
self.compressor = None
def __enter__(self):
if self.compression == 'gzip':
self.compressor = gzip.open(
self.output_file, 'wt',
compresslevel=self.level
)
elif self.compression == 'zstd':
import zstandard as zstd
cctx = zstd.ZstdCompressor(level=self.level)
f = open(self.output_file, 'wb')
self.compressor = cctx.stream_writer(f, closefd=True)
return self
def __exit__(self, *args):
if self.compressor:
self.compressor.close()
def write_record(self, record: dict):
"""Write single record to compressed stream"""
line = json.dumps(record) + '\n'
if self.compression == 'zstd':
self.compressor.write(line.encode('utf-8'))
else:
self.compressor.write(line)
# Usage: Generate and compress on-the-fly
with StreamingCompressor('output.jsonl.gz', compression='gzip') as compressor:
for i in range(1000000):
record = {
'id': i,
'data': generate_data(),
'timestamp': datetime.now().isoformat()
}
compressor.write_record(record)
# Database export with streaming compression
def export_database_compressed(table, output_file):
"""Export database table to compressed JSONL"""
import psycopg2
conn = psycopg2.connect("dbname=mydb")
cursor = conn.cursor()
with StreamingCompressor(output_file) as compressor:
cursor.execute(f"SELECT * FROM {table}")
while True:
rows = cursor.fetchmany(1000)
if not rows:
break
for row in rows:
record = dict(zip([desc[0] for desc in cursor.description], row))
compressor.write_record(record)
cursor.close()
conn.close()
# Parallel compression with multiprocessing
from multiprocessing import Pool, Queue
import queue
def parallel_compress_worker(input_queue, output_file, worker_id):
"""Worker process for parallel compression"""
temp_file = f"{output_file}.part{worker_id}"
with gzip.open(temp_file, 'wt') as f:
while True:
try:
record = input_queue.get(timeout=1)
if record is None: # Poison pill
break
f.write(json.dumps(record) + '\n')
except queue.Empty:
continue
def parallel_compress(records: Iterator, output_file, num_workers=4):
"""Compress using multiple processes"""
from multiprocessing import Manager
manager = Manager()
input_queue = manager.Queue(maxsize=1000)
# Start workers
pool = Pool(num_workers)
workers = [
pool.apply_async(parallel_compress_worker, (input_queue, output_file, i))
for i in range(num_workers)
]
# Feed records to queue
for record in records:
input_queue.put(record)
# Send stop signal
for _ in range(num_workers):
input_queue.put(None)
# Wait for completion
for worker in workers:
worker.get()
pool.close()
pool.join()
# Merge part files
with gzip.open(output_file, 'wt') as f_out:
for i in range(num_workers):
part_file = f"{output_file}.part{i}"
with gzip.open(part_file, 'rt') as f_in:
for line in f_in:
f_out.write(line)
os.remove(part_file)
Parallel Processing
Python Multiprocessing
import multiprocessing as mp
import json
from typing import Callable
def process_chunk(chunk_data):
"""Process a chunk of JSONL records"""
chunk_id, lines = chunk_data
results = []
for line in lines:
record = json.loads(line)
# Process record...
processed = transform_record(record)
results.append(processed)
return results
def parallel_process_jsonl(input_file, output_file, num_processes=None):
"""Process JSONL file in parallel"""
if num_processes is None:
num_processes = mp.cpu_count()
# Read file and split into chunks
with open(input_file, 'r') as f:
lines = f.readlines()
chunk_size = len(lines) // num_processes
chunks = [
(i, lines[i*chunk_size:(i+1)*chunk_size])
for i in range(num_processes)
]
# Handle remainder
if len(lines) % num_processes:
chunks[-1] = (num_processes-1, lines[(num_processes-1)*chunk_size:])
# Process in parallel
with mp.Pool(num_processes) as pool:
results = pool.map(process_chunk, chunks)
# Flatten results and write
with open(output_file, 'w') as f:
for chunk_results in results:
for record in chunk_results:
f.write(json.dumps(record) + '\n')
# Memory-efficient streaming parallel processing.
# The process targets live at module level: Python 3.14 made forkserver the
# default start method on Linux (macOS and Windows already spawn), and
# nested functions cannot be pickled into those workers. process_fn must
# be a module-level function too, and the caller needs an
# if __name__ == '__main__': guard.
def _reader(input_file, input_queue, chunk_size, num_workers):
"""Read the file and queue batches of lines"""
with open(input_file, 'r', encoding='utf-8') as f:
batch = []
for line in f:
batch.append(line)
if len(batch) >= chunk_size:
input_queue.put(batch)
batch = []
if batch:
input_queue.put(batch)
for _ in range(num_workers):
input_queue.put(None) # One stop signal per worker
def _worker(input_queue, output_queue, process_fn):
"""Process batches until the stop signal arrives"""
while True:
batch = input_queue.get()
if batch is None:
break
output_queue.put([process_fn(json.loads(line)) for line in batch])
output_queue.put(None)
def _writer(output_file, output_queue, num_workers):
"""Write results until every worker has finished"""
completed_workers = 0
with open(output_file, 'w', encoding='utf-8') as f:
while completed_workers < num_workers:
results = output_queue.get()
if results is None:
completed_workers += 1
continue
for record in results:
f.write(json.dumps(record) + '\n')
def stream_parallel_process(input_file, output_file, process_fn, chunk_size=10000):
"""Process large files without loading them into memory (output order is not preserved)"""
num_workers = mp.cpu_count()
input_queue = mp.Queue(maxsize=10)
output_queue = mp.Queue(maxsize=10)
reader = mp.Process(target=_reader, args=(input_file, input_queue, chunk_size, num_workers))
workers = [
mp.Process(target=_worker, args=(input_queue, output_queue, process_fn))
for _ in range(num_workers)
]
writer = mp.Process(target=_writer, args=(output_file, output_queue, num_workers))
reader.start()
for w in workers:
w.start()
writer.start()
reader.join()
for w in workers:
w.join()
writer.join()
Go Goroutines
package main
import (
"bufio"
"encoding/json"
"os"
"runtime"
"strings"
"sync"
)
type Record struct {
ID int `json:"id"`
Data string `json:"data"`
}
func processRecord(record Record) Record {
// Process record...
record.Data = strings.ToUpper(record.Data)
return record
}
func parallelProcessJSONL(inputFile, outputFile string) error {
numWorkers := runtime.NumCPU()
// Channels
recordsChan := make(chan Record, 1000)
resultsChan := make(chan Record, 1000)
var wg sync.WaitGroup
// Start workers
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for record := range recordsChan {
processed := processRecord(record)
resultsChan <- processed
}
}()
}
// Writer goroutine
writerDone := make(chan bool)
go func() {
f, _ := os.Create(outputFile)
defer f.Close()
writer := bufio.NewWriter(f)
defer writer.Flush()
for result := range resultsChan {
data, _ := json.Marshal(result)
writer.Write(data)
writer.WriteString("\n")
}
writerDone <- true
}()
// Read and distribute work
f, err := os.Open(inputFile)
if err != nil {
return err
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
var record Record
json.Unmarshal(scanner.Bytes(), &record)
recordsChan <- record
}
close(recordsChan)
wg.Wait()
close(resultsChan)
<-writerDone
return nil
}
func main() {
parallelProcessJSONL("input.jsonl", "output.jsonl")
}
Rust Rayon
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
#[derive(Debug, Serialize, Deserialize)]
struct Record {
id: i32,
data: String,
}
fn process_record(record: Record) -> Record {
// Transform record
Record {
id: record.id,
data: record.data.to_uppercase(),
}
}
fn parallel_process_jsonl(input_path: &str, output_path: &str) -> std::io::Result<()> {
// Read all lines
let file = File::open(input_path)?;
let reader = BufReader::new(file);
let lines: Vec<String> = reader.lines().collect::<Result<_, _>>()?;
// Process in parallel with Rayon
let results: Vec<Record> = lines
.par_iter()
.filter_map(|line| serde_json::from_str::<Record>(line).ok())
.map(process_record)
.collect();
// Write results
let mut output = File::create(output_path)?;
for record in results {
let json = serde_json::to_string(&record)?;
writeln!(output, "{}", json)?;
}
Ok(())
}
fn main() {
parallel_process_jsonl("input.jsonl", "output.jsonl").unwrap();
}
Memory-Efficient Parsing
Generator-Based Streaming
import json
from typing import Iterator, Dict, Any
def stream_jsonl(filepath: str, buffer_size: int = 8192) -> Iterator[Dict[str, Any]]:
"""
Memory-efficient JSONL streaming
Uses only buffer_size bytes of memory regardless of file size
"""
with open(filepath, 'r', buffering=buffer_size) as f:
for line in f:
yield json.loads(line)
# Process 100GB file using 8KB memory
for record in stream_jsonl('huge_file.jsonl'):
process(record)
# Filter while streaming
def filter_stream(filepath: str, condition: callable) -> Iterator[Dict]:
"""Stream and filter without loading entire file"""
for record in stream_jsonl(filepath):
if condition(record):
yield record
# Usage: Extract specific records
matches = filter_stream('data.jsonl', lambda r: r.get('status') == 'active')
for match in matches:
print(match)
# Aggregate while streaming
def aggregate_stream(filepath: str, key: str) -> Dict:
"""Compute aggregates without loading full dataset"""
aggregates = {
'count': 0,
'sum': 0,
'min': float('inf'),
'max': float('-inf')
}
for record in stream_jsonl(filepath):
value = record.get(key, 0)
aggregates['count'] += 1
aggregates['sum'] += value
aggregates['min'] = min(aggregates['min'], value)
aggregates['max'] = max(aggregates['max'], value)
aggregates['average'] = aggregates['sum'] / aggregates['count']
return aggregates
# Memory profiling
import tracemalloc
def profile_memory():
"""Profile memory usage of different approaches"""
# Bad: Load entire file
tracemalloc.start()
with open('data.jsonl', 'r') as f:
data = [json.loads(line) for line in f]
current, peak = tracemalloc.get_traced_memory()
print(f"Load all: {peak / 1024 / 1024:.1f} MB")
tracemalloc.stop()
# Good: Stream
tracemalloc.start()
for record in stream_jsonl('data.jsonl'):
pass
current, peak = tracemalloc.get_traced_memory()
print(f"Stream: {peak / 1024 / 1024:.1f} MB")
tracemalloc.stop()
# Output:
# Load all: 2048.5 MB
# Stream: 0.8 MB
Object Pooling
import json
from collections import deque
class RecordPool:
"""Reuse record objects to reduce GC pressure"""
def __init__(self, max_size=1000):
self.pool = deque(maxlen=max_size)
def get(self):
"""Get record from pool or create new"""
return self.pool.pop() if self.pool else {}
def release(self, record):
"""Return record to pool"""
record.clear()
self.pool.append(record)
def process_with_pooling(filepath):
"""Process JSONL with object pooling"""
pool = RecordPool()
with open(filepath, 'r') as f:
for line in f:
record = pool.get()
record.update(json.loads(line))
# Process record...
process(record)
# Return to pool
pool.release(record)
Schema Evolution & Versioning
Version Field Strategy
# Include version in every record
{"_version": "1.0", "id": 1, "name": "Alice"}
{"_version": "2.0", "id": 2, "name": "Bob", "email": "[email protected]"}
{"_version": "2.1", "id": 3, "name": "Charlie", "email": "[email protected]", "verified": true}
# Python: Handle multiple versions
def parse_record(line: str) -> dict:
"""Parse record with version-specific logic"""
record = json.loads(line)
version = record.get('_version', '1.0')
if version == '1.0':
# Migrate v1 to current schema
record['email'] = f"{record['name'].lower()}@example.com"
record['verified'] = False
elif version == '2.0':
# Add missing fields from v2.1
record.setdefault('verified', False)
# Remove version field from working data
record.pop('_version', None)
return record
# Batch migration to new version
def migrate_schema(input_file, output_file, target_version='3.0'):
"""Migrate JSONL file to new schema version"""
def migrate_to_v3(record):
"""Upgrade any version to v3"""
# Parse with version handling
parsed = parse_record(json.dumps(record))
# Add v3 fields
parsed['created_at'] = datetime.now().isoformat()
parsed['_version'] = target_version
return parsed
with open(input_file, 'r') as f_in:
with open(output_file, 'w') as f_out:
for line in f_in:
record = json.loads(line)
migrated = migrate_to_v3(record)
f_out.write(json.dumps(migrated) + '\n')
Schema Validation
from jsonschema import validate, ValidationError
# Define schemas for each version
SCHEMAS = {
'1.0': {
'type': 'object',
'required': ['id', 'name'],
'properties': {
'id': {'type': 'integer'},
'name': {'type': 'string'}
}
},
'2.0': {
'type': 'object',
'required': ['id', 'name', 'email'],
'properties': {
'id': {'type': 'integer'},
'name': {'type': 'string'},
'email': {'type': 'string', 'format': 'email'}
}
}
}
def validate_jsonl(filepath, report_errors=True):
"""Validate all records against their declared schema"""
errors = []
valid_count = 0
with open(filepath, 'r') as f:
for line_num, line in enumerate(f, 1):
try:
record = json.loads(line)
version = record.get('_version', '1.0')
if version not in SCHEMAS:
errors.append({
'line': line_num,
'error': f'Unknown schema version: {version}'
})
continue
validate(instance=record, schema=SCHEMAS[version])
valid_count += 1
except json.JSONDecodeError as e:
errors.append({
'line': line_num,
'error': f'JSON parse error: {e}'
})
except ValidationError as e:
errors.append({
'line': line_num,
'error': f'Schema validation failed: {e.message}'
})
if report_errors:
for error in errors:
print(f"Line {error['line']}: {error['error']}")
return {
'valid': valid_count,
'errors': len(errors),
'error_details': errors
}
Distributed Processing
Apache Spark
from pyspark.sql import SparkSession
from pyspark.sql.functions import *
# Initialize Spark
spark = SparkSession.builder \
.appName("JSONL Processing") \
.config("spark.executor.memory", "4g") \
.config("spark.executor.cores", "4") \
.getOrCreate()
# Read JSONL files (auto-parallelized)
df = spark.read.json("hdfs://data/*.jsonl")
# Process in parallel across cluster
result = df \
.filter(col("status") == "active") \
.groupBy("category") \
.agg(
count("*").alias("count"),
avg("price").alias("avg_price"),
sum("quantity").alias("total_quantity")
)
# Write results as JSONL
result.write.mode("overwrite").json("hdfs://output/aggregated.jsonl")
# Partition large datasets
df.repartition(100) \
.write \
.partitionBy("date", "category") \
.mode("overwrite") \
.json("hdfs://output/partitioned/")
Dask Distributed
import dask.bag as db
from dask.distributed import Client
import json
# Start distributed cluster
client = Client('scheduler-address:8786')
# Read JSONL with Dask: one partition per file (or block)
bag = db.read_text('data/*.jsonl').map(json.loads)
# Parallel transformations (lazy until written or computed)
processed = bag \
.filter(lambda r: r['status'] == 'active') \
.map(lambda r: {**r, 'processed': True})
# Write results: serialize back to lines first - to_textfiles writes strings,
# name_function fills the * in the path, and last_endline=True ends
# each file with a newline as JSONL expects
processed.map(json.dumps).to_textfiles('output/part-*.jsonl', name_function=lambda i: f'{i:05d}', last_endline=True)
Indexing for Fast Access
Offset Index
import json
class JSONLIndex:
"""Build offset index for random access"""
def __init__(self, filepath):
self.filepath = filepath
self.index = {} # key -> byte offset
self.build_index()
def build_index(self):
"""Build index of record locations"""
with open(self.filepath, 'rb') as f:
offset = 0
for line in f:
record = json.loads(line)
key = record.get('id')
if key:
self.index[key] = offset
offset = f.tell()
def get(self, key):
"""Get record by key in O(1) time"""
if key not in self.index:
return None
offset = self.index[key]
with open(self.filepath, 'rb') as f:
f.seek(offset)
line = f.readline()
return json.loads(line)
def save_index(self, index_file):
"""Save index to disk"""
with open(index_file, 'w') as f:
json.dump(self.index, f)
def load_index(self, index_file):
"""Load pre-built index"""
with open(index_file, 'r') as f:
self.index = json.load(f)
# Usage
index = JSONLIndex('large_file.jsonl')
record = index.get(12345) # Instant lookup
index.save_index('large_file.jsonl.index')
Best Practices
Compression Strategy
- Use zstd for best speed/ratio tradeoff in production
- Use gzip for maximum compatibility
- Train zstd dictionaries for 5-10% better compression
- Compress while streaming to avoid temporary files
- Use LZ4 for real-time systems requiring minimum latency
Performance Optimization
- Always stream large files - never load entirely into memory
- Use parallel processing for CPU-intensive transformations
- Profile memory usage to identify bottlenecks
- Partition large datasets for distributed processing
- Build indexes for random access patterns
Schema Management
- Include version field in every record
- Maintain backward compatibility when evolving schemas
- Validate records against declared schema version
- Document schema changes in migration scripts
- Test migration paths before deploying schema changes
Distributed Systems
- Use Spark or Dask for datasets larger than single machine RAM
- Partition data by date or category for efficient queries
- Compress partitions to reduce network transfer
- Monitor cluster resource utilization
- Implement retries for transient failures