Сортировать огромный JSON-файл, используя bash или python

3

Требование: у меня есть файл 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
  • 0
    Тогда как можно отсортировать файл JSON? Есть ли какая-либо утилита или что-нибудь еще?
  • 0
    да, правильно, я проверил около 20 строк, и я мог их видеть.
Показать ещё 4 комментария
Теги:
sorting
jq

2 ответа

2
Лучший ответ

Вот одно решение, основанное на предложении в одном из комментариев:

Если вы можете, например, поставить префикс строк с помощью ключа сортировки, чтобы их можно было сортировать как текст, а не как 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'
  • 0
    @peak поправьте меня, если я ошибаюсь, но когда вы сортируете, тогда все данные json будут загружены в память.
  • 0
    gzcat sth.json.gz | jq -cr '"\(.ps)\t\(.)"' | sort | cut -f 2 >> abc это команда, которую я использовал. и он работает как всегда.
Показать ещё 2 комментария
0

Что бы это ни стоило, я создал тестовый сценарий над файлом 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 в качестве хранилища данных вместо простого текстового файла. Он только немного больше, чем текстовый файл, но гораздо более универсален.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню