bash 스크립트를 사용하여 다음에 대한 유효하고 간단한 ID를 생성하려고 합니다.
{"name": "John", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
{"name": "John1", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
{"name": "John2", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
{"name": "John3", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
{"id": "XXX", "name": "John", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
{"id": "XXX", "name": "John1", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
{"id": "XXX", "name": "John2", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
{"id": "XXX", "name": "John3", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
약 5,000,000개의 유사한 레코드를 갖게 되며 반복 가능하고 예측 가능한 ID를 생성하고 싶습니다. 다음 파일을 처리하는 데 시간이 제한되어 있으므로 Linux 시스템의 sql lite 데이터베이스에서 20분 이내에 이 작업을 완료해야 합니다.
MD5, SHA1은 AMD Ryzen 1900X CPU의 16개 스레드에서 GNU Parallel과 같은 작업을 수행할 수 있고 몇 분 안에 수행할 수 없다면 사용하기에는 너무 비쌉니다.
MD5를 사용해 보았는데 1분 45초 만에 28,000개의 ID에 대한 계산이 완료되었습니다. SHA1을 사용하면 2분 3초가 걸렸습니다.
저는 아주 간단하게 ID를 만들려고 합니다.
JohnGatesGermany20180
John1GatesGermany20180
John2GatesGermany20180
John3GatesGermany20180
다음 요구 사항을 충족해야 하는 경우 어떤 제안을 하시겠습니까?
- 세게 때리다
- 리눅스
- 5,000,000개의 레코드를 처리해야 합니다.
- 20분 이내
- 동일한 json 행의 경우 ID가 동일해야 합니다.
수행된 테스트:
#!/usr/local/bin/bash
while IFS= read -r line
do
uuid=$(uuidgen -s --namespace @dns --name "www.example.com" )
done < testfile1.txt
1,000,000개 행의 md5 해시:
$time bash script.sh
real 13m6.914s
user 10m24.523s
sys 2m56.095s
cksum은 1,000,000에 대해 crc 확인을 수행합니다.
#!/usr/local/bin/bash
while IFS= read -r line
do
# uuid=$(uuidgen -s --namespace @dns --name "www.example.com" )
echo "$line $uuid"|cksum >> test3.txt
done < testfile1.txt
$time bash script.sh
real 12m49.396s
user 12m23.219s
sys 4m1.417s
답변1
귀하의 스크립트가 너무 오래 걸리는 이유는 uuidgen
귀하가 각 줄에서 실행하고 있기 때문일 것입니다. cksum
각 프로세스를 시작하는 것만으로도 많은 시간이 낭비됩니다.
5M 라인 형식을 {"name": "John%d", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
tmpfs 파일 시스템의 파일에 배치하려면 다음 Python 스크립트가 몇 초 안에 완료됩니다.
#! /usr/bin/env python3
import hashlib
import sys
for line in sys.stdin:
print(hashlib.md5(line.rstrip('\n').encode('utf-8')).hexdigest())
구현하다:
$ time ./foo.py < input > output
./foo.py < input > output 6.00s user 0.13s system 99% cpu 6.135 total
% wc -l input output
5000000 input
5000000 output
10000000 total
Python이므로 행을 JSON으로 디코딩하고 각 행에 ID를 삽입할 수도 있습니다. 다음과 같은 비효율적인 코드도 있습니다.
#! /usr/bin/env python3
import hashlib
import json
import sys
for line in sys.stdin:
l = line.rstrip('\n').encode('utf-8')
o = json.loads(line)
o["id"] = hashlib.md5(l).hexdigest()
print(json.dumps(o))
1분 안에 완료:
% time ./foo.py < input > output
./foo.py < input > output 42.11s user 0.42s system 99% cpu 42.600 total
% head output
{"name": "John1", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "2dc573ccb15679f58abfc44ec8169e52"}
{"name": "John2", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "ee0583acaf8ad0e502bf5abd29f37edb"}
{"name": "John3", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "a7352ebb79db8c8fc2cc8758eadd9ea3"}
{"name": "John4", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "2062ad1b67ccdce55663bfd523ce1dfb"}
{"name": "John5", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "5f81325c104c01c3e82abd2190f14bcf"}
{"name": "John6", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "493e0c9656f74ec3616e60886ee38e6a"}
{"name": "John7", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "19af9ef2e20466d0fb0efcf03f56d3f6"}
{"name": "John8", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "2348bd47b20ac6445213254c6a8aa80b"}
{"name": "John9", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "090a521b4a858705dc69bf9c8dca6c19"}
{"name": "John10", "surname": "Gates", "country": "Germany", "age": "20", "height": "180", "id": "fc3c699323cbe399e210e4a191f04003"}
내 사양:
- 인텔® 코어™ i7-8700 CPU @ 3.20GHz × 12
- 2666MHz DDR4 메모리
uuidgen
나는 당신의 스크립트를 기반으로 4분 만에 500,000줄을 간신히 관리했습니다. 출력을 저장하도록 수정합니다.
#!/usr/bin/bash
while IFS= read -r line
do
uuidgen -s --namespace @dns --name "$line"
done < input > uuid
구현하다:
% timeout 240 ./foo.sh
% wc -l uuid
522160 uuid
답변2
JSON 행이 지시한 대로라고 가정하고 awk에서 간단한 ID 아이디어를 구현합니다. 모두 한 줄에 있습니다.
awk -F'"' 'BEGIN{OFS=FS} {$1=$1"\"id\": \""$4$8$12$16$20"\", "; }1' < input
나는 당신과 비슷한 시스템을 가지고 있지 않으므로 시간이 허용되는지 확인해야 할 것입니다.
답변3
사고 실험으로서 저는 이러한 유형의 문제를 해결하기 위해 CLI 도구를 얼마나 활용할 수 있는지 확인하고 싶었습니다. 이를 위해 빠른 해시 CLI 도구를 사용해 보고 싶습니다.xx 해시 값작업을 수행합니다.
xxHash는 RAM 제한에 가깝게 작동하는 매우 빠른 비암호화 해시 알고리즘입니다. 32비트와 64비트의 두 가지 버전으로 제공됩니다.
xxhsum
모든 프로그래밍 언어에서 작동하지만 이 실험에서는 CLI 버전, 특히 32비트 모드를 사용하므로 xxhsum -H0
.
귀하가 발견하고 다른 사람들이 말했듯이 해시 함수 CLI 도구 또는 도구를 반복해서 호출하는 것은 일반적으로 이러한 유형의 접근 방식이 실패하는 경우입니다. 이것을 5M 번 호출하는 것은 xxhsum
그것을 사용하는 데 차선책이 될 것입니다. 장점은 파일 I/O인데, 5M 행을 5M 파일로 변환하면 어떻게 될까요?
이 작업은 Linux에서 실제로 간단합니다. 다음 split
명령을 사용하십시오.
split -l 1 afile
각 파일에 한 줄씩 이러한 파일(예: 1M)을 해시하는 것이 얼마나 빠릅니까?
예제 1 라인 파일$ cat datadir/xzeyw
{"name": "John4000", "surname": "Gates", "country": "Germany", "age": "20", "height": "180"}
1M 파일이 포함된 디렉터리
$ ls -l datadir | wc -l
1000002
해시할 시간
$ { time xxhsum -H0 * > ../nfile 2>&1 ;} |& awk '/real|user|sys/ {print $1": "$2"\t"}' | tr -d '\n'
real: 0m6.998s user: 0m5.007s sys: 0m1.569s
네 맞습니다. 약 7초 정도 걸렸습니다! 나는 이것이 매우 인상적이라고 생각합니다. 이 방법을 사용하면 xxhsum
한 번만 실행하는 비용만 발생하고 1M 파일을 반복할 수 있습니다.
이 방법의 단점
물론 그 단점 중 하나는 split
여러분이 상상할 수 있듯이 이것이 가장 비용이 많이 드는 작업이 된다는 것입니다. X 줄이 포함된 단일 파일을 가져와서 단일 줄이 포함된 X 파일로 HDD에 분리해야 하기 때문입니다.
다음은 일부 데이터입니다.
./hashy.bash
make data
---------
real: 0m17.492s user: 0m12.434s sys: 0m4.788s
split data
----------
real: 2m15.180s user: 0m0.700s sys: 2m4.443s
hash data
---------
real: 0m6.487s user: 0m5.798s sys: 0m0.459s
split
여기에서 작업이 약 2분 정도 소요되었음을 알 수 있습니다 .노트:이 출력의 첫 번째 줄은 100만 줄의 JSON이 포함된 파일을 빌드하는 데 걸리는 시간을 보여줍니다.
또 다른 단점은 명령줄에서 처리하는 파일 수입니다. 일부 장소에서 사용하고 있으므로 *
1M 또는 5M 파일 이름으로 확장되며 이는 위험한 것으로 간주될 수 있습니다. 파일 수를 늘리면 명령줄 매개변수에 할당된 공간을 초과할 위험이 있다는 점에 유의하세요.
명령줄 길이에 대한 자세한 내용은 다음 링크를 참조하세요.
- 실제 최대 매개변수 목록 길이를 찾는 표준적인 방법은 무엇입니까?
- 시스템 구성 "변수" ARG_MAX의 Linux 구현이 다른 시스템 변수와 다릅니까? POSIX와 호환됩니까?
- 명령의 단일 매개변수의 최대 크기를 정의하는 것은 무엇입니까?
결론적으로
상상할 수 있듯이 1M 또는 5M 파일로 이러한 문제를 해결하는 것은 거의 우스꽝스러워 보입니다. 나도 동의해야 해요. 하지만 CLI 도구를 올바른 방식으로 활용하면 뛰어난 성능을 얻을 수 있다는 점을 보여주기 때문에 여전히 흥미로운 실험입니다.
hashy.bash 코드
누구든지 코드에 관심이 있다면:
$ cat hashy.bash
#!/bin/bash
echo ""
echo "make data"
echo "---------"
rm -f afile
{ time for i in {0..1000000};do echo "{\"name\": \"John${i}\", \"surname\": \"Gates\", \"country\": \"Germany\", \"age\": \"20\", \"height\": \"180\"}">> afile ;done ;} \
|& awk '/real|user|sys/ {print $1": "$2"\t"}' | tr -d '\n'
echo ""
echo ""
rm -fr datadir && mkdir datadir && cd datadir
echo "split data"
echo "----------"
{ time split -l 1 ../afile ;} |& awk '/real|user|sys/ {print $1": "$2"\t"}' | tr -d '\n'
echo ""
echo ""
echo "hash data"
echo "---------"
{ time xxhsum -H0 * > ../nfile 2>&1 ;} |& awk '/real|user|sys/ {print $1": "$2"\t"}' | tr -d '\n'
cd - > /dev/null 2>&1
echo ""
echo ""
인용하다
답변4
먼저 데이터를 SQLite 데이터베이스로 가져옵니다. 여기서는 Miller( mlr
)를 사용하여 제공한 JSONL 데이터를 CSV로 변환한 다음 data
새 데이터베이스의 테이블로 읽습니다.
mlr --l2c cat file.json | sqlite3 database.db '.import --csv /dev/stdin data'
완료되면 UPDATE 문을 사용하여 제안된 구성표를 사용하여 식별자를 생성할 수 있습니다.
sqlite> .mode box
sqlite> SELECT * FROM data;
┌───────┬─────────┬─────────┬─────┬────────┐
│ name │ surname │ country │ age │ height │
├───────┼─────────┼─────────┼─────┼────────┤
│ John │ Gates │ Germany │ 20 │ 180 │
│ John1 │ Gates │ Germany │ 20 │ 180 │
│ John2 │ Gates │ Germany │ 20 │ 180 │
│ John3 │ Gates │ Germany │ 20 │ 180 │
└───────┴─────────┴─────────┴─────┴────────┘
sqlite> ALTER TABLE data ADD COLUMN id TEXT;
sqlite> UPDATE data SET id = concat(name,surname,country,age,height);
sqlite> SELECT * FROM data;
┌───────┬─────────┬─────────┬─────┬────────┬────────────────────────┐
│ name │ surname │ country │ age │ height │ id │
├───────┼─────────┼─────────┼─────┼────────┼────────────────────────┤
│ John │ Gates │ Germany │ 20 │ 180 │ JohnGatesGermany20180 │
│ John1 │ Gates │ Germany │ 20 │ 180 │ John1GatesGermany20180 │
│ John2 │ Gates │ Germany │ 20 │ 180 │ John2GatesGermany20180 │
│ John3 │ Gates │ Germany │ 20 │ 180 │ John3GatesGermany20180 │
└───────┴─────────┴─────────┴─────┴────────┴────────────────────────┘
id
분명히 Miller에게 즉석에서 칼럼을 작성 하도록 요청할 수 있습니다 . 다음은 공백으로 구분된 각 레코드 필드의 MD5 해시를 사용합니다.
mlr --l2c put '$id = md5(joinv($*," "))' file | sqlite3 database.db '.import --csv /dev/stdin data'
sqlite> .mode box
sqlite> SELECT * FROM data;
┌───────┬─────────┬─────────┬─────┬────────┬──────────────────────────────────┐
│ name │ surname │ country │ age │ height │ id │
├───────┼─────────┼─────────┼─────┼────────┼──────────────────────────────────┤
│ John │ Gates │ Germany │ 20 │ 180 │ 150c35e2efb7093e1c30a46a0226f82c │
│ John1 │ Gates │ Germany │ 20 │ 180 │ c58a8be627dc1d6c9da36dd6de9fa62d │
│ John2 │ Gates │ Germany │ 20 │ 180 │ e41b62a821f51c13eea2191ebcbb5837 │
│ John3 │ Gates │ Germany │ 20 │ 180 │ 8e1012a599356fee66727107b750ba1a │
└───────┴─────────┴─────────┴─────┴────────┴──────────────────────────────────┘
최근(2020년) MacBook Air(M1)에서 이를 테스트하는 데 Miller를 사용하여 MD5 해시를 계산하고 500만 개의 레코드를 데이터베이스로 가져오는 데 약 42초가 걸렸습니다.