git 에서 줄바꿈 문제 - CRLF, LF

어느날 git 의 레포지토리의 파일이 모두 changed 상태로 보여 당황했다. 한 파일을 diff 로 보니 줄의 마지막에 ^M 이 보였다. 에디터가 OS에 따라서 LF 를 CRLF 로 바꿔서 보여주는 것이었다.

CRLF 상황

Linux 를 20.04 LTS 를 사용하다 os-release-upgrade 를 사용해서 22.04 LTS 로 업그레이드 했다.

그리고 한동안 다른 작업을 하다 소스를 다루게 되어서 윈도우 11에서 VS code 로 WSL의 우분투 폴더를 열어 보니 git 의 staging area 에 커밋할 파일이 엄청 들어와 있는 것이다. 찾다 보니 아래 그림 같이 줄바꿈 코드로 CRLR 로 인식을 한 것이다.

소스 파일을 수정할 일이 아니라서 검색을 좀 해보니 git 의 전역 설정에서 가능할 것 같았다. 다음 링크를 참조했다.

  1. https://stackoverflow.com/questions/28076760/git-add-adds-m-to-the-end-of-every-line
  2. https://github.com/Microsoft/WSL/issues/2318

git 전역 설정으로 autocrlf 를 활성화 한다. git 전역 설정에 다음 같이 autocrlf 를 true 지정하면 해결이 된다.

1
$ git config --global core.autocrlf true

[git] Swallow clone 과 브랜치

Swallow Clone 은 git 저장소에 커밋된 기록중 일부만 사용하는 기술 있다. 그런데 브랜치를 추가하면 원격지와 클론한 저장소에서 탐색이 안되는 단점이 있다. 여기서 swallow clone 에서 branch를 사용하는 방법을 요약한다.

Swallow Clone 에서 branch를 사용하자.

기본적으로 swallow clone 을 사용하면 원격 저장소의 branch 를 모두 볼 수 없다. 이것은 .git/config 를 보면 refs/remotes/origin/master 로 구성되어 있어서이다. 그래서 swallow clone 한 저장소는 원격 저장소의 master 브랜치만 보게 된다.

다음은 .git/config 의 일부이다.

1
2
3
[remote "origin"]
url = git@github.com:repository/YOUR_REPO.git
fetch = +refs/heads/*:refs/remotes/origin/master

master 브랜치 대신 브랜치 트리 모두를 보려면 아래 같은 단계로 처리해 준다.

  1. 다음 같이 원격 저장소의 모든 브랜치를 대상으로 처리해 준다.
1
git remote set-branches origin '*'

origin 의 브랜치를 master에서 * 로 지정하면 .git/config가 아래 같이 수정된다.

1
2
3
[remote "origin"]
url = git@github.com:repository/YOUR_REPO.git
fetch = +refs/heads/*:refs/remotes/origin/*
  1. 이어서 원격 레포지토리를 업데이트 하면 관련한 브랜치 정보를 갱신한다.
1
2
3
4
5
6
7
8
9
10
11
$ git remote update
Fetching origin
remote: Enumerating objects: 306, done.
remote: Counting objects: 100% (306/306), done.
remote: Compressing objects: 100% (283/283), done.
remote: Total 285 (delta 161), reused 6 (delta 0), pack-reused 0
Receiving objects: 100% (285/285), 8.59 MiB | 4.35 MiB/s, done.
Resolving deltas: 100% (161/161), completed with 16 local objects.
From git@github.com:repository/YOUR_REPO.git
* [new branch] 1024_dev -> origin/1024_dev
* [new branch] gt_master -> origin/gt_master
  1. 업데이트 한 후에 원격 브랜치만 검색해도 잘 나타난다.
1
2
3
4
5
$ git branch -r
origin/1024_dev
origin/HEAD -> origin/master
origin/gt_master
origin/master
  1. 원격지 브랜치를 체크아웃해서 사용하면 된다.
1
$ git checkout -b t origin/1024_dev

함께 보면 유용한 git 명령

간단하게 빌드/테스트 를 하기위함 이라면 `Sparse-checkout`` 과 함께 이용하면 좋다.

  1. [git] Sparse Checkout
  2. Swallow Clone

MariaDB - json type Tutorial

MariaDB / MySQL 에서 json data type을 사용할 수 있다.

MariaDB json tutorial

https://mariadb.com/ko/resources/blog/using-json-in-mariadb/

USE lecture;

CREATE TABLE locations (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
type CHAR(1) NOT NULL,
latitude DECIMAL(9,6) NOT NULL,
longitude DECIMAL(9,6) NOT NULL,
attr JSON,
PRIMARY KEY (id)
);

DESC locations;

INSERT INTO locations (type, name, latitude, longitude, attr) VALUES
(‘R’, ‘Lou Malnatis’, 42.0021628, -87.7255662,
‘{ “details”: {
“foodType”: “Pizza”,
“menu”: “https://www.loumalnatis.com/our-menu
},
“favorites”: [{“description”: “Pepperoni deep dish”, “price”: 18.75},
{“description”: “The Lou”, “price”: 24.75}]}’);

SELECT * FROM locations;

– scala value
SELECT NAME, latitude, longitude,
JSON_VALUE(attr, ‘$.details.foodType’) AS Food
FROM locations;

– return object
SELECT NAME, latitude, longitude,
JSON_QUERY(attr, ‘$.details’) AS details
FROM locations;

SELECT NAME, latitude, longitude,
JSON_QUERY(attr, ‘$.favorites’) AS details
FROM locations;

– return values
SELECT NAME, latitude, longitude,
JSON_EXTRACT(attr, ‘$.details’) AS details
FROM locations;

creating INDEX

– virtual colunn 을 이용해 json_oject의 요소를 컬럼으로 만들고
– 이것을 인덱스로 지정한다.

ALTER TABLE locations ADD COLUMN
food_type VARCHAR(25) AS ( JSON_VALUE( attr, ‘$.details.foodType’)) VIRTUAL;
DESC locations;

CREATE INDEX foottypes ON locations(food_type);

modifying json data

– JSON_INSERT(): inserting new object
UPDATE locations
SET attr = JSON_INSERT(attr, ‘$.nickname’, ‘oh ben’)
WHERE id=1;
SELECT JSON_QUERY(attr, ‘$.nickname’) FROM locations;
SELECT JSON_EXTRACT(attr, ‘$.nickname’) FROM locations;

SELECT * FROM locations;

UPDATE locations
SET attr = JSON_INSERT(attr, ‘$.foodTypes’,
JSON_ARRAY(‘Asian’, ‘Mexican’))
WHERE id = 1;

SELECT JSON_EXTRACT(attr, ‘$.foodTypes’) FROM locations;

– JSON_ARRAY_APPEND() : adding array elements
UPDATE locations
SET attr = JSON_ARRAY_APPEND(attr, ‘$.foodTypes’, ‘German’)
WHERE id = 1;

SELECT JSON_QUERY(attr, ‘$.foodTypes’) FROM locations;
SELECT JSON_EXTRACT(attr, ‘$.foodTypes’) FROM locations;

– JSON_REMOVE(): REMOVING ARRAY ELEMENTS

UPDATE locations
SET attr = JSON_REMOVE(attr, ‘$.foodTypes[2]’) – German
WHERE id = 1;

SELECT JSON_EXTRACT(attr, ‘$.foodTypes’) FROM locations;

Hybrid querhying: sql, json 장점

– json object 생성
SELECT
JSON_OBJECT(‘name’, name, ‘latitude’, latitude, ‘longitude’, longitude) AS data
FROM locations;

– merging
SELECT JSON_MERGE(
JSON_OBJECT(
‘name’, name,
‘latitude’, latitude,
‘Longitude’, longitude),
attr) AS DATA
FROM locations;

– JSON TO TABULAR DATA
– Server 10.6 the JSON_TABLE()

SELECT JSON_EXTRACT(attr, ‘$’) FROM locations;

SELECT l.name, d.food_type, d.menu
FROM locations AS l,
JSON_TABLE( l.attr,
‘$’ COLUMNS(
food_type VARCHAR(25) PATH ‘$.foodType’,
menu VARCHAR(200) PATH ‘$.menu’
)
) AS d
WHERE id=1;

ALTER TABLE locations ADD CONSTRAINT check_attr
CHECK(
type != ‘S’ OR (type = ‘S’ AND
JSON_TYPE(JSON_QUERY(attr, ‘$.details’)) = ‘OBJECT’ AND
JSON_TYPE(JSON_QUERY(attr, ‘$.details.events’)) = ‘ARRAY’ AND
JSON_TYPE(JSON_VALUE(attr, ‘$.details.yearOpened’)) = ‘INTEGER’ AND
JSON_TYPE(JSON_VALUE(attr, ‘$.details.capacity’)) = ‘INTEGER’ AND
JSON_EXISTS(attr, ‘$.details.yearOpened’) = 1 AND
JSON_EXISTS(attr, ‘$.details.capacity’) = 1 AND
JSON_LENGTH(JSON_QUERY(attr, ‘$.details.events’)) > 0));

DROP TABLE locations;


  1. jsonpath-expressions: https://mariadb.com/kb/en/jsonpath-expressions/
  2. Json type tutorials : Using-json-in-mariadb

MariaDB - json_datatype 사용

MariaDB 에는 보통 관계형 테이블에서 SQL을 이용해 JSON data를 사용하기 위해서 별도의 json type을 지원하고 있다.

MySQL / MariaDB json type

MariaDB 에서 일반 테이블에서 json을 지원하기 위해 json type을 지원하고 있다. 단 MySQL 의 json data type에 호환하기 위해서 MariaDB json type은 longtext 의 alias 로 제공한다.

  • https://mariadb.com/kb/en/json-data-type/
  • json이 sql 표준에 모순되어서 mariadb는 최소 성능에을 유지하는 벤치마크를 가지고 있다.
  • 적절한 json 데이터를 유지하기 위해서 json_valid 함수를 check 구문에 사용할 수 있다. (v10.4.3)

JSON 데이터 형식에 대해서는 다양한 소개를 검색할 수 있다.

MySQL / Oracle / MariaDB / PostgreSQL / MSSQL 등에서 Bigdata 분야에서 관심 받는 No-SQL의 자료 구조로 각광받고 있다. json은 1:n 의 관계에서 유동적인 자료를 처리하기 위해서 지원하는 추세이다.

MariaDB: json datatype 사용하기

1
2
3
> USE lecture;
>
> SHOW VARIABLES LIKE 'version';

json datatype 을 가진 테이블 생성

json_test 테이블을 생성한다. JSON 데이터 타입으로 선언하면 내부적으로 longtext로 저장 됨

1
2
3
4
> create table json_test (id  int ,  data JSON );
> DESC json_test;

> SHOW CREATE TABLE json_test;

SHOW CREATE 결과는 아래와 같을 것이다.

1
2
3
4
CREATE TABLE `json_test` (
`id` int(11) DEFAULT NULL,
`data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`data`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

테스트한 테이블을 제거

1
> DROP TABLE json_test;

json data validation

check() 검증 함수를 이용해서 insert하는 json data의 형식/유효성을 검증할 수 있다.

1
2
3
4
5
6
CREATE TABLE json_test (
id int ,
data JSON CHECK (JSON_VALID(data))
);

SHOW CREATE TABLE json_test;

show 결과는 아래 같다.

1
2
3
4
5
CREATE TABLE `json_test` (
`id` int(11) DEFAULT NULL,
`data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`data`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

json 입력

json 데이터 입력해 확인해 보자. 먼저 문자열을 입력해보자.

1
2
-- ERROR 4025 (23000): CONSTRAINT 
INSERT INTO json_test VALUES (1, 'invalid');

json 형식이 아니기 때문에 ERROR 4025 에러가 발생한다.

json datetype 컬럼에 json 데이터 형식을 가진 문자열로 입력할 수 있다.

1
2
3
4
-- string 
INSERT INTO json_test VALUES(1, '{ "name": "그르렁", "age": 30 }');
INSERT INTO json_test VALUES (2, '{"id": 1, "name": "으르렁", "age": 29, "address": "서울시"}');
SELECT * FROM json_test;

select 한 결과는 json data 컬럼에 JSON Object 형식으로 저장됨을 확인할 수 있다.

1
2
{ "name": "그르렁", "age": 30 }
{"id": 1, "name": "으르렁", "age": 29, "address": "서울시"}

또한 json datetype 컬럼에 json_object() 함수를 사용해 데이터를 입력할 수 있다.

1
json_object(key, value [,key, value])

json_object 로 입력하고 select 로 확인 해보자.

1
2
3
-- key, value, key, value 형태로 계속 입력된다.
> INSERT INTO json_test VALUES (3 , json_object('name' , '노르라' , 'Sex' , '남자' , 'age', 25, '전화' , '010-111-2222')) ;
> SELECT * FROM json_test;

문자열 JSON notation 형식으로 저장되는 data 에서 정보를 얻을 때는 JSON path 형식의 표현식을 사용한다.

json object의 값 얻기

JSON object 를 얻기위해서 다양한 JSON_ 형식의 함수를 지원한다. 함수들은 모둔 json path expression 을 사용해 json object 를 탐색한다. 아래는 대표적인 함수

1
2
3
4
- json_value(json_doc, path): READING SCALAR DATA
- json_array(json_doc, path): list 에서 JSON array 를 반화
- JSON_EXTRACT(json_doc, path[, path] ...) : return values
- JSON_QUERY(json_doc, path[, path] ...) : return json object, array, null (invalid json)

간단히 사용해 보자.

json_test 테이블에서 age 속성을 select 해보자

1
2
3
4
> SELECT JSON_VALUE(DATA, '$.age') AS age FROM json_test;
30
29
25

이번에는 name, age, 전화 속성만 추출해 보자.

1
2
3
4
5
6
7
8
9
> SELECT JSON_VALUE(DATA, '$.name') AS name,
JSON_VALUE(DATA, '$.age') AS age,
JSON_VALUE(DATA, '$.전화') AS 전화
FROM json_test;

"name" "age" "전화"
"그르렁" "30" \N
"으르렁" "29" \N
"노르라" "25" "010-111-2222"

JSON path expression

JSON 함수에서 JSON_Function(json_doc, path) 인자 path 는 json path expression 을 사용한다.

  • MariaDB jsonpath 는 Mysql 의 jsonpath 에 근접해 구현하고, 와일드카드 ** 스텝은 표준 보다는 MySQL에 가깝게 구현하고 있다.

path expression:

1
2
3
4
path: ['lax'] '$' [step]
`[lax]` : path mode 가 lax 지원.
`$` : 콘텍스트 아이템 시작. path는 항상 $로 시작함.
`[step]` : Json Object 인 object, array , wild card 가 이어진다.

object member selector

  • . : json object 를 선택시.
  • .* : 해당 object의 모든것

array 요소 selector

  • [n] : n 번째 요소
  • [*] : 모든 요소

mariadb 10.9 이후

  • [-n] : 마지막에서 -n 번 인덱스
  • [last-n] : 마지막에서 n번째 까지
  • [M to N] : m 과 n 사이 인덱스

예를 들어보자,

1
$.name    -- json객체의 name 속성을 지시한다.

배열 접근

1
$[5]      -- 5 번재 요소

Object selector tutorial

json 객체가 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SET @json='{
"A": [0,
[1, 2, 3],
[4, 5, 6],
"seven",
0.8,
true,
false,
"eleven",
[12, [13, 14], {"key1":"value1"},[15]],
true],
"B": {"C": 1},
"D": 2
}';

이 json 객체를 SQL 과 json 함수를 이용해 탐색하는 예이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
SELECT JSON_EXTRACT(@json, '$.A');             -- A객체
SELECT JSON_EXTRACT(@JSON, '$.A[2]'); -- A객체의 2 번째 요소
SELECT JSON_EXTRACT(@json, '$.A[-8][1]');

SELECT JSON_EXTRACT(@json, '$.B');

SELECT JSON_EXTRACT(@json, '$.A[last-1]');
SELECT JSON_EXTRACT(@json, '$.A[last-1][2]');
SELECT JSON_EXTRACT(@json, '$.A[last-7][1]');

SELECT JSON_EXTRACT(@json, '$.A[3 to 5]');

SELECT JSON_EXTRACT(@json, '$.A[3 to last]');

-- wildcard step '**'

SELECT JSON_EXTRACT(@JSON, '$**[1]');

SELECT JSON_EXTRACT(@JSON, '$**.C');

json functions 사용

테스트로 생성한 json_test 테이블을 사용해서 많이 사용하는 json 함수를 사용해 보자.

JSON_VALUE:

1
JSON_VALUE(json_doc, path) : path 에 있는 스칼라 값을 반환
1
select json_value('{"key1":123}', '$.key1');

객체 안의 배열과 스칼라 값 객체의 json_valie() 결과

1
2
3
4
5
-- key1 속성에 배열과 스칼라 값 객체의 json_valie() 결과
select json_value('{"key1": [1,2,3], "key1":123}', '$.key1');
123

select json_value('{"key1": [1,2,3], "key1":123}', '$.*');

배열은 [index[last]] 형식으로 탐색

1
select JSON_VALUE('[{"key1": [1,2,3], "key1":123}]', '$[0].key1');

JSON_EXTRACT:

1
JSON_EXTRACT(json_doc, path[, path] ...) : path에 일치하는 모든 return values. null (invalid)
1
2
3
4
5
6
7
8
9
10
11
SET @json = '[1, 2, [3, 4]]';
SELECT JSON_EXTRACT(@json, '$[1]');
SELECT JSON_EXTRACT(@json, '$[last]');


SELECT id, JSON_EXTRACT(DATA, '$**.age') FROM json_test;
SELECT id, JSON_EXTRACT(DATA, '$**.전화') FROM json_test;

SELECT id, JSON_EXTRACT(DATA, '$.name') FROM json_test;
SELECT id, JSON_EXTRACT(DATA, '$.age') FROM json_test;
SELECT id, JSON_EXTRACT(DATA, '$.전화') FROM json_test;

constraint error 는 보자, 새로운 객체를 추가한다.

1
2
3
INSERT INTO json_test VALUES(4, '{ "name": "가로미", "age": 29 }');
INSERT INTO json_test VALUES(5, '{ "name": "가로미", "age": 29 }');
SELECT * FROM json_test;

기존 4번 인덱스가 있어서 다음은 constraint error 를 발생한다.

1
2
-- constraint error:
INSERT INTO json_test VALUES(4, '{ "name": "가로미", "age": 29 }, { "name": "그러릿", "age": 21, "address": "경기도 성남시" }');

json_array()

JSON array 를 생성해 준다.

1
json_array(): list 에서 JSON array 를 반화

여러 데이터를 json_array로 배열로 입력한다.

1
2
3
4
5
INSERT INTO json_test VALUES(7, JSON_ARRAY(129, 3.1416, 'My name is "Foo"', NULL) );
INSERT INTO json_test VALUES(8, JSON_ARRAY( '{ "name": "크르렁", "age": 30 }', '{ "name": "갸르릉", "age": 21 }') );

INSERT INTO json_test VALUES(6, '{ "address" : [ { "name": "고로릿", "age": 20 }, { "name": "그러릿", "age": 21 }] } ');
INSERT INTO json_test VALUES(8, JSON_OBJECT( "address", JSON_ARRAY( '{ "name": "크르렁", "age": 30 }', '{ "name": "갸르릉", "age": 21 }') ) );

입력된 결과를 쿼리한다.

1
2
3
4
5
6
7
8
SELECT * FROM json_test;

SELECT id, JSON_EXTRACT(DATA, '$.address[0]') FROM json_test;

SELECT id, JSON_EXTRACT(DATA, '$.address[-1]') FROM json_test;


DROP TABLE json_test;

JSON_QUERY()

json_query 는 json 객체 형식의 표현을 입력해서 사용한다.

1
2
-- JSON_QUERY(json_doc, path
-- JSON_QUERY(json_doc, path[, path] ...) : path의 json object, array를 반환, null (invalid json)
1
2
3
4
5
-- key1 객체
select json_query('{"key1":{"a":1, "b":[1,2]}}', '$.key1');

-- key1 객체의 배열
select json_query('{"key1":123, "key1": [1,2,3]}', '$.key1');

json_table()

  • mariadb 10.6 이후.
  • json data 를 sql table 로 변환한다.

@json 변수에 json data 가 선언되고, sql 문에서 json_table 에 의해 관계형 테이블로 탐색된다.

1
2
3
4
5
6
7
8
9
10
11
12
set @json='
[
{"name":"Laptop", "color":"black", "price":"1000"},
{"name":"Jeans", "color":"blue"}
]';

select * from json_table(@json, '$[*]'
columns(
name varchar(10) path '$.name',
color varchar(10) path '$.color',
price decimal(8,2) path '$.price' )
) as jt;

SET 과 json 형식

SET 구문을 사용해서 json 변수를 선언하고 SQL 에서 사용할 수 있다.

1
2
3
SET @json = '{"key1":"60\\" Table", "key2":"1"}';

SELECT JSON_VALUE(@json,'$.key1') AS Name , json_value(@json,'$.key2') as ID;

— 참고

  1. json datatype : https://mariadb.com/kb/en/json-data-type/
  2. jsonpath-expressions: https://mariadb.com/kb/en/jsonpath-expressions/

[git] Swallow clone

shallow clone 은 git저장소의 일부 이력만 받아오는 방법이다. 즉 git 커밋등의 이력이 많아지고, 저장소 용량이 커지면 clone을 받는데, 시간도 많이 걸리고 저장소 크기도 용량이 커져서 clone, push, pull 시 시간 손해가 있을 수 있고, 어떤 경우 중단되는 경우도 있을 수 있다.

그래서 git저장소의 일부 이력만 선택적으로 받아 올 수 있다.

  1. 보통 swallow clone 은 저장소를 build & deployment 를 수행 할 때 유용하다.
  2. 저장소 소스를 수정하고 push 하는 경우에는 불리하다.

swallow clone

swallow clone을 위해서 git clone에 depth 인자에 주어진 개수만큼의 history 를 가져온다.

1
git clone [repo_url] --depth [개수]

최종 commit 부분만 swallow clone 하기

최종 history (git commit log) 만을 대상으로 clone 한다면 depth 1 로 주면 된다.

1
git clone --depth 1 abcd@git.com/abc/big_repository

git log를 보면 1개의 로그만 나타납니다. 기록이 하나밖에 안남는다.

1
git log 

100개의 기록 더가져오기

swallow clone한 저장소에서 fetch 를 사용해서 추가적인 log 기록을 더 가져올 수 있다.

1
2
git fetch --depth 50
git fetch --depth 50

주의!!!

현재 개발된 코드를 확인하고, 빌드하고 그리고 실행하고 테스트하는데는 shallow clone은 가볍게 수행할 수 있는 장점이 있다. 그런데 . 저장소에서 소스를 수정하고 push 하려고 하려면 저장소의 모든 Git 이력을 이용해 이 수정된 내용이 적용되야 한다. 그런데 swallow clone 상태에서는 어떤 기록에서 편집되었는지에 대한 것을 확인할 수 없어서 push가 거부될 수 있다.

이런 경우에는 unswallow 를 통해서 모든 history 를 복구한 후에 작업을 수행해야 한다.


unswallow 하기

기존 –depth 로 swallow clone 이 된 레포지토레애서 Un-swallow 를 하기 위해서 fetch 명령을 이용한다.

1
$ git fetch --unshallow # 저장소의 남은 history 를 읽어온다

함께 보면 유용한 git 명령

간단하게 빌드/테스트 를 하기위함 이라면 `Sparse-checkout`` 과 함께 이용하면 좋다.

[git] Sparse Checkout



- 참고

유니코드 이해하기

한글을 표현하는 인코딩

한글 인코딩의 이해 1편: 한글 인코딩의 역사와 유니코드

한글 인코딩의 이해 2편: 유니코드와 Java를 이용한 한글 처리

한글과 유니코드

The Absolute Minimum Every Software Developer Must Know About Unicode in 2023

Jupyterlab 4 업그레이드 주의 사항.

Jupyter 3.x 를 사용중에 Jupyter 4.0 출시를 했다고 해서 가상환경을 만들고 jupyterlab 4.0 을 설치했었다. 조금 더 UI 가 명확해 보이고 괜잖았는데 한글 입력시 글가가 깨져서 입력되거나 혹은 현재 편집중인 Cell 의 위치가 달라져 보이는 이상한 증상이 있어서 4.0 업그레이드 버전 사용을 안했는데 이유는 Node.js 버전 문제로 보인다.

Node.js 버전

기존에 jupyterlab 3.6 에서 Node.js 14 버전을 사용하고 있었다. Jupyterlab 4.x 버전의 주피터 확장 모듈 빌드도 문제가 없었는데 위에 언급한 이상한 현상이 발생해서 Jupyterlab 4.x 가상환경에서 Node.Js 버전을 18버전으로 업그레이후 후에는 문제없이 사용중이다.

Oracle XE Database 설치 - Windows

Oracle XE Database 설치를 알아본다.

  1. Oracle XE 설치
  2. Uninstall

1. 오라클 XE 설치

XE 는 Express Edition 의 약어입니다.

오라클 18c 버전은 설치 파일이 용량이 2G가 넘기 때문에 다운로드부터 설치까지 수분~수십분 이상이 소요

설치에 문제가 생기는 원인들

  • 컴퓨터 이름이 한글로 되어 있을 떄
  • 오라클을 한번 설치한 적이 있을 경우
    • oracle home 에서
      • ./deinstall/deinstall
      • regedit를 이용하여 기록을 삭제
      • 방법 명령창에서 sc delete OracleServiceXE 를 수행)

XE 라이센스

Download

최신 버전은 다운로드 페이지에서 직접 다운로드 할 수 있다.

  • 2022/3월 현재 21c 버전을 다운로드 할 수 있다.

https://www.oracle.com/kr/database/technologies/xe-downloads.html

이전 버전은 로그인후 다운로드 할 수 있다. 회원가입후 로그인한다.

회원가입

무료 Oracle 계정은 이메일 인증을 통해서 계정 생성이 가능하다.

XE 시스템 제약

  • 운영체제: Windows 10, Linux (우분투, 레드햇, CentOS 등)
  • 최대 12GB 사용자 데이터
  • 최대 2GB 데이터베이스 RAM
  • 최대 CPU 스레드 2개
  • 최대 3개의 PDB (Up to 3 Pluggable Databases)

Oracle XE 21c 설치

다운로드한 설치 파일을 실행한다.

라이센스

설치 위치

SYS, SYSTEM, PDBADMIN 계정 비밀번호입력: 관리자 계정 비밀번호를 정하여 주는 것으로 오라클 관리자 계정 아이디는 ‘SYSTEM’ 이다.

  • 혼란을 방지하기 위해 Manager1 으로 입력하자.

설치 시작

윈도우 보안 경고에서 Java 허용

구성요소 구성

설치 완료

오라클 XE 설치후 시작메뉴

설치후 확인

서비스 상태 확인

  1. Oracle XE 설치 완료되면, 윈도우 cmd 창에서 sc query 명령어를 통해 설치를 확인할 수 있다.
  1. 윈도우 메뉴 중 ‘앱 및 기능’ 목록 확인

데이터베이스 연결 확인

tnslistener 사용

  • Oracle XE 설치후 DB 연결을 위해 꼭 필요한 서비스인 ‘TNSListener’가 자동설치 되는데, cmd 창에서 연결 상태 확인이 가능하다.
  1. tnsping
  • cmd 창에서 tnsping 명령어 이용.
  • ‘tnsping 127.0.0.1’을 실행하면 아래와 같이 TNSListener, 연결 상태를 확인할 수 있다.
  1. lsnrctl

접속 에러

  1. 접속에러 : TNS-12541 TNS 리스너 에러

윈도우 컴퓨터 이름 에러일 가능성이 크다.
1. 내 컴퓨터 -> 속성 에서 컴퓨터 이름 확인
2. listener.ora 에서 HOST 이름 변경

  1. 오라클 서비스 재시작
    • 윈도우 서비스 Oracle TNS Listener 실행 확인
    • 윈도우의 서비스에서 OracleServiceXE 서비스 재시작

작업관리자 혹은 서비스 매니져에서 사용 가능.

  • 작업관리자 : Ctrl+Shift+ESC
  • 서비스 매니져 : Win + R 에서 services.msc

listener.ora, tnsnames.ora

tnsnames.ora 와 listener.ora 파일의 HOST 를 localhost 로 변경한다.

tnsnames.ora

listener.ora

2 Uninstall


참고

MySQL/MariaDB Server SSH Over Tunneling

클라이언트에서 원격 서버 MySQL DB server 접속시 SSH turnneling 을 이용할 수 있다.

  1. ssh tunneling 이해
  2. SSHTunnelForwarder 클래스
  3. HeidiSQL ssh tunnel 사용

ssh tnnneling 이해

비 암호화된 통신을 지원하는 원격 프로그램이 암호화된 통신을 지원하는 ssh 를 사용해서 원격지에 접속이 가능하게 한다. 직접 TLS/SSL 구현을 하지 않더라도 SSL 로 암호화된 통신 채널을 통해 안전하게 프로그램을 사용할 수 있다.

터널링은 아래 같이 SSH 서버를 통해서 원격지에서 SSH 통신을 지원한다.

링크 참고2 의 그림

MySQL client 에서 Database server 포트 3306으로 직접 접속하지 않고 ssh 를 이용해 원격 서버에 연결하고 원격 서버 내부에서 mysql server 에 접속해 사용하게 해준다.

SSHTunnelForwarder 이용 tunneling 사용

sshtunnel 모듈이 필요하다.

1
2
3
> pip install pymysql
> pip install sshtunnel
> pip install paramiko

시나리오

다음 2개의 시나리오는 참조 링크 3 의 공식문서에 있는 내용이다.

사례1) 사용자가 8080 웹 서비스에 연결할 필요가 있을 때 22번 포트로만 연결가능할 때

1
2
3
4
5
6
7
8
9
10
11
----------------------------------------------------------------------

|
-------------+ | +----------+
LOCAL | | | REMOTE | :22 SSH
CLIENT | <== SSH ========> | SERVER | :8080 web service
-------------+ | +----------+
|
FIREWALL (only port 22 is open)

----------------------------------------------------------------------

사례2) SSH server 가 허용하는 경우.

1
2
3
4
5
6
7
8
9
10
11
----------------------------------------------------------------------

|
-------------+ | +----------+ +---------
LOCAL | | | REMOTE | | PRIVATE
CLIENT | <== SSH ========> | SERVER | <== local ==> | SERVER
-------------+ | +----------+ +---------
|
FIREWALL (only port 443 is open)

----------------------------------------------------------------------

DB접속 정보

sshtunnel 패키지를 사용한다. DB 접속 정보를 외부 json 파일에서 얻어온다고 가정한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import json
from pathlib import Path

import sqlalchemy as sqla
from sqlalchemy import text
import pymysql

import pandas as pd
import numpy as np

ROOT_PATH = Path.home()

with open( ROOT_PATH / Path('.api_keys/secret_keys.json')) as f:
secrets = json.loads(f.read())

DB_USER, DB_PW = secrets['lecture']['userid'], secrets['lecture']['password']
SERVER24_USER, SERVER24_PW = secrets['DBSERVER']['userid'], secrets['DBSERVER']['password']

SSHTunnelForwarder 사용

1
2
3
4
SSHTunnelForwarder((HOST_IP, SSH_PORT),
ssh_username='USER_ID',
ssh_pkey='SSH_KEY',
remote_bind_address=('127.0.0.1', 3306))
  • ssh_address_or_host:

    • tuple 형태나 string 형태로 설정할 수 있으며, 위와같이 키워드를 생략할 수 있습니다.
    • 튜플형식: (호스트주소, 포트) 함께 정의
    • 문자열 형식: 호스트 주소 설정 (ssh_port 키워드도 별도로 설정해줘야 합니다.)
      • ~/.ssh/config 에 설정한 Host
  • ssh_port: ssh 서비스 포트

  • ssh_username: ssh 연결에 사용될 인증된 사용자

  • ssh_password: ssh 연결에 사용될 사용자의 접속 비밀번호
    보안을 위해 비밀번호를 설정하는 것보다는 private key을 사용하는 것을 권장합니다.

  • ssh_pkey: ssh 연결에 사용될 private key 파일 혹은 paramiko.pkey.PKey

  • remote_bind_address: (호스트주소, 포트) 튜플 형식. ssh 연결을 통해 접속한 원격 서버에서 접속할 private server 접속 정보.

with 구문과 사용

SSHTunnelForwarder 클래스에 __enter__, __exit__ magic method가 정의되어 with 구문과 함께 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
with sshtunnel.SSHTunnelForwarder(
(_host, _ssh_port),
ssh_username=_username,
ssh_password=_password,
remote_bind_address=(_remote_bind_address, _remote_mysql_port),
local_bind_address=(_local_bind_address, _local_mysql_port)
) as tunnel:
connection = mysql.connector.connect(
user=_db_user,
password=_db_password,
host=_local_bind_address,
database=_db_name,
port=_local_mysql_port)

Windows 경우 보안 경고를 확인!

윈도우즈에서 파이썬 코드에서 SSHTunnelForwarder 를 처음 사용시 아래 같이 경고를 만난다.

사례1

사례1 로 접속하는 방법이 전형적인 터널링 구현이다. SSHTunnelForwarder을 with 구문과 함께 쓸 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import pymysql
from sshtunnel import SSHTunnelForwarder

with SSHTunnelForwarder(('DBSERVER', 2020),
ssh_username=SERVER24_USER,
ssh_password=SERVER24_PW,
remote_bind_address=('127.0.0.1', 3306), ) as tunnel:

# connect MySQL like local
with pymysql.connect(
host='127.0.0.1', #(local_host)
user=DB_USER,
passwd=DB_PW,
db='lecture',
charset='utf8',
port=tunnel.local_bind_port, # ssh로 접속한 클라이언트 포트
cursorclass=pymysql.cursors.DictCursor) as conn:
with conn.cursor() as cur:
sql = "show tables;"
cur.execute(sql)
print(sql)
results = cur.fetchall()
print(results)
for result in results:
print(result)

터널링을 통해서 SQL Query가 잘 진행된다.

사례2) ssh_pkey 사용 (실패)

기록을 위해 남긴다. 잘 안되는 코드이다.

사례2 같이 직접 서버에 접근이 안되는 경우 ssh key pair 를 사용할 수 있다.

사용자 아이디, 패스워드 형식에서 패스워드를 직접 코드 등에 입력하기 보다 private key 쌍을 사용하면 좀 더 보안에 안전하다.

ssh-keygen 으로 생성한 공개키를 DB 서버의 ssh/authorized_keys`` 파일에 id_rsa.pub 내용을 ssh-copy-id또는scp` 명령으로 서버에 복사한다.

1
> ssh-copy-id -i ~/.ssh/id_rsa.pub username@jump_server_host -p ssh_port

scp 를 사용해도 좋다

1
2
3
> scp -P 2020 .\.ssh\id_rsa qkboo@DBSERVER:~/auth_key
#복사한 공개키를 `.ssh/authorized_keys` 에 추가한다.
> cat auth_key >> .ssh/authorized_keys

SSHTunnelForwarder 에서 ssh_pkey 에 비밀키를 지정해 준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
with SSHTunnelForwarder(('DBSERVER', 2020),
ssh_username='qkboo',
ssh_pkey='~/.ssh/id_rsa',
remote_bind_address=('127.0.0.1', 3306)) as tunnel:

print( tunnel.ssh_host_key )
print( tunnel.local_bind_address )
print( tunnel._remote_binds )

# connect MySQL like local
with pymysql.connect(
host='127.0.0.1', #(local_host)
user='user1',
passwd='012345',
db='lecture',
charset='utf8',
port=3306,
# port=tunnel.local_bind_port,
cursorclass=pymysql.cursors.DictCursor) as conn:
with conn.cursor() as cur:
sql = "show tables;"
cur.execute(sql)
print(sql)
results = cur.fetchall()
print(results)
for result in results:
print(result)

SQLAlchemy engine 생성하기

SQLAlchemy 의 engine 을 생성할 수 있다. with 구문과 함께 사용할 수 있지만 engine 을 여러 프로시저에서 지속해서 사용한다면 아래 같이 start(), stop() 사이에 pymysql, sqlalchemy 코드를 배치해도 좋다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# SSH Tunnel 사용1
from sshtunnel import SSHTunnelForwarder

server = SSHTunnelForwarder(('DBSERVER', 2020),
ssh_username=SERVER24_USER,
ssh_password=SERVER24_PW,
remote_bind_address=('127.0.0.1', 3306), )
server.start() # Tunnel 시작

local_port = str(server.local_bind_port)
engine = sqla.create_engine(f'mysql+pymysql://{DB_USER}:{DB_PW}@127.0.0.1:{local_port}/bookstore')
query = """SELECT * FROM book;"""
df = pd.read_sql(sqla.text(query), con=engine)
engine.dispose()

server.stop() # Tunnel 종료

with 구문과 사용

1
2
3
4
5
6
7
8
9
10
11
12
# SSH Tunnel 사용2 - with 구문과 사용
with SSHTunnelForwarder(('192.168.0.24', 2020),
ssh_username=SERVER24_USER,
ssh_password=SERVER24_PW,
remote_bind_address=('127.0.0.1', 3306), ) as tunnel:

local_port = str(tunnel.local_bind_port)
engine = sqla.create_engine(f'mysql+pymysql://{DB_USER}:{DB_PW}@127.0.0.1:{local_port}/bookstore')

query = """SELECT * FROM book;"""
df_sector = pd.read_sql(sqla.text(query), con=engine)
engine.dispose()

클라이언트에서 ssh tunnel 사용

클라이언트에서 MySQL / MariaDB 접속시 보안을 위해서 SSH over 방식을 통해 3306 포트가 아닌 ssh over 방법을 사용할 수 있다.

  1. HeidiSQL ssh tunnel 사용하기
  2. MysQL Workbench 에서 ssh tunnel 사용하기
  3. Server 에서 bind 제외하기

1. HeidiSQL ssh tunnel 사용하기

유형을 SSH Tunnel 로 선택하고 MySQL / MariaDB 데이터베이스의 DB 사용자 계정과 비밀번호을 입력한다.

SSH tunnel 을 만들어줄 ssh client 를 선택하고 개인 키 파일을 선택한다. SSH 로 로그인하는 DB server의 사용자 계정 ID를 입력한다.

선택한 개인 키가 로그인 패스워드를 대체한다.

SSH 아이디-패스워드 방법은 잘 안된다.

2. MysQL Workbench 에서 ssh tunnel 사용하기

MySQL workbench 에서 ssh tunnel 로 접속하려면 DB 서버측에 authorized_keys 에 클라이언트 공개키가 등록되어야 한다.

DB 서버의 `ssh/authorized_keys`` 파일에 id_rsa.pub 내용을 추가한다.

1
> scp -P 2020 .\.ssh\id_rsa qkboo@192.168.0.24:~/auth_key

복사한 공개키를 .ssh/authorized_keys 에 추가한다.

1
cat auth_key >> .ssh/authorized_keys

root 계정은 잘 안된다.

3. Server 에서 bind 제외하기

서버에서 my.cnf 에 bind를 0.0.0.0 으로 해두었으면 기본 값인 127.0.0.1로 해두고 SSH tunnel 방법으로 통해 접근하므로 좀 더 안전할 수 있다.

  • DB server 로컬에서는 직접 mysql client 로 접근가능하고
  • 외부 / 원격지에서는 ssh tunnel 을 통해서 작업이 가능하다.
1
bind-address  = 127.0.0.1

참고

  1. 참고1 : MySQL SSH Tunnel 소개, blog
  2. 참고2 - SSH Tunneling : SSH Tunnel 이란
  3. 참고3 : sshtunnel docs

MariaDB 클라언트-서버 TLS/SSL 암호화 연결(3)

MySQL / MariaDB 에 TLS 를 활성화 하고 클라이언트를 사용시 몇 가지 사례를 살펴본다.

글 타래:

  1. MariaDB 클라언트-서버 TLS/SSL 암호화 연결(1)
  2. MariaDB 클라언트-서버 TLS/SSL 암호화 연결(2)
  3. MariaDB 클라언트-서버 TLS/SSL 암호화 연결(3)

글 진행 순서:

  1. Grant 와 SSL 사용

1. Grant 와 SSL 사용

GRANT 로 어떤 사용자에게 SSL 을 필수로 지정한다.

1
mysql> grant all privileges on freedb.* to 'USERID'@'%' require ssl;

결과적으로 USERID 가 freedb 에서 허용된 권한은 다음 같다

1
2
3
mysql> show grants for 'USERID'@'%';
| GRANT USAGE ON *.* TO `USERID`@`%` IDENTIFIED BY PASSWORD '*0F5BEAE3607A82096C813BBE86B0BD7F91BD7339' REQUIRE SSL |
| GRANT ALL PRIVILEGES ON `freedb`.* TO `USERID`@`%` |

이 계정에 주어진 SSL 필수 권한 상태에서 MySQL Workbench로 SSl 을 강제로 끄고 연결해 보자.

SSL을 연결하지 않고 접속하면 접근을 막는 것을 확인할 수 있다.

2. TLS 세션 확인

mysqlclient, MySQLworkbench 혹은 Python/C/Java 등에서 DBI 인터페이스로 SSL 을 연결하고 SQL 을 실행한다. 이때 접속한 세션이 TLS 상태로 연결됐는지를 확인할 수 있다.

접속한 클라이언트에서 아래 SQL 로 Ssl_cipher 를 검색해보면 현재 세션이 TLS 상태인지를 확인이 가능하다. 빈 결과가 나오면 평문 TCP/IP 으로 접속되 것이다.

1
2
3
4
5
6
7
> show session status like 'Ssl_cipher';
+---------------+------------------------+
| Variable_name | Value |
+---------------+------------------------+
| Ssl_cipher | TLS_AES_256_GCM_SHA384 |
+---------------+------------------------+
1 row in set (0.006 sec)

  1. 참고1 : Verifying that a Connection is Using TLS
  2. 참고2 : Configuring MySQL to Use Encrypted Connections