레이블이 hadoop인 게시물을 표시합니다. 모든 게시물 표시
레이블이 hadoop인 게시물을 표시합니다. 모든 게시물 표시

2021년 8월 1일 일요일

2) 빅데이터 플랫폼 아키텍처에 대하여.. 하둡을 알아보자

전 포스팅(1편)에서 BDP를 큰 관점에서 훑어보았는데 개인적인 사정으로 2편이 조금 늦어졌다. 그래도 시작한 김에 꾸준히 연재해보고자 한다.


데이터가 부각되면서 저장소의 개념과 종류도 많아지고 여러가지를 적재적소에 조합하여 사용하는 시대가 왔다. 기존의 서비스 트랜잭션들은 RDB로 했어야했지만 이는 성능보다는 안정성에 우선이 된다. 방대한 양의 데이터를 pk가 존재하는 RDB에 운영이 되고 있는 데이터에 넣자니 CPU나 memory가 감당할 수 없고 결국에는 HDFS라는 파일시스템으로 저장하는 하둡이 확고히 정착되었다.


하둡은 HDFS로 저장하며 말 그대로 파일시스템으로 저장한다. 단일 머신이 아닌 n개의 머신을 묶어서 분산하여 데이터가 저장된다.

먼저 위키독스에 올라온 하둡 아키텍처를 보자.


분산 저장소라는 것은 서버(머신)이 n개가 묶여서 동작한다는 것이다.

이 하둡에 여러가지 에드온들이 달리긴 하지만 가장 필수적인 것부터 하나씩 알아보자.


하둡은 네임노드와 데이터노드로 구성되어있다.

먼저 데이터 노드는 실제로 데이터가 저장되는 서버다. 데이터 노드가 10대가 실행중이다는 말은 10개의 서버에서 각각 10개의 데이터 노드가 올라와있다는 것을 의미한다. "A라는 파일을 하둡에 저장해라!"라는 명령이 떨어지면 block size 설정에 의해 n개의 조각으로 쪼개지고 이 n개의 파일은 각각 replication 설정(만약 replica가 3이라면)에 의해 3개의 복제본(원본포함 3개)이 random으로 각 데이터 노드에 저장이 된다.

과거에 데이터 사이즈(파일 사이즈)가 작았을 때는 block size가 32~64MB 정도 수준이었으나 지금은 128~256MB가 default로 많이 사용된다. 더 잘게 쪼개는 것이 좋을지, 큰 덩어리로 쪼개는 것이 좋을지는 서비스의 상황에 따라 다르겠지만 아무래도 큰 덩어리로 쪼개도 충분하다면 네트워크 IO라던지, 어느 노드에 데이터가 저장되어있는지(meta information)의 정보가 가벼워질 수 있다.


결론은 데이터노드는 데이터가 저장되는 곳이다. 그럼 네임노드는 무얼 하는 친구들일까? 네임노드는 실제로 데이터가 저장되는 서버가 아니다. 어느 데이터노드에 어떤 데이터가 저장되어있는지, 데이터 노드의 health check라던지 이런 meta성 정보를 스스로 저장하고 관리한다. 


그럼 데이터노드가 죽을 경우는 어떻게 될까? 

만약 1개의 서버가 down되었을 경우 다른 서버에 해당 데이터의 복제본이 존재하기 때문에 문제가 되지 않는다. 물론 재수없게 해당 복제본이 한 노드에 몰빵이 되어있을 경우는 문제가 될 수 있지만 보통은 최소 수십대 이상을 묶어놓기 때문에 동일한 복제본이 한 서버에 들어갈 확률은 그리 높지않다.


만약 네임노드가 죽는다면 어떻게 될까?

사람으로 치면 뇌(메타정보를 들고있는)가 꺼지는 것이다. 당연히 위험하다.

하둡을 설치할 때 네임노드를 어떻게 할 것인지를 선택해야한다. 

1. NameNode+Secondary NameNode 형태로 설치

이 경우는 네임노드가 2대라고해서 한대가 꺼졌을 때 복구해주는 구조가 아니다. Secondary NameNode는 check point 역할을 할 뿐 Name node를 대체하지 못한다. DB에서 처럼 단지 edit log 등을 들고있을 뿐이다. 즉 장애상황에서는 위험하기 때문에 별도로 이러한 정보를 들고 있는 네임스페이스 이미지를 백업을 해둔다면 어떻게든 복구가 가능은 하겠지만 운영상황에서 크리티컬 장애를 만날 경우 복구를 못할 가능성을 항상 염두해야한다.


2. NameNode HA(High Availability) 구성

이 경우에는 세컨더리 네임노드 없이 NameNode를 HA구성하는 것이다. 당연히 한대는 Active, 한대는 StandBy 형태로 되어있다. 액티브가 죽으면 스탠바이가 살아날 것이고 (물론 그 시간 동안은 장애다. 어쩔 수 없는...) StandBy가 Active가 되면서 고장난 서버를 고쳐서 다시 띄우면 된다. 이를 위해 ZKFC(ZKFailoverController)라는 것도 각 네임노드에 띄워주긴 해야하지만 실행만 하면되는 간단한거라서 pass.

HA 구성 방법은 링크를 참고하자.(링크)


주키퍼(Zookeeper)

네임노드가 죽었으니 빨리 대체 네임노드를 띄워야 하거나, 혹은 왜 복제본은 3개인지(홀수) 이런 것은 주키퍼(zookeeper)라는 분산 코디네이터(프레임워크)가 도와준다. 보통 분산 환경에서는 주키퍼는 필수이다. 예를 들어서 1개의 파일을 3개로 쪼개서 저장했는데 내부적인 문제로 1개의 사본이 잘못된 정보를 들고 있다면 다수결을 통해서 "1개가 잘못되었으니 2개짜리가 진짜 정보다" 라는 것을 판단할 수 있고 주키퍼와 각 서버가 통신하며 "네가 보내주는 health 정보가 정상이 아니구나.. 잠깐 내려가서 쉬고 있으렴" 이런 역할을 수행할 수 있다.


주키퍼의 경우 크게 만질 것도 없고 보통 3대 서버에 주키퍼를 설치해놓는다면 주키퍼를 사용하는 프레임워크들에서 이 주키퍼 클러스터를 함께 써도 안전하게 사용이 가능해보인다. 가령 하둡과 카프카, 스파크 스트리밍 등을 한대의 주키퍼 클러스터를 써도 대용량의 처리를 하지 않는다면 괜찮아보인다.)



지금까지 네임노드, 데이터노드, 주키퍼를 언급했는데 여기까지가 끝이 아니다. 단순히 데이터를 저장하는 것이 끝이 아니기 때문이다. 컴퓨터(서버)는 각각이 cpu 코어와 memory, disk를 갖고있기 때문에 이 자원들을 사용해야한다. 이를 위해 알아야할 것이 노드매니저, 리소스매니저다.


네임노드는 그래도 뇌역할을 하니까 연산작업을 안시키더라도 데이터노드들의 리소스들은 가만히 두면 너무 아깝다. 심지어 데이터를 들고 있으니 연산을 하기 최적의 서버들이다. 이 리소스들을 사용하기 위해서 각 데이터노드들에서 노드매니저에서 설정하고 띄워줘야한다.

정리하면 데이터노드들에서는 데이터노드와 노드매니저가 반드시 떠있어야한다고 보면되고 노드매니저는 향후 리소스매니저(yarn, mesos 등)에서 리소스를 사용할 수 있도록 관리자 역할을 한다.


여기서 멈출까 하다가.. 더 추가해서 과거에는 하드웨어 성능이 좋지 않았고 초창기에는 disk 연산방식을 많이 썼다. 각 데이터노드의 disk에서 데이터를 불러와서 연산하고 다시 disk에 쓰고.. 이러한 MR(mapreduce) 방식으로 처리했지만 요즘은 RAM이 너무 좋아져서 Spark(In-memory 방식)가 확고하게 자리를 잡았다. 아무리 ssd라 할지라도 memory보다 빠를 순 없고  Network I/O를 줄이는 것은 한계가 있기 때문에 Disk I/O 시간을 줄이는 것은 확실하게 성능 보장을 할 수 있으니까..


또한 데이터를 하둡에 저장할 때 text로 저장할건지, parquet, orc 등으로 저장가능하다. 이러한 부분이 참 편리하다.


예를 딱 한가지만 더 들고 이번 포스팅은 마무리..

예를 들어서 어떤 데이터에 대해 Group by 명령을 수행하면 대충 이러한 일련의 과정을 거친다. 각 데이터 노드에 존재하는 데이터들을 Disk에서 Memory로 올리고 Key 연산을 위해 Network I/O도 일어나고 그 결과를 다시 HDFS로 저장한다. 이러한 과정이 매우 크고 길 경우 Disk I/O는 꽤나 큰 비효율을 발생시키기 때문에 In-Memory 방식으로 처리하면 좋을 것이다. 또한 A노드에서 B노드에 존재하는 데이터가 필요할 경우 Network I/O가 발생할 것인데 만약 모든 노드에 특정 데이터가 필요할 경우도 있을 것이고 반복해서 이러한 suffle이 발생하는 것도 큰 비효율성을 낳는다. 따라서 최적화가 필요하다. 이러한 작업을 MR로 할건지, Spark로 처리할 건지는 다음 포스팅에서 설명해보도록 하자. Spark 위주가 될 것이며 쉽게 인메모리 연산엔진이라고 보면 된다. (요즘 MR은 거의 안쓴다. 자바로 짜는 것도 복잡하고.. 길어진다.) 


이번 시간에는 하둡에 대해서 간단하게 알아봤는데 권한이나 복제방식 등을 다 서술하기에는 너무 많아진다. 단지 리눅스 경로처럼 파일에 접근이 가능하고 사용할 수 있기 때문에 사용법은 아주 쉽고 권한의 경우는 Hue, Hive에서 막는 것이 가장 간단하고 쉬워보인다. 내가 설계한다면 하둡을 리소스 매니저 UI page말고는 열어주지 않는 것이 향후 관리에 편해지는 것 같다. (다른이에게 데이터를 오픈할 경우에는 Hue와 Hive를 통해서만 접근이 가능하도록...)


쓰다보니 너무 의식의 흐름대로 쓰고 말았다.. 다음에는 더 집중하고 정리해서 쓰는걸로..


2021년 1월 14일 목요일

HA hadoop+Spark 조합으로 namespace 사용할 때 발생할 수 있는 에러(UnknownHostException)

현재 HA hadoop + Spark + mesos + azkaban 조합으로 배치를 구성하고 있는데 서버에 직접 들어가서 shell script를 수행하면 정상적으로 동작을 한다.

scala source에서는 hadoop url을 namespace로 접근한다.

그러나 아즈카반에서 수행을 하면 에러를 뱉는다.

Exception in thread "main" java.lang.IllegalArgumentException: java.net.UnknownHostException: hadoop-cluster-bdnode
....
at org.apache.spark.deploy.SparkSubmit.main(SparkSubmit.scala)
Caused by: java.net.UnknownHostException: hadoop-cluster-bdnode
... 40 more
....
java.lang.RuntimeException: azkaban.jobExecutor.utils.process.ProcessFailureException: Process exited with code 1
...
Caused by: azkaban.jobExecutor.utils.process.ProcessFailureException: Process exited with code 1
at azkaban.jobExecutor.utils.process.AzkabanProcess.run(AzkabanProcess.java:125)
at azkaban.jobExecutor.ProcessJob.run(ProcessJob.java:304)


shell script에서는 동작할 때에는 hadoop conf(hdfs-site.xml, core-site.xml) 정보를 알고 있다. bashrc에 hadoop_conf_dir을 해놨기 때문이다. 하지만 스케쥴러 azkaban에서 수행하려고 하면 해당 구성을 다시 shell script에 export 해주거나 spark-submit file안에서 알려줘야 한다. 배치를 수행하는 command shell을 모두 바꿀 수가 없어서 그냥 후자로 적용했다.
export HADOOP_HOME=/data01/sw/hadoop
export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop


이제 잘 동작한다.




2020년 12월 30일 수요일

hadoop 3.x ec policy(erasure coding) vs replication 3

하둡3에서 ec policy라는 기능이 생겼다. 말로만 들었던 기능인데 이참에 테스트할 겸 적용해보기로 한다.

하둡2에서 하둡3으로 데이터 마이그레이션을 할 때 공간이슈 때문에 3복제 대신에 ec policy 적용을 고려해보았다.


적용 자체는 path별로 구분해서 적용할 수 있게 되어있었고 적용하면 replication이 아닌 ec policy에 의해 1 replication + @로 저장&관리되는 형태이다.


각종 정보(ex 패리티비트)로 인해 블록수를 더 많이 자치할 수 있어 namenode 입장에서 약간의 overhead가 있다고는 하지만 case by case인것 같고 내 기준에서는 크게 무리될 정도는 아니었기에 바로 적용을 해보았다. 

3 replica가 1 replica로 즉시 바뀌지는 않고 신규로 write되는 것부터 바뀌는 것 같다. 

즉 해당 옵션은 hdfs path별로 on/off 시켜서 동작할 수 있다.


기존 3 replica의 경우 3배의 용량을 차지하는데 ec policy를 사용하면 1.3배 정도로도 데이터를 저장할 수 있다고 한다. 


아무튼 적용하고 장애 테스트를 해보기 위해 해당 block을 저장하고 있는 datanode들을 down시켰더니 그 즉시 해당 data를 읽을 수 없는 문제가 존재했다.

3 replica의 경우 데이터노드가 몇 대 내려가더라도 1대만 살아있으면 즉시 제대로 동작하는데 ec policy는 그러지 못하는 것 같다. 

(또한 -replication 옵션과 -policy옵션을 동시에 사용이 불가능하다.)


현재까지 테스트한 결론으로는 일회성으로 read하는 단순 저장용이나 또는 복구가능한 집계성 데이터는 ec policy를 적용하고 원천 데이터처럼 자주 read하는 데이터들은 그냥 3 replication이 좋을 것 같다.

어쨌든 배치를 돌려야하는데 즉시 read하지 못하기 때문에 적용이 불가능해보인다.


시간관계상 제대로 알아보지 못했지만 분명한 것은 일괄적용하기에는 무리가 있다.

참조 : https://hadoop.apache.org/docs/r3.1.1/hadoop-project-dist/hadoop-hdfs/HDFSErasureCoding.html

2020년 12월 15일 화요일

hadoop name node heap size

hadoop cluster에서 특히 spark cluster를 hadoop data node에 구성을 하기 때문에 name node heap size는 신경 안써도 된다고 생각할 수도 있는데 hdfs가 많아질 수록 신경을 써야한다.

하둡 네임노드의 경우 memory는 data node의 file system에 영향을 받는다. 따라서 하둡 과거버전처럼 block size를 64MB로 할 때보다 128MB로 하면 네임노드의 heap이 확 줄어드는 것을 관찰 할 수 있을 것이다.

아무래도 데이터를 큰 바구니에 담으면 기억해야하는 바구니 갯수가 줄어들테니 당연한 일이다.


아래는 기록용으로 남기려고 한다.

hadoop v2.7.5

하둡 2.7.5버전에서의 block size 256MB이고 모든 데이터가 3 replica를 갖는다. 

file과 directories 갯수에 따른 메모리 사용량이다.


27270751 files and directories, 22352862 blocks = 49623613 total filesystem object(s).

Heap Memory used 27.18 GB of 39.51 GB Heap Memory. Max Heap Memory is 56.89 GB.

Non Heap Memory used 104.05 MB of 106.5 MB Commited Non Heap Memory. Max Non Heap Memory is -1 B.



hadoop v3.1.4

현재 하둡 3.1.4에서 block size도 동일하게 256MB를 잡았고 동일하게 3 replica이다. 100% 마이그레이션을 하지는 않았는데 replica 쪽에 개선이 있어서 메모리를 덜 사용할 것이다.


7,743 files and directories, 11,767 blocks (11,767 replicated blocks, 0 erasure coded block groups) = 19,510 total filesystem object(s).

Heap Memory used 1.99 GB of 5.08 GB Heap Memory. Max Heap Memory is 56.89 GB.

Non Heap Memory used 80.31 MB of 83.31 MB Commited Non Heap Memory. Max Non Heap Memory is <unbounded>.


데이터, 배치 마이그레이션 끝내고 꾸준히 모니터링을 하면서 얼마나 사용하는지 봐야겠다.



2020년 11월 30일 월요일

Apache Hadoop 3.1.4 HA(High Availability) Install Guide with Cloudera guide

과거에 개인적으로 하둡 2~3 버전을 완전분산 모드로 설치를 해보면서 확실히 크게 달라진 점은 없었다. 당시에는 HA구성을 할 필요가 없었는데 이번에 HA 구성을 해보려고 한다.
하지만 프로덕션 환경에서 설치하기위해서 최적의 구성을 생각했어야했는데 개인적으로 생각한 내용과 클라우데라에서 제안한 아키텍처를 보더라도 크게 차이는 없었다.


출처 : 클라우데라 제안 구성

여기서 상황에 맞게 필요한 것만 구성을 하고 결론은 마스터 3대, 배치서버1대, 데이터노드 N대로 구성하고 다음과 같은 룰을 따르고자 한다.

리소스 매니저는 Yarn을 한번 올려보고.. 실제로 사용은 Mesos로 컨트롤 할 것이다. Mesos의 장점이 하둡 클러스터의 구간을 나눠놓고 1번 메소스, 2번 메소스, 3번 메소스 클러스터로 분리운영이 가능한데 이는 서버가 장애가 날때, 혹은 배치들을 분리했을 때 운영상 장점이 있기 때문이다.

대략적인 구조는 이런식으로 가져간다.


마스터1 : 주키퍼 저널노드 네임노드(HA) ZKFC 리소스매니저(HA) 메소스마스터

마스터2 : 주키퍼 저널노드 네임노드(HA) ZKFC 리소스매니저(HA) 메소스마스터

마스터3 : 주키퍼 저널노드 잡히스토리서버 스파크히스토리서버 메소스마스터

슬레이브서버들 : 데이터노드 메소스슬레이브


사실 yarn을 쓸 일은 거의 없긴한데 일단 sqoop이나 다른 프레임워크들과 etl을 하면서 필요할 일이 있으니 넣어두고 ha를 굳이 안해도되는데 간단하니까 해보자. 왠만하면 최소화로 가져가야 서버를 리부팅할때 올려야할 서비스를 빼먹지 않기 때문에 간소화하는 것을 좋아한다.

설치는 자바설치, /etc/hosts, 인증키 배포 등 기본적으로 세팅해놓아야 할 설정들은 모두 되었다고 가정을 한다.


먼저 마스터 3대에 주키퍼를 설치한다. (3.6.2 설치함.)
zoo_sample.cfg를 복사해서 zoo.cfg를 만들고
dataDir=path
server.1=master1:2888:3888
server.2=master2:2888:3888
server.3=master3:2888:3888
적당한 곳으로 세팅한다.
다 되었으면 주키퍼를 띄운다. (./zkServer.sh start)

다음은 바로 하둡이다.
압축을 풀고 
tar -zxvf hadoop-3.1.4.tar.gz
링크를 걸고
ln -s hadoop-3.1.4 hadoop
필요한 환경세팅을 세팅한다.



기존에는 slaves, masters로 나뉘어져있던 파일이 /etc/workers로 통합이 되었고 여기에 datanode가 올라갈 서버들을 적는다.

각각의 서버는 data01, appData01~04로 구성이 되어있고 하둡은 data01에 두고 log나 data를 쓰는 부분은 appData01로 구성한다. 하둡 같은 경우는 os부분만 레이드1(미러링) 하고 나머지는 솔직히 안해도 된다고 본다. 어차피 3복제이고 하둡을 다시 올리는데 큰 리소스가 안들기 때문이다.

그럼 설정파일을 작성하자.
하둡 설정파일들이 default로라도 되어있으면 좋은데 그냥 깡통이라서 사실 하나씩 쓰기가 너무 괴롭다..

hadoop-env.sh에 몇몇 필요한 경로 세팅을 하고 저장한다.
주석을 풀고 커스터마이징해서 작성하면 된다.
HADOOP_CONF_DIR
HADOOP_HEAPSIZE_MAX
HADOOP_WORKERS
HADOOP_LOG_DIR
HADOOP_PID_DIR
HADOOP_SECURE_PID_DIR


다음은 hdfs-site.xml이다.
<configuration>
        <!-- for NameNode -->
        <property>
                <name>dfs.namenode.name.dir</name>
                <value>/appData01/hadoop/hdfs/name,/appData02/hadoop/hdfs/name,/appData03/hadoop/hdfs/name,/appData04/hadoop/hdfs/name</value>
        </property>

        <!-- for DataNode -->
        <property>
                <name>dfs.datanode.data.dir</name>
                <value>/appData01/hadoop/hdfs/data,/appData02/hadoop/hdfs/data,/appData03/hadoop/hdfs/data,/appData04/hadoop/hdfs/data,/appData05/hadoop/hdfs/data</value>
        </property>

        <!-- HA -->
        <property>
                <name>dfs.nameservices</name>
                <value>hadoop-cluster-bdnode</value>
        </property>
        <property>
                <name>dfs.ha.namenodes.hadoop-cluster-bdnode</name>
                <value>namenode1,namenode2</value>
        </property>
        <property>
                <name>dfs.namenode.rpc-address.hadoop-cluster-bdnode.namenode1</name>
        <value>master1:8020</value>
        </property>
            <property>
                    <name>dfs.namenode.rpc-address.hadoop-cluster-bdnode.namenode2</name>
                        <value>master2:8020</value>
            </property>
        <property>
                <name>dfs.namenode.http-address.hadoop-cluster-bdnode.namenode1</name>
                <value>master1:50070</value>
        </property>
        <property>
                <name>dfs.namenode.http-address.hadoop-cluster-bdnode.namenode2</name>
                <value>master2:50070</value>
        </property>
        <property>
                <name>dfs.namenode.shared.edits.dir</name>
                <value>qjournal://master1:8485;master2:8485;master3:8485/hadoop-cluster-bdnode</value>
        </property>
        <property>
                <name>dfs.client.failover.proxy.provider.hadoop-cluster-bdnode</name>
                <value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>
        </property>
        <property>
                <name>dfs.ha.fencing.methods</name>
                <value>sshfence</value>
        </property>
        <property>
                <name>dfs.ha.fencing.ssh.private-key-files</name>
                <value>/home/scom/.ssh/id_rsa</value>
        </property>
        <property>
                <name>dfs.journalnode.edits.dir</name>
                <value>/appData01/hadoop/hdfs/journalnode</value>
        </property>
        <property>
                <name>dfs.ha.automatic-failover.enabled</name>
                <value>true</value>
        </property>
        <property>
                <name>dfs.blocksize</name>
                <value>268435456</value>
        </property>
        <property>
                <name>dfs.namenode.handler.count</name>
                <value>100</value>
        </property>
        <property>
                <name>dfs.hosts</name>
                <value>/data01/sw/hadoop/etc/hadoop/workers</value>
        </property>
        <property>
                <name>dfs.webhdfs.enabled</name>
                <value>true</value>
        </property>
        <property>
                <name>dfs.namenode.datanode.registration.ip-hostname-check</name>
                <value>false</value>
        </property>

        <property>
                <name>dfs.datanode.du.reserved</name>
                <!-- cluseter variant 90G -->
                <value>96636764160</value>
                <description>Reserved space in bytes per volume. Always leave this much space free for non dfs use.</description>
        </property>
</configuration>


다음은 core-site.xml이다.
<configuration>
        <property>
                <name>fs.defaultFS</name>
                <value>hdfs://hadoop-cluster-bdnode</value>
        </property>
        <property>
                <name>ha.zookeeper.quorum</name>
                <value>master1:2181,master2:2181,master3:2181</value>
        </property>
        <!-- trash -->
        <property>
                <name>fs.trash.interval</name>
                <value>14400</value>
                <description>Number of minutes after which the checkpoint gets deleted. If zero, the trash feature is disabled. </description>
        </property>
        <!-- for HUE-->
        <property>
                <name>hadoop.proxyuser.hue.hosts</name>
                <value>*</value>
        </property>
        <property>
                <name>hadoop.proxyuser.hue.groups</name>
                <value>*</value>
        </property>
</configuration>


다음은 yarn-site.xml 이다.
<configuration>

<!-- Site specific YARN configuration properties -->
        <property>
                <name>yarn.nodemanager.aux-services</name>
                <value>mapreduce_shuffle</value>
        </property>
        <property>
                <name>yarn.nodemanager.aux-services.mapreduce.shuffle.class</name>
                <value>org.apache.hadoop.mapred.ShuffleHandler</value>
        </property>
        <property>
                <name>yarn.nodemanager.local-dirs</name>
                <value>/appData01/hadoop/yarn/nm-local-dir,/appData02/hadoop/yarn/nm-local-dir,/appData03/hadoop/yarn/nm-local-dir,/appData04/hadoop/yarn/nm-local-dir</value>
        </property>
        <property>
                <name>yarn.resourcemanager.address.rm1</name>
                <value>master1:8032</value>
        </property>
        <property>
                <name>yarn.resourcemanager.webapp.address.rm1</name>
                <value>master1:8088</value>
        </property>
        <property>
                <name>yarn.resourcemanager.scheduler.address.rm1</name>
                <value>master1:8030</value>
        </property>
        <property>
                <name>yarn.resourcemanager.resource-tracker.address.rm1</name>
                <value>master1:8031</value>
        </property>
        <property>
                <name>yarn.resourcemanager.admin.address.rm1</name>
                <value>master1:8041</value>
        </property>

        <property>
                <name>yarn.resourcemanager.address.rm2</name>
                <value>master2:8032</value>
        </property>
        <property>
                <name>yarn.resourcemanager.webapp.address.rm2</name>
                <value>master2:8088</value>
        </property>
        <property>
                <name>yarn.resourcemanager.scheduler.address.rm2</name>
                <value>master2:8030</value>
        </property>
        <property>
                <name>yarn.resourcemanager.resource-tracker.address.rm2</name>
                <value>master2:8031</value>
        </property>
        <property>
                <name>yarn.resourcemanager.admin.address.rm2</name>
                <value>master2:8041</value>
        </property>

        <property>
                <name>yarn.resourcemanager.fs.state-store.uri</name>
                <value>/appData01/hadoop/yarn/system/rmstore,/appData02/hadoop/yarn/system/rmstore,/appData03/hadoop/yarn/system/rmstore,/appData04/hadoop/yarn/system/rmstore</value>
        </property>
        <property>
                <name>yarn.resourcemanager.hostname.rm1</name>
                <value>master1</value>
        </property>
        <property>
                <name>yarn.resourcemanager.hostname.rm2</name>
                <value>master2</value>
        </property>

        <!-- configure yarn -->
        <property>
                <name>yarn.nodemanager.resource.cpu-vcores</name>
                <value>10</value>
        </property>
        <property>
                <name>yarn.nodemanager.resource.memory-mb</name>
                <value>40960</value>
        </property>
        <property>
                <name>yarn.scheduler.minimum-allocation-mb</name>
                <value>1024</value>
        </property>
        <property>
                <name>yarn.scheduler.maximum-allocation-mb</name>
                <value>24576</value>
        </property>

        <!-- ResourceManager 시작시 state 복구여부 --> 
        <property> 
                 <name>yarn.resourcemanager.recovery.enabled</name> 
                 <value>true</value> 
        </property> 
        <!-- persistent store로 사용할 class --> 
        <property> 
                 <name>yarn.resourcemanager.store.class</name> 
                 <value>org.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStore</value> 
        </property> <!-- Zookeeper 서버 리스트 --> 
        <property> 
                 <name>yarn.resourcemanager.zk-address</name> 
                 <value>master1:2181,master2:2181,master3:2181</value> 
        </property>
        <property>
                <name>yarn.resourcemanager.ha.enabled</name>
                <value>true</value>
        </property>

        <!-- ResourceManager가 leader election에 참가할 cluster 이름 지정 -->
        <property>
                <name>yarn.resourcemanager.cluster-id</name>
                <value>rm-cluster</value>
        </property>
        <!-- cluster에서 HA를 구성할 ResourceManager id 지정 -->
        <property>
                <name>yarn.resourcemanager.ha.rm-ids</name>
                <value>rm1,rm2</value>
        </property>
</configuration>


다음은 mapred-site.xml이다
<configuration>
        <property>
                <name>mapreduce.framework.name</name>
                <value>yarn</value>
        </property>

        <!-- mapreduce2 memory setting -->
        <property>
                <name>yarn.app.mapreduce.am.resource.mb</name>
                <value>1536</value>
        </property>
        <property>
                <name>mapreduce.map.memory.mb</name>
                <value>4096</value>
        </property>
        <property>
                <name>mapreduce.reduce.memory.mb</name>
                <value>8192</value>
        </property>

        <!-- mapreduce java memory setting -->
        <property>
                <name>mapreduce.map.java.opts.max.heap</name>
                <value>3276</value>
        </property>
        <property>
                <name>mapreduce.reduce.java.opts.max.heap</name>
                <value>6553</value>
        </property>

                <property>
                <name>yarn.app.mapreduce.am.resource.mb</name>
                <value>2048</value>
        </property>

                <property>
                                 <name>yarn.app.mapreduce.am.env</name>
                                 <value>HADOOP_MAPRED_HOME=/data01/sw/hadoop</value>
                </property>
                <property>
                                <name>mapreduce.map.env</name>
                            <value>HADOOP_MAPRED_HOME=/data01/sw/hadoop</value>
                </property>
                <property>
                                <name>mapreduce.reduce.env</name>
                                <value>HADOOP_MAPRED_HOME=/data01/sw/hadoop</value>
                </property>

</configuration>


여기까지 하면 하둡 파일은 다 작성이 되었고 이 디렉토리를 마스터2~3, 데이터노드들에 복사를 하자.

scp -r hadoop-3.1.4 ip:/data01/sw/ 


네임노드 HA는 주키퍼로 한 것인데 주키퍼는 가벼우면서도 참으로 편리하다.
zkfc 실행 전 포맷 한번 해주고
hdfs zkfc -formatZK

마스터 1~3번기에서 저널노드 실행
현재 : hdfs --daemon start journalnode 
과거 : hadoop-daemon.sh start journalnode

마스터 1번기에서 네임노드 초기화
hdfs namenode -format

마스터 1번기에서 네임노드 실행
현재 : hdfs --daemon start namenode 
과거 : hadoop-daemon.sh start namenode


마스터 1번기에서 zkfc 실행
현재 : hdfs --daemon start zkfc 
과거 : hadoop-daemon.sh start zkfc

이제 마스터 1번기가 active 상태가 되었다.
이제 마스터 2번기를 standby로 올리자.

마스터 2번기에서 스탠바이상태로 세팅
hdfs namenode -bootstrapStanby

마스터 2번기에서 스탠바이 네임노드 실행
hdfs --daemon start namenode 

마스터 2번기에서 zkfc 실행
hdfs --daemon start zkfc

이제 리소스매니저(yarn)을 올린다.
마스터 1번기에서 yarn --daemon start resourcemanager 실행.

마스터 3번기에서 jobhistory server 실행
현재 : mapred --daemon start historyserver
과거 : sbin/mr-jobhistory-daemon.sh start historyserver

마스터 2번기에서도 yarn을 올린다.
yarn --daemon start resourcemanager

마지막으로 데이터노드들에서 노드매니저를 올린다.
yarn --daemon start nodemanager

그림 작업은 끝이났다.
마스터1:50070 혹은 마스터2:50070에 접근을 해보면 active/standby를 확인할 수 있다.
테스트를 해보기 위해서 active namenode를 kill해보면 아직 사용을 안해서 거의 즉시 failover가 되기는 한다. 이제 배치에서도 네임스페이스를 활용하도록 하자!




네임노드 ha와 리소스매니저 ha가 끝이났다.
하둡이 버전이 올라가면서 가장 크게 바뀐 것은 아무래도 명령어가 바뀌고 workers 파일로 바뀌었다는것, 포트가 바뀌었다는 것, 내부적으로는 압축 효율이 바뀌었다는 점 정도인 것 같다. 그리고 ui가 많이 바뀌었다. 적응이 안된다.

만약에 설치를 하다가 제대로 안되면(특히 포맷) 해당 개인이 사용하려고 생성했던 디렉토리를 삭제하고 다시 해보도록 하자. 그 안에 파일들이 이미 만들어져서 사용되고 있기 때문에 안될 가능성이 높다.

아래 명령어로 네임노드 Active/Standby를 확인 가능하다. 한번만 쳐도 알아낼 수 있는 명령어가 있었으면 좋겠다.
hdfs haadmin -getServiceState namenode1
hdfs haadmin -getServiceState namenode2

리소스매니저 Active/Standby 상태 확인은 다음과 같다.
yarn rmadmin -getServiceState rm1
yarn rmadmin -getServiceState rm2

개인적으로 네임노드2대를 VIP로 사용하여 인터페이스를 해야하는 경우가 아니라면 딱히 VIP는 필요없어보이고 어차피 네임스페이스로 접근하여 해결하도록 하자.

2020년 10월 26일 월요일

Source and target differ in block-size. Use -pb to preserve block-sizes during copy

hadoop distcp -update $source $target

하둡 클러스터 2대를 운영하고 있는 상황에서 매일 A클러스터에서 B클러스터로 데이터를 백업을 한다. 현재 하둡은 source target 모두 2.7.1 버전이다.


이때 distcp 명령어를 사용하고 이는 mapreduce로 수행이 되기 때문에 성능이 좋다.
(distcp는 하둡 클러스터간 파일을 복사할 때 유용하다.)


정상적으로 수행되면 아래와 같은 로그를 볼 수 있는데..

INFO tools.DistCp: DistCp job-id: job_1587237267517_10878
INFO mapreduce.Job: Running job: job_1587237267517_10878
INFO mapreduce.Job: Job job_1587237267517_10878 running in uber mode : false
INFO mapreduce.Job:  map 0% reduce 0%
INFO mapreduce.Job:  map 55% reduce 0%
INFO mapreduce.Job:  map 82% reduce 0%
INFO mapreduce.Job:  map 91% reduce 0%
INFO mapreduce.Job:  map 95% reduce 0%
INFO mapreduce.Job:  map 100% reduce 0%
INFO mapreduce.Job: Job job_1587237267517_10878 completed successfully
INFO mapreduce.Job: Counters: 33

갑자기 실패가 났길래 타겟쪽에 용량이 부족한가? 라는 의심이 들었고 로그를 봤다.

HDFS-DISTCP ERROR - Caused by: java.io.IOException: Couldn't run retriable-command: Copying $source to $target
HDFS-DISTCP ERROR - at org.apache.hadoop.tools.util.RetriableCommand.execute(RetriableCommand.java:101)
HDFS-DISTCP ERROR - at org.apache.hadoop.tools.mapred.CopyMapper.copyFileWithRetry(CopyMapper.java:281)

Caused by: java.io.IOException: Check-sum mismatch between $source and $target. Source and target differ in block-size. Use -pb to preserve block-sizes during copy. Alternatively, skip checksum-checks altogether, using -skipCrc. (NOTE: By skipping checksums, one runs the risk of masking data-corruption during file-transfer.)


Check-sum이 mismatch했고 소스와 타겟간에 block-size가 다르다고 한다.
해결책은 그냥 무시하는 옵션을 주면 된다. -skipcrccheck

default block size가 256MB인데 보니까 source쪽이 128MB로 생성되었더라.
그런데 file size는 128이 넘는다..


무슨현상인지 이해는 잘 안가지만 어쨌든 단순히 백업용도이고 파일 복사가 되면 타겟쪽에서는 다시 256MB로 blocksize가 맞춰지는 것으로 확인했고 이대로 넘어가려고 한다.

정리하면 큰 문제가 없으면
hadoop distcp -update -skipcrccheck $source $target 으로 하자!









2020년 2월 2일 일요일

하둡 - 오류가 발생한 블록 처리하기 (Status : Corrupt)

간혹 장비 노후라던지의 문제로 인해 하둡 블록이 문제가 되는 경우가 있다.
블록에 오류가 발생할 경우 Corrupt라는 메시지가 뜨게 되는데 데이터의 원본이 존재하거나 다른 방법으로 복구가 가능하다면 문제가 되지 않는다.

하지만 어떠한 이유로 든 간에 replication 1로 생성된 파일이 Corrupt가 발생한다면 해당 파일은 더이상 사용이 불가능하기 때문에 지워줘야한다. (과거 Polybase로 HDFS에 내리는 경우 replication 1로 떨궈졌다.)

다음 명령어를 통해 하둡 파일 시스템 상태를 체크한다.
hadoop fsck /


Status : CORRUPT가 확인되고 블록 1개가 문제가 되었다.
(replication이 1이고 테스트 파일이기 때문에 더 이상 필요가 없어서 지우기로 한다.)

hadoop fsck -delete 혹은 -move를 통해 해당 파일을 정리한다.

-move를 하면 /lost+found로 파일이 이동하는데 어차피 필요가 없으니 -delete를 하도록 한다.

hadoop fsck -delete


완료되면 Status가 Healthy로 나와야하는 것 같은데 그게 아닌가.. 뭐 어찌됐든 -delete를 하고 node overview에서 에러가 사라졌고.. 다시 hadoop fsck / 를 날려보았다.


하둡 상태가 정상(HEALTHY)로 돌아왔다.

2019년 11월 26일 화요일

하둡 데이터노드 추가/리밸런싱 하기 (Adding and Rebalancing Hadoop Datanodes)

하둡에 데이터 노드를 추가하는 경우는 공간이 모자라거나 성능향상이 필요할 때이다. 공간이 부족할 경우 어쩔 수 없이 노드를 추가해야하지만(아니면 과거 데이터를 지우거나) 그 외에 성능 확장(병렬처리)을 위해서라면 10%의 성능 향상을 위해 기존 노드 갯수의 10%만큼은 추가해주어야 한다고 알려져있다.



하둡에 신규 노드를 추가하고 리밸런싱을 통해 재분산 작업을 해보도록 하자.

작업순서는 크게 다음과 같다.

1. 신규 데이터 노드들에 자바, 하둡 설치
2. ssh 통신을 위한 인증키복사
3. 마스터 노드에서 slave를 인식할 수 있도록 slaves 파일에 slave host 추가
(하둡 버전에 따라서 masters, slaves 혹은 workers로 존재할 수 있다.)
4. 마스터 노드에서 BalancerBandwidth 변경
5. 마스터 노드에서 데이터 노드 rebalance 수행
0. 예상치 못한 상황이 생길 경우 먼저 처리.


각 노드에 기존 노드들과 동일한 자바를(zulu jdk) 설치하자.
기존에 갖고 있던 자바 설치파일을 scp로 옮긴다.
scp ./zulu8.38.0.13-ca-jdk8.0.212-linux_x64.tar.gz id@a.b.c.214:test/sw/java
scp ./zulu8.38.0.13-ca-jdk8.0.212-linux_x64.tar.gz id@a.b.c.215:test/sw/java
scp ./zulu8.38.0.13-ca-jdk8.0.212-linux_x64.tar.gz id@a.b.c.216:test/sw/java
...


압축을 푼다.
tar -zxvf zulu8.38.0.13-ca-jdk8.0.212-linux_x64.tar.gz
tar -zxvf zulu8.38.0.13-ca-jdk8.0.212-linux_x64.tar.gz
tar -zxvf zulu8.38.0.13-ca-jdk8.0.212-linux_x64.tar.gz
...

기존노드들 처럼 심볼릭 링크 jdk를 만든다.
ln -s /sw/java/zulu8.38.0.13-ca-jdk8.0.212-linux_x64 jdk

JAVA_HOME path 변경해준다.
~/.bashrc에 JAVA_HOME과 HADOOP_HOME(미리 세팅)해 놓는다.
그리고 각각 환경에 맞게 필요한 것이 있다면 적용하도록 한다.





source .bashrc로 적용해서 echo $JAVA_HOME이 zulu가 잡히면 성공이다.

자바 세팅이 끝났다. 똑같은 방법으로 하둡을 설치한다.



하둡 설치 및 세팅
각 신규 노드는 별도로 4개의 디스크를 더 마운트하여 총 test01~5까지 있다.
먼저 /test01/hadoop 디렉토리 생성하고 test02~5까지 계층구조로 디렉토리 생성한다.
mkdir /test01/hadoop
mkdir -p /test02/hadoop/hdfs/data/
mkdir -p /test03/hadoop/hdfs/data/
mkdir -p /test04/hadoop/hdfs/data/
mkdir -p /test05/hadoop/hdfs/data/



역시나 마찬가지로 기존에 쓰던 하둡을 압축해서 신규노드들로 옮겨서 설치한다.
(위에서 ~/.bashrc에서 하둡경로 세팅해놓은 곳으로 설치한다고 보면 된다.)


scp ./hadoop-2.7.1.tar.gz id@10.203.5.214:/data01/hadoop
scp ./hadoop-2.7.1.tar.gz moneymall@10.203.5.215:/data01/hadoop
scp ./hadoop-2.7.1.tar.gz moneymall@10.203.5.216:/data01/hadoop
...

압축을 푼다.
각 노드에서 tar -zxvf hadoop-2.7.1.tar.gz 

cd $HADOOP_HOME으로 가서 경로가 잘 잡혔는지 확인한다.
hdfs dfs -ls / 명령어로 설치가 잘 되었는지 확인한다.

hdfs 명령어로 하둡 깔린거 확인했고 홈 잡혀있는거 확인했고 잘된것 같다.




다음은 ssh 통신을 위한 인증키를 복사하도록 하자.
~/.ssh 만들기
cd ~
mkdir .ssh
chmod 700 .ssh
cd .ssh

기존에 뚫어놓은 키 가져오기
아무데서나 가져와도 된다. 어차피 최초 설치 시 마스터가 뿌린것이기 때문이다.
scp authorized_keys id@a.b.c.201:/~~path~~/.ssh

다음처럼 키 내용이 제대로 들어있는지 확인하고 제대로 들어있으면 정상이다.

귀찮아도 ssh로 신규 데이터노드에 한번씩 꼭 붙어보자. (비번을 물어보지 않을 때까지..)

다음은 마스터 노드의 slaves 파일에 신규노드 추가 명시하자. 현재 하둡 2.7버전이고 여기에는 masters, slaves 파일이 존재했다. slaves파일에 신규 호스트들을 추가한다.


작업이 거의 끝나간다.

마스터에서 datanode refresh를 해준다.

hdfs dfsadmin -refreshNodes


기존 slave 72대에 일단 테스트로 5대만 추가해서 리프레쉬한 결과 5대의 데드노드가 추가됨을 확인

실제로 리프레시 후 Dead Nodes에 추가되었는지 확인한다.


yarn에서 보면 바로 적용은 안되고 조금 있어야 바뀌는 것 같다.

이제 추가된 데이터노드를 하나씩 살린다.

cd $HADOOP_HOME
sbin/hadoop-daemon.sh start datanode
sbin/yarn-daemon.sh start nodemanager


전후 jps를 통해 문제 없이 잘 살아남을 확인했다.

마지막으로 기존 데이터를 분산시켜주기 위해 리밸런싱을 하도록 하자.
(며칠이 걸릴지 모른다.)

마스터노드에서 대역폭을 100메가로 변경한다.
hdfs dfsadmin -setBalancerBandwidth 104857600


리밸런싱 시작한다. 숫자 5는 각 노드간 차이가 5%이내로 함을 의미한다.
sbin/start-balancer.sh -threshold 5




실행하니까 신규 노드에 데이터가 차는것을 확인할 수 있다.



리밸런싱 로그를 tail 걸어서 확인해보면 잘 진행되고 있다. 언제 끝날려나..


추후 확인해보니.. 기존 노드의 용량이 4TB씩이고 최대 3TB씩 사용하고있었다. 이를 5%씩 bandwidth 10mb로 분산시킨 결과 대략 2TB씩 골고루 나뉘어졌으며 이런 케이스에서는 약 15일 정도 소요되었다.

2019년 9월 30일 월요일

Polybase를 활용한 ETL(mssql -> hadoop) 라이브러리 개발

Polybase를 활용한 ETL(mssql -> hadoop) 라이브러리 개발

ssis, sqoop 등 etl 툴이 많지만 db to db가 아닌 mssql에서 hadoop으로 데이터를 내린다면 polybase를 활용하여 쉽게 해결할 수 있다.

구조(준비물)은 다음과 같다.

1. EtlSqlOperator.java
- Connect mssql, Read sql query file, Run sql, Delete/Move hdfs path
2. EtlSqlOperator.sh
- Run shell script
3. DelHdfsPath.sh
- Delete path if exists
4. MoveHdfsPath.sh
- Move path if exists
5. sql query file
- CETAS query
6. sqljdbc.jar
- For db connection


사용방법
원하는 경로에 etl 디렉토리(상위)를 만들었고 etl 디렉토리 하위에는 sql, lib 디렉토리가 있다. lib에는 jdbc driver를 넣어놨고 sql 디렉토리에는 실제로 날릴 CETAS 쿼리들이 들어있다. 나머지 쉘 스크립트나 java 파일은 etl 디렉토리에 넣어놨다.

1. EtlSqlOperator.java를 자신의 상황에 맞게 수정해서 컴파일 한다.
2. 쿼리 구문을 작성해서 sql 디렉토리 하위에 넣는다.
3. EtlSqlOperator.sh에 2번에서 작성한 sql 쿼리 파일을 추가해서 실행한다.



소스, 드라이버 등 파일은 깃헙에 올려놓았다.
깃헙 주소 : https://github.com/parksuseong/ETL_Library



파일 설명 부분

EtlSqlOperator.java 

import java.sql.Connection;
import java.io.File;
import java.io.FileReader;
import java.io.FileNotFoundException;
import java.io.IOException;

import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.ResultSet;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class EtlSqlOperator {

public static void DelPath(String str){

try {
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec("path/etl/DelHdfsPath.sh " + str );

InputStream is = process.getInputStream();
InputStreamReader isr = new InputStreamReader(is);

BufferedReader br = new BufferedReader(isr);

String line;
   // this is trick
while((line = br.readLine()) != null) {

System.out.println(line);
}


} catch (IOException e1) {
System.out.println("del error..........");
// TODO Auto-generated catch block
e1.printStackTrace();
}

}


public static void MovePath(String str1, String str2){

try {
Runtime runtime = Runtime.getRuntime();
System.out.println("------------------------------------------------------------");
System.out.println("from " + str2 + " to " + str1);
System.out.println("------------------------------------------------------------");
String tmptmp = "path/etl/MoveHdfsPath.sh " + str1 + " " + str2+"*";
Process process = runtime.exec(tmptmp);

InputStream is = process.getInputStream();
InputStreamReader isr = new InputStreamReader(is);

BufferedReader br = new BufferedReader(isr);

String line;
   // this is trick
while((line = br.readLine()) != null) {

System.out.println(line);

}


} catch (IOException e1) {
System.out.println("move error.........");
// TODO Auto-generated catch block
e1.printStackTrace();
}

}


public static void main (String args []) {

String sql = "";
System.out.println("ETL Start...");
System.out.println("PARAMETER FILE NAME IS " + args[0]);

// sql file read
System.out.println("------------------------ READ SQL FILE ----------------------");
try{
File file = new File(args[0]);
FileReader filereader = new FileReader("path/etl/sql/"+file);
int singleCh = 0;
while((singleCh = filereader.read()) != -1){
sql += (char)singleCh;
}
filereader.close();
}catch (FileNotFoundException e) {
// TODO: handle exception
System.out.println(e);
}catch(IOException e){
System.out.println(e);
}


Connection conn = null;
Statement stmt = null;
ResultSet rs = null;

try {

// connect db
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
conn = DriverManager.getConnection("jdbc:sqlserver://xx.xxx.x.xx:1433","userid","password");
stmt = conn.createStatement();



int start_idx = sql.indexOf("LOCATION='/")+10;
int end_idx = sql.indexOf(",",10);
// create destination path
String path_str = sql.substring(start_idx,end_idx-1);
// create temp path
String tmp_path = "/user/BIGDATA/DW/STG"+path_str;
// delete temp path if exists
System.out.println("------------------------------ destination path ------------------------------ \n" + path_str);
System.out.println("------------------------------ tmp path ------------------------------ \n" + tmp_path);
System.out.println("--------------------------------------------------");
DelPath(tmp_path);
// create tmp_sql
String tmp_sql = sql.replace(path_str, tmp_path);
System.out.println("------------------------------ tmp sql ------------------------------\n" + tmp_sql);
// run tmp_sql
System.out.println("------------------------------ tmp_sql run ------------------------------");
int result = stmt.executeUpdate(tmp_sql);
// delete destination path if exists
System.out.println("------------------------------ del func ------------------------------");
DelPath(path_str);
System.out.println("------------------------------ del complete ------------------------------");
//move to destination from temp path
MovePath(path_str,tmp_path);
System.out.println("------------------------------ move succ ------------------------------");
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally{
try {
if (rs!=null) rs.close();
if (stmt!=null) stmt.close();
if (conn!=null) conn.close();
} catch (SQLException e) {}
}


}

}


EtlSqlOperator.sh



DelHdfsPath.sh


MoveHdfsPath.sh



SQL_TEST_TABLE (example)




주의할 점은 지우고 쓰는 것을 sync를 맞춰야되서 나름대로의 trick을 넣어뒀다.
// this is trick
그렇지 않으면 쓰는동안 지워버리는 문제가 생길 수 있기 때문이다. 그때의 에러는 해당 경로가 존재하지 않는다고 나올 것이다.

2022년 회고

 올해는 블로그 포스팅을 열심히 못했다. 개인적으로 지금까지 경험했던 내용들을 리마인드하자는 마인드로 한해를 보낸 것 같다.  대부분의 시간을 MLOps pipeline 구축하고 대부분을 최적화 하는데 시간을 많이 할애했다. 결국에는 MLops도 데이...