Требование: у меня есть файл Json в формате .gz. Таким образом, когда он сжат, он составляет около ~ 500 МБ. Когда я распаковываю его, файл json становится почти размером ~ 10 ГБ. Извлеченный файл JSON содержит отдельные объекты JSON построчно. Я хочу отсортировать файл на основе поля ps
используя любой скрипт bash или программы на python.
Поскольку файл слишком велик, его не рекомендуется загружать в память. Итак, я использовал команды gzcat и cat bash для потоковой передачи данных JSON, а затем направил их в jq для сортировки. Но либо система не отвечает во время процесса, либо я получаю пустой файл в файле output.json
>cat sth2.json | parallel --pipe --group --block 1000M --recend '\n}\n' "jq -s -c 'sort_by(.ps) | .[]'" > "output.json"
>gzcat sth2.json.gz | parallel --pipe --group --block 1000M --recend '\n}\n' "jq -s -c 'sort_by(.ps) | .[]'" > "output.json"
Аппаратное обеспечение: 16 ГБ ОЗУ, процессор Core i5
Пример данных JSON: -
{
"ps":"abc"
....
}
{
"ps":"def"
......
}
{
"ps":"abc"
....
}
Ожидаемый результат:
{
"ps":"abc"
....
}
{
"ps":"abc"
....
}
{
"ps":"def"
....
}
Я не понимаю, что я делаю не так. Кто-нибудь может подсказать, как отсортировать такой огромный JSON файл? Ссылки, по которым я следовал: https://github.com/joelpurra/jq-hopkok/tree/master/src/parallelism
Кроме того, есть ли какой-нибудь способ, которым я могу сделать с помощью любого уменьшения карты без Hadoop?
Подход 1: Потоковая передача данных в локальную базу данных Sqlite.
import sqlite3
import fileinput
PATH=".../sqlite-snapshot-201904101324/testDB.db"
insert_query="INSERT INTO feeds (data) VALUES (?)"
def db_connect(db_path=PATH):
con = sqlite3.connect(db_path)
return con
con = db_connect() # connect to the database
cur = con.cursor() # instantiate a cursor obj
record_count = 0
for line in fileinput.input():
cur.execute(insert_query,(line,))
командная строка:
>gzcat sth.json.gz | python insert.py
Вот одно решение, основанное на предложении в одном из комментариев:
Если вы можете, например, поставить префикс строк с помощью ключа сортировки, чтобы их можно было сортировать как текст, а не как JSON, тогда сортировка GNU может легко сортировать файлы 10GB+ без загрузки их в память. - тот другой парень
Вы можете использовать jq, чтобы сделать это следующим образом:
jq -cr '"\(.ps)\t\(.)"'
Это создаст строки с разделенными табуляцией значениями так:
abc {"ps":"abc","x":0}
abc {"ps":"abc","x":1}
Использование опции -c гарантирует, что каждая пара (т.е. Ключ сортировки и объект) записывается в одну строку.
Теперь вы можете легко сортировать строки, например, используя sort
; и затем используйте, например, cut
для удаления поля .ps.
Наконец, если вы действительно хотите отформатировать вывод, вы можете снова использовать jq
(например, jq.
), jq.
в том, что jq по умолчанию ориентирован на поток.
Выше предполагается, что значения .ps без вкладок. Если это не так, то вы можете использовать другой разделитель полей или:
jq -cr '([.ps] | @tsv) + "\t" + tostring'
gzcat sth.json.gz | jq -cr '"\(.ps)\t\(.)"' | sort | cut -f 2 >> abc
это команда, которую я использовал. и он работает как всегда.
Что бы это ни стоило, я создал тестовый сценарий над файлом data.json.gz
содержащим 1 000 000 объектов, записанных вплотную в data.json.gz
напечатанном JSON (то есть каждый объект занимает несколько строк). Файл данных имеет размер 160 МБ в несжатом виде и содержит такие объекты:
{
"ps": "000F56EN9H",
"d": 22556399,
"w": "RiYZLaWsSYcCtAoGxhZFSFFLthBurISXwmnAVFtKcuErEuumIzkWCuCtWTBAQJosmtIRrLyPbEDwjtWmsZPZDuYtshPGPrlmPDoy"
}
В моей системе я мог читать из файла .gz и разбивать его на строки, содержащие по одному объекту в Python, со скоростью около 250 000 объектов в секунду.
def get_obj_strings():
with gzip.open(GZ_PATH, 'rt', encoding='ascii', newline=None) as f:
remainder = ''
while True:
chunk = f.read(CHUNK_SIZE)
if not chunk:
break
chunk = remainder + chunk
start = 0
while True:
end = chunk.find('\n}\n', start)
if end > -1:
yield [chunk[start:end + 3]]
start = end + 3
else:
break
remainder = chunk[start:]
encoding
которая есть в вашем текстовом файле,CHUNK_SIZE
размером 1 МБ, вы также можете поэкспериментировать с этим.При сохранении этих элементов в базе данных SQLite...
def init_data_json_db(conn):
conn.executescript('''
DROP TABLE IF EXISTS data;
CREATE TABLE data (obj TEXT);
PRAGMA synchronous=OFF;
''')
conn.commit()
... скорость немного замедлилась, до 150 000 объектов в секунду. Я ожидаю, что производительность в значительной степени зависит от того, сколько отдельных объектов у вас есть.
def load_data_json_db(conn, records):
print("Loading data from %s to %s..." % (GZ_PATH, DB_PATH))
t0 = time.time()
init_data_json_db(conn)
conn.executemany('INSERT INTO data (obj) VALUES (?);', records)
conn.commit()
seconds_taken = time.time() - t0
result = next(conn.execute('SELECT COUNT(*) FROM data;'))
rows_per_second = result[0] / max(seconds_taken, 0.00000000001)
print("Loaded %s records loaded in %1.1f s. (%1.0f/s)" % (result[0], seconds_taken, rows_per_second))
В целом, потоковая передача 160 МБ /1 000 000 элементов в SQLite занимает у меня около 7 секунд, YMMV. Потоковая передача 10 ГБ данных займет пропорционально больше времени.
Сортировка данных в SQL по ключу ps
занимает около 2 секунд. На производительность сортировки влияет длина ключа.
D:\> sqlite3.exe "data.json.db"
sqlite> .timer on
sqlite> SELECT obj FROM data ORDER BY json_extract(obj, '$.ps') LIMIT 1;
{
"ps": "0000CNJIN3",
"d": 52857163,
"w": "AixNrVQkagXevocrSoTlJzdZmUBFlgRlArCXPcHTXHMlJeCWQmWlEWosyDhgeHdpfyJhrXLOvhWrCkaldKLkvPJhWeTdbETHMhtg"
}
Run Time: real 2.176 user 1.731611 sys 0.421203
Разумно предположить, что загрузка данных будет быстрее, если вместо Python используется команда .import
CLI sqlite, но для этого требуется, чтобы каждый объект находился на отдельной строке (т. .import
Входные данные фактически превращаются в CSV с одним столбцом).
gifcat
выходных данных gifcat
через sed
для удаления лишних символов новой строки, а затем прямая sqlite3
в sqlite3
может сработать, попробуйте и сравните.
После этого вы можете использовать интерфейс командной строки sqlite3
чтобы экспортировать отсортированный результат запроса прямо в файл, возможно, снова сжав его в процессе, так что можно полностью обойти Python и сделать все это в сценарии оболочки.
Стоит подумать о сохранении базы данных SQLite в качестве хранилища данных вместо простого текстового файла. Он только немного больше, чем текстовый файл, но гораздо более универсален.