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

[컴][웹] 2개의 다른 domain 에서 서버가 같은 상위 domain 을 사용하는 cookie 를 생성하도록 경우, 브라우저의 credential: 'include' 동작

만약 a.domain.net, b.domain.net 이 모두 Domain을 .domain.net 으로 해서 Set-Cookie 를 날리는 상황을 가정해보자.

  • a.domain.net : Set-Cookie: session=11111111111; path=/; domain=.domain.net; HttpOnly

  • b.domain.net : Set-Cookie: session=22222222222; path=/; domain=.domain.net; HttpOnly

  • A라는 사이트에서 login 은 해서 cookie 가 만들어졌다. 이때 credential 을 include 하면 이 cookie 가 request 시점에 날라간다.

  • 이 때 B라는 사이트에서 login을 한다. 그런데 같은 domain 에 대해서 만들기 때문에, A 라는 사이트에서 사용하던 cookie 가 browser에서 지워지고, 새롭게 B site 에서 사용할 cookie 가 만들어진다.

  • cookie 는 도메인 별로 unique 하게 존재하지만, 이것을 해당 domain 외의 domain 에서 access 하는 것 또한 same-origin policy 를 따라야 해서, 다른 site A에서 credential(cookie)이 없는 채로 날라간다.

See Also

  1. http - Share cookies between subdomain and domain - Stack Overflow
  2. https://setcookie.net/ : 여기서 Set-Cookie 의 domain설정을 해서 테스트해 볼 수 있다.

[컴] 무한스크롤 vs pagination

비교

무한스크롤 vs pagination

ref.1 은 infinte scroll 과 pagination 의 비교한 글이다.

일부를 정리하면 다음과 같다.

  • 좀 더 목적이 있는 글에는 pagination 이 낫다.

  • 유저가 빠르게 소비하는 content 에는 infinite 이 낫다.

  • 구글 search 의 결과는 pagination 이지만, google image 의 결과는 infinite 인 이유가 image 를 보는 속도가 빠르기 때문이다.

예외 상황

모든 페이지의 모습이 ref.1 의 글안에 들어맞는 것은 아니다.

몇몇 무한스크롤은 명시적으로 콘텐츠를 더 불러오기 위해 클릭을 필요로 한다.

  • 덕덕고의 검색 결과는 infinite 이다.
  • 덕덕고에서는 페이지 이후에 명시적으로 다음 페이지 로드를 하도록 한다.
    • 이것은 footer를 보여줄 수 있다.
    • 다만 여전히 클릭을 해야 한다.

개인적인 생각

개인적으로 무한스크롤의 가장 큰 단점은 명시적으로 그 곳을 찾아가기가 어렵다는 것이라고 생각한다.

그것을 극복하는 방법중에 각 글에 대한 url을 제공하는 것이 의미가 있다고 본다. 이 경우 viewer에 대한 구현은 2가지가 있을 수 있다.

  • 하나는 url의 내용만 보여주는것,
  • 다른 하나는 스크롤등의 흐름안에서 그것을 보여주는 법.
    • 이것은 '텔레그램 채널'이 가장 적절한 구현의 예를 보여준다거 생각한다.

Reference

  1. UX: Infinite Scrolling vs. Pagination

[컴] storage and retrieval

 

db index / index 가 저장되는 방법

storage and retrieval

ref.1 의 storage and retrieval 부분을 정리중…

index 저장에 대한 구현방법

  • log 를 append 를 하는 것이 빠르게 write 할 수 있는 방법. 기존의 것에 overwrite 하는 것보다 유리하다.
  • 이것은 특정 size 의 segment 까지만 저장하고, 한도를 넘어가면 새로운 파일로 저장. 이런식으로 여러 segmemt를 저장한다.

compaction 과 merge:

  • log를 write 하면서 동시에 가장 최근 값만 남기고 버리는 작업을 진행. 이것을 compaction 이라고 하자.
  • 이 compaction 을 하면서 동시에 다른 segment 것과 합치는 작업(merge) 를 진행할 수도 있다.
  • compaction 과 merge 를 완료하면, 최종적으로 모든 segement 에서 1개의 key 만 남게 된다.

hash table 한계:

  • 각 segment 에 대한 hash table 은 메모리에 둔다. hash table 은. file 버전은 효율적이지 않아서 없다.
    • file 버전의 hash table 은 많은 랜덤 액세스 I/O가 필요하고, 가득 차면 커지는 데 비용이 많이 들며, 해시 충돌에는 까다로운 로직이 필요하다.
  • hash table은 range query에는 적합하지 않다. kitty00000과 kitty99999 사이의 모든 키를 스캔하려 할 때, 할 수 없으며 해시 맵에서 각 키를 개별적으로 조회해야만 한다.

SSTable:

  • sorted string table 의 약자다.
  • 각 segment는 key 순으로 정렬되게 insert한다.(이러면 당연히 sequential insert 가 안된다.) 그리고 각 segment 에서 compaction 을 시킨다.(그러면, 키가 unique하게 된다.) 이제 이 segement들을 merge 하면서 sort 한다.(merge sort)
  • 이렇게 되면, 모든 key 에 대한 hashmap 을 갖지않고, 듬성듬성 key 를 갖는 sparse hash map 으로도 괜찮다. 근처에 있는 key 의 위치로 가서, 그 안에서 다시 찾아들어가면 된다.
  • 이것은 hash map 으로 찾아들어간 key들이 들어있는 chunk 의 경우는 압축해서 보관해도 된다. 압축을 하게 되면, I/O 대역폭 사용을 줄이고, disk 공간도 줄 일 수 있다.
  • insert 할 때부터, key를 정렬하면서 insert 를 하는 방법은 AVL tree 나 red-black tree 등이 있다.
  • B-tree 등을 이용하면, disk 에서도 이 정렬된 구조를 유지하는 것이 가능하지만, 메모리에서 이 정렬된 구조(structure)를 유지하는 것이 쉽다.

이제 storage engine 이 동작은 이렇게 하게 된다.

  • balanced tree 가 메모리에 있다.
  • write 이 들어오면, balanced tree 에 추가하게 된다.
  • 이 in-memory tree 를 memtable 이라 하자.
  • 이 memtable 이 대략적으로 수MB 정도로 커지면, 지금까지의 tree 를 file로 저장한다.
  • 이 때 이 file 은 SSTable(Sorted String Table, key 로 정렬된 data 를 가진 chunk 로 보면 된다. 즉, tree 의 leaft node를 순서대로 저장한 모양이 될 것) 로 저장한다.
  • 이것이 효과적인 이유는 tree 가 이미 key로 정렬된 key-value pair들을 유지하고 있기 때문이다.
  • 새롭게 만들어진 SSTable file 이 가장 최근 db segment 가 된다.
  • 이 SSTable file 로 이전의 key 가 저장되는 동안에도 write 가 들어오면, 새로운 memtable 이 생성된다.

결과적으로, 하나의 SSTable 에는 특정시간동안 쌓인 key 들이 있게 된다.

read 를 처리할때는

  • 먼저 memtable 에서 key를 찾는다.

  • 여기에 없으면, 가장최근의 disk segment 로 간다. 거기도 없으면, 그 다음 오래된 segment 로 가서 key 를 찾게 된다.

  • 때떄로 merge 및 compaction process 를 backgroud 에서 실행한다. 이 때 SSTable 이 줄어든다.

  • 이것을 실행해서, segment 파일들을 merge하고, overwritten 된 값이나 delted 된 값들을 버린다.

이때 만약 memtable 이 날라갈 때를 대비해서, write 될때 그냥 sequential 한 log 를 남기는 것으로 이 문제를 어느정도 커버할 수 있다. 그리고 이 로그는 SSTable 를 만들때 discard 하면 된다.

Reference

  1. Designing Data Intensive Applications

[컴] 3-way merge

merge / diff / 3way / 3-way / three-way merge /git merge

3-way merge

우리가 단순하게 diff 를 한다고 생각하면, 2개의 file을 비교하는 것을 생각한다.

이전의 merge 는 단순하게 이전파일(이것을 A라고 하자)에서 현재파일(이것을 B라고 하자.)을 비교하는 것을 생각했다.

A <--> B

그런데 version control system(vcs) 에서 이 방식은 문제가 발생한다. 하나의 file에 대해서 한사람만 수정하는 것이 아니라 여럿이 하나의 파일을 수정하는 경우가 발생한다.

이 경우 당연히 같은 시간에 같은 위치에 대한 작업이 발생하고, 이것에 대한 판단이 어려워진다.

예를 들어 ’AC’라는 내용이 있는 file을 수정하는 경우, 한쪽은 ABC 로 수정하고, 한쪽은 ADC 로 수정했다고 하자. 그러면 이것이 merge 될때, B를 선택할지 D를 선택할지 알 수 없다.

이것을 2-way merge를 사용하는 경우라면, 단순히 ABC –> ADC 로 변화한 것으로 판단하게 된다. 그런데 3-way merge를 보면, ‘ABC’와 ’ADC’ 의 같은 origin 인 ‘AC’와 비교한다. 그래서 ’AC –> ABC’ 와 ‘AC –> ADC’ 두개를 보고, 추가를 해야할 곳이 ’BD’모두를 추가해야 한다고 판단할 수 있다.

---------------------> time flies

                            +------+
                            | A    |
                            | +B   |
+-----+     +-----+         | +D   |
| AC  | --> | ABC | --------| C    |
+-----+     +-----+         +------+  
   |                          ^
   |               +-----+    |
   +-------------> | ADC | ---+ 
                   +-----+    
                      
                    

좀 더 자세하고, 다양한 설명은 ref. 1을 참고하도록 하자.

Reference

  1. version control - Why is a 3-way merge advantageous over a 2-way merge? - Stack Overflow

[컴][db] PostgreSQL 의 MVCC

 

postgresql / lock mechanism /

PostgreSQL 의 MVCC

postgres 는 ‘여러버전 동시 제어(Multiversion Concurrency Control, MVCC)’ 를 구현했다.

그래서 reading 과 writing 의 lock 이 관련이 없다. 즉, reading 을 위해 lock 이 걸려도, write 를 하는데 지장이 없다. write 은 새로운 version 의 row를 만들테니.

다음은 ref.1 의 정리다.

  • 각 SQL 문은 기초 데이터의 현재 상태와 관계없이 얼마 전의 데이터 스냅샷(데이터베이스 버전)을 보게 된다.
  • 같은 row에 대해 동시에 transaction 들이 발생하면 data 가 일관되지 않게 된다. 이런 일관되지 않은 data(inconsistent data) 를 statement 가 바라보게 되는 것을 막는다.
    • 이것이 각 데이터베이스 세션에 대해 transaction isolation을 제공하게 된다.
  • MVCC는 기존 데이터베이스 시스템의 locking methodologies을 피함으로써 다중 사용자 환경에서 합리적인 성능을 발휘할 수 있도록 lock contention을 최소화한다.
  • lock 대신에 MVCC 를 이용할때의 주요장점
    • MVCC에서 데이터 쿼리(read)를 위해 획득한 lock이 데이터 write를 위해 획득한 lock과 충돌하지 않기 때문에 read가 write를 차단하지 않고 write가 read를 차단하지 않는다는 것이다.
    • PostgreSQL은 Serializable Snapshot Isolation(SSI) level을 사용하여 가장 strictest level의 transaction isolation를 제공하는 경우에도 계속 이것을 보장한다.
  • 테이블 및 row level의 lock 기능도 PostgreSQL에서 사용할 수 있다.
    • 일반적으로 full transaction isolation이 필요하지 않고 특정 conflict 지점을 명시적으로(explicitly) 관리하는 것을 선호하는 애플리케이션들은 이것을 사용하면 된다.
  • 그러나 MVCC를 적절히 사용하면 일반적으로 lock보다 더 나은 성능을 제공한다.
  • 또한 application-defined advisory lock들은, ’single transaction에만 관련이 있는 것이 아닌 lock들’을 획득할 수 있는 메커니즘을 제공한다.

Refrence

  1. PostgreSQL: Documentation: 15: 13.1. Introduction

[컴][자바] G1 Garbage Collector 2/2

  1. 쿠…sal: [컴][자바] java 의 G1 collector
  2. 쿠…sal: [컴][java] G1 Garbage Collector 2/2

G1 Garbage Collector 2/2

G1 heap 구조

하나의 memory area 를 여러 size의 region 들로 나눠서 사용하게 된다. heap 이 대략적으로 2000개정도의 region 으로 나눠진다. 가장 작은 사이즈는 1Mb 에서 가장큰 것은 32Mb 정도 된다.

그림처럼, 각 region 들은 Eden, Survivor, Old Generation space 중 하나로 사용된다. 여기서 Enden과 Survivor 는 young generation 이다. 추가로 Humongous region 이라는 4번째 region type 이 있다. (한국말로 하면 ’거대영역’정도일려나.)

옛날 garbage collector 들처럼 region 들이 연속적일 필요는 없다. (예를 들어, CMS 의 경우를 보면, eden, survivor 등 각 영역은 1개의 연속된 공간으로 이뤄졌다. 참고)

frome ref. 1
  • live objct들(사용중인 object들)은 한 region 에서 다른 region 으로 evcuated 된다.(즉, copy 또는 move 된다.)
  • region 들은 다른 application thread 들을 멈추는 것 없이 병렬로 collected 되도록 디자인 됐다.

G1 에서 Young GC

young generation garbabe collection 들, 즉 young GCs

  • 이때 STW(Stop The World) 멈춤이 있다. 즉, 모든 application thread 들은 이 operation 을 위해서 멈추게 된다.
  • young GC 는 여러 thread들을 사용해서 병렬로 수행된다.
  • live object들은 새로운 survivor region 들로 copy 되거나 old generation region 들로 copy 된다.
    • live object들은 1개 이상의 survivor region 들로 evacuated 된다. 몇몇 aging threshold 에 도달한 object들은 old generation region 들로 가게 된다.
  • Eden size 와 survivor size 는 다음 young GC를 위해 계산된다.
  • Accounting information은 size를 계산하는데 도움을 주기 위해 저장된다.
    • ’pause time 목표’같은 값들이 고려되어진다.
  • young generation memory 는 여러 region 들로 구성된다. 이 region 들은 연속된 공간에 있지 않다. 이것은 필요할 때 size를 조정하는 것을 쉽게 해준다.
  • 이 방법은 region 들을 필요할 때 region 의 size를 조정하기 매우 쉽게 해준다. 필요할 때 더 크게, 또는 더 작게 만들게 해준다.

G1 으로 Young GC 가 끝나면:

  • live object들은 survivor region 들이나 old generation region 들로 evacuated 된 상태다.
  • 아래 그림에서 최근에 old generation region 으로 움직인 것은 어두운파란색이고, 최근에 survivor region 으로 간 object들은 녹색이다.

G1 으로 Old Generation Collection

CMS collector 처럼, G1 collector 는 ’낮은 pause 시간을 갖는 old generation object를 위한 collector’로 디자인 됐다.

old generation 에 대한 G1 collection phase들은 다음과 같다.

  1. initial Mark(Stop the World Event)
  2. Root Region Scanning
  3. Concurrent Marking
  4. Remark(Stop the World Event)
  5. Cleanup(Stop the World Event and Concurrent)

상세 내용:

  1. Inintial Marking

    • Stop the World Event : 이것도 ’Stop the World Event’이다.
    • live object에 대한 initial marking 은 young generation garbage collection 때 같이 수행된다. log에 이것들은 GC pause (young)(inital-mark)로 기록된다.
  2. concurrent marking phase

    • 전체 heap 에서 live object들을 찾는다.
    • 이것은 application 이 실해되는 중에 일어난다.
    • 이 phase는 young generation garbage collection 들에 의해 interrupted 될 수 있다.
    • liveness 를 결정하는 accounting information 이 계산된다.
    • liveness informatin 은 application 이 실행되는 중에 계산된다.
    • liveness information 은, evaucation pause 동안에, 어떤 region 들이 환원(reclaim) 되는 것이 제일 좋은지 구별한다.
    • CMS 같은 sweeping phase 가 없다.
  3. Remark Phase

    • heap 에 있는 live object 의 marking 을 완료한다.
    • 완전하게 비어있는 region 들은 환원된다.(reclaimed)
    • Snapshot-at-the-Begging(SATB) 알고리즘을 사용한다. 이것은 CMS 에서 사용되던 것보다 더 빠르다.
    • 모든 region 에 대한 liveness 를 계산한다.
  4. Copying/Cleanup Phase

    • live object들과 완전히 빈 region에 대한 accounting을 수행한다.(Stop the world)
    • Remembered Sets 를 정리한다.(Stop the world)
    • 빈 region들을 초기화하고 그것들을 free list에 반환한다.
    • young generation 과 old generation 은 동시에 환원된다.
    • old generation region 들은 그들의 liveness 에 기반해서 선택된다.
      • G1 은 가장 낮은 liveness 를 가진 region들을 선택한다.
      • 이 region 들은 가장 빠르게 collect 된다.
    • 그리고 나서 이 region들은 young GC 가 수행되는 시간에 같이 실행돼서 collected 된다.
    • 이것은 log에 [GC pause (mixed)] 라고 표시된다.

선택된 region 들은 collected되고, 어두운파랑색 region 과 어두녹색 region 으로 compacted된 상태다.

[컴] Concurrent Mark Sweep (CMS) collector

Concurrent Mark Sweep (CMS) collector

※ 다음 내용은 ref. 1을 번역하고, 몇몇 부분은 설명을 추가해서 재구성했다.

CMS collector 를 한국말로 번역한다면, ‘동시에 마킹해서 쓸어버리는 collector’ 정도가 될 것 같다.

이 CMS 는 대부분의 garbage collecion 일을 application thread들과 동시에(concurrently) 처리하게 해서 ‘잠시멈춤’(pause) 들을 최소화 하려 시도한다.(–> application thread 들이 동작할 때 같이 garbage collection 을 한다)

일반적으로 동시에 실행돼서 pause가 적은 garbabe collector 는 object들을 복사하거나, 압축 하지 않는다.

garbage collection 은 live object들을 이동시키지 않고 처리된다. 만약 fragmentation 이 문제가 되면 좀 더 큰 heap 을 할당해야 한다.

1. CMS collector 를 위한 heap 구조

heap 은 다음 3개의 공간(space)으로 나눠진다.

  • Young generation
    • Eden space
    • 2 Survivors spaces
  • Old generation

full GC 를 하는 것이 아니라면, 압축(compaction)은 하지 않는다.

from ref. 1

2. Young GC 가 CMS에서 어떻게 동작하는가

application 이 일정시간 동작한 후의 heap 모습을 보면 대략 다음과 같다.

  • young generation 공간은 allocated 되었다.
  • old generation 공간은 일부 allocated 되어 있다.
    • Object들은 old generation area 에 흩어져 있게 된다.
    • 이 allocated object 를 다른 곳으로 움직이지 않고, 그 자리에서 deallocate 하게 된다.
    • full GC 가 있지 않는한, space는 compacted 되지 않는다.
from ref. 1

GC

young generation 에 있는 CMS collector는 parrallel collector와 동일한 알고리즘을 사용한다.

heap 의 old generation에 대한 CMS collector 의 수행은 다음과 같은 큰단계(phase)들로 나눌 수 있다.

  1. initial mark(Stop the World Event)
  2. concurrent mark
  3. remark(Stop the World Event)
  4. concurrent sweep
  5. resetting

단계별 설명:

  1. Young generation collection

    여기선 시간이 지나서 오래된 object들을 다른 space로 옮기는 copy 가 이뤄진다.

    • Eden space와 survivor space 에 있는 live object들(사용중인 object들)은 다른 survivor space로 copy 된다.
      –> 그리고 기존의 것은 지워진다. 그래서 Young GC 이후에 Eden space와 ’survivor 중 한개’는 비워진다.
    • aging threshold 에 다다른 오래된 object들은 old generation 으로 가게 된다.
    from ref. 1

    아래 그림에서 새롭게 old generation 으로 가게된 obejct들은 어두운파란색으로 표시됐다.

    from ref. 1
  2. CMS 로 Old Generation Collection

    한참 application 이 돌다가 이제 old generation 이 어느정도 찼다 싶으면, CMS가 시작된다. 여기서부터 CMS 동작의 시작이다. 여기서는 live object를 찾아서 mark 를 한다.

    대략적으로 이야기 하면, 처음에는 잠깐 멈춰서 live object를 찾는다.(initial mark) 그리고 다시 application 이 돌고, 그러면서 이제 live object들을 계속 mark 한다.(concurrent mark) 그리고 다시 잠깐 멈춰서 live object를 찾는다.(remark)

    ‘stop the world’ event 가 총 2번 발생하는데, inital mark 와 remark 시점에 발생한다. old generation 이 특정 점유율(occupancy rate)에 도달할때, CMS 가 시작된다.

    1. initial mark : initial mark 는 짧은 pause phase 이다. live object들이 mark 되는 시점이다.
    2. Concurrent marking : application 은 계속 실행되고 있는 동안에 Concurrent marking 은 live object들을 찾는다.
    3. remark : remark phase 에서는, 방금수행한 concurrent marking 동안에 놓쳤던 object들을 찾게 된다.
    from ref. 1
  3. Old Generation Collection - Concurrent Sweep

    이전 phase에서 mark 되지 않은 Object들, 즉 live object들이 아닌 dead object들은 그자리에서 deallocated 된다. compaction 은 없다.

  4. Old Generation Collection - After Sweeping

    위 sweeping phase 이후, 많은 메모리들이 freed 된 상태가 된다.

    최종적으로 CMS collector 는 resetting phase로 갈 것이다. 이제 다시 GC threshold 에 도달할 때까지 그냥 기다린다.

See Also

  1. 쿠…sal: [컴][자바] java 의 G1 collector

Reference

  1. Getting Started with the G1 Garbage Collector

[컴] rootless Podman 를 ‘sudo’, ‘su’ 로 사용하면 안되는 이유

 

rootless Podman 를 ‘sudo’, ‘su’ 로 사용하면 안되는 이유

ref.1 의 내용을 정리했다.

문제상황

  • 만약 user1 로 만들어진 podman container가 있다.
  • 이 상황에서 su user1으로 들어가서 image를 다시 build하면 error 가 난다.
    Error: error creating tmpdir: mkdir /run/user/1000: permission denied

podman이 temporary files directory

  • 이 디렉토리는 2가지 용도로 사용
    • directory 를 sentinel file 을 저장해서 restart 를 감지하는데 쓰고,
    • container가 재시작할 때 다시 생성되는 container content 를 저장, 예를 들면, /etc/resolv.conf
  • 그래서 podman 이 시작된 후에 이 directory 를 바꾸는 것은 심각한 문제를 일으킬 수 있다.

podman 이 restart 를 감지해야 하는 이유

재부팅되면, podman 은 refreshing the state 를 해야한다.

  • 재부팅이 되면
    • 모든 container들은 더 이상 running 하지 않는다.
    • 모든 container filesystem들은 unmount된다.
    • 모든 network interface들은 새롭게 만들어질 필요가 있다.
  • podman은 이것들을 반영하기 위해서 podman database를 update할 필요가 있다.
  • 그리고 container를 launch 할 준비가 됐다고 확신시켜주기 위한 per-boot setup 을 할 필요가 있다.
  • 이것을 ’refreshing the state’라고 한다.

‘refreshing the state’

다음의 동작을 ‘refreshing the state’ 라고 부른다.

  • podman은 reboot 될 때의 상태를 podman database에 update할 필요가 있다.
  • 그리고 container를 launch 할 준비가 됐다고 확신시켜주기 위해 per-boot setup 을 할 필요가 있다.

필요한 이유:

  • 이것은 podman 은 deamon 이 아니기 때문에 필요하다.(즉, podman이 항상 떠 있지 않아서?)
  • 각각의 podman command 는 새로운 process로 실행된다. 처음에 container들이 어떤 상태인지 모르는 상태로 실행된다.
  • 그래서 database를 보고, 모든 container의 정확한 현재 모습과 상태들을 알 수 있다.
  • 이 그림이 계속 정확하게 유지되기 위해서, reboot후에 ‘refreshing the state’ 는 필수다.

history

  • refresh 를 수행하려면, system이 reboot 됐는지를 알 수 있는 방법이 필요하다.
  • 개발초기, Podman team 은 tmpfs filesystem 에서 보초병 파일(sentinel file)을 만들어 사용하기로 정했다.
  • 이것은 in-memory filesystem 이다. 그래서 reboot 하면 이전 정보가 사라진다.
  • Podman 은 이 tmpfs 에 file 이 존재하는지 여부를 보고, 없으면 file을 만드는 것을 통해, 이 system reboot 이후 처음인지 여부를 알 수 있다.
  • 문제는 어디에 temporary files directory 를 넣어야 하는가를 결정하는 것이다.
  • 명확한 답은 /tmp 이다. 하지만 /tmp가 항상 tmpfs filesystem 이 라는 법은 없다.
  • 대신에, 기본적으로, Podman 은 /run을 이용한다. 이곳은 tmpfs 라는 것이 보장된다.
  • 다만 /run은 root 에 의해서만 write 이 가능하다.
  • 그래서 rootless podman 은 다른 곳을 찾았다. 그곳이 /run/user/$uid 이다. (/run/user/$uid 에 대한 이야기는 ref.2 를 참고)
  • 이 directory 가 만들어지려면, PAM 설정이 필요하다.(ref. 2 에 따르면 이 폴더는 pam_systemd에서 만든다.)
  • Rootless Podman 은 만약 시스템들이 이 directory 를 지원하지 않으면, /tmp를 사용하게 된다.
  • 물론 이것도 /tmp 가 tmpfs 가 아닐 가능성은 있지만, 더 나은 방법이 없다.

long-running container 사용할때

  • 이 디렉토리는 user session 이 만들어질때 만들어지고, user session 이 사라질때 자동으로 지워진다.
  • 이 때 문제는 user session 으로 들어와서 podman 을 실행하고, logout 을 해버리면, run/user/$uid가 사라지고, 이것은 Podman 이 refresh the state 를 하고, 현재 running 하고 있는 container들이 running 하지 않다고 표시하게 된다.
  • 그래서 logind-managed system에서는 persistent user session 을 만들 수 있는 option 이 있다.(login enable-linger)
  • login enable-linger 은 long-running container들을 실행하는 user들은 모두 이 것을 이용해야 한다.

su, sudo

  • su, sudo는 login session 을 만들지 않는다. 그래서 /run/user/$uid 를 만들지 않는다.
  • 그렇기 때문에, Rootless podman 을 사용할 때 su, sudo로 사용하면 안된다. Root container들은 문제가 없다.
  • 다만, loginctl enable-linger 를 사용해서 그 user에 대해서 영구적인 user session 을 만들면 가능하다.
  • sudo, su 대신에 user session 을 만들어주는, machinectl login 또는 ssh 를 사용해서 접근하면 된다.

susudo 를 사용해서 rootless podman 을 실행하는 법

  1. 처음 시작시에 sudo 또는 su로 Podman 을 실행하면, /tmp가 temporary files directory로 잡혀서 실행가능
  2. --runroot flag 를 이용해서 temporary files directory path 를 지정해서 사용할 수 있다.
  3. 다만 이것들은 cgroups v1 system에서만 가능. 그리고 정확한 환경변수를 위해 --login argument가 필요할 것이다.
  4. cgroups v2 system 에서 하려면, cgroup 들을 setup 하고, rootless container에 대한 resource limit 들을 setup 하기 위해 login session 의 다른 부분들이 더 필요

Reference

  1. Why can’t I use sudo with rootless Podman? | Enable Sysadmin
  2. linux - What is this folder /run/user/1000? - Unix & Linux Stack Exchange

[컴] podman 에서는 해당 계정에서 만든 container들은 해당 계정으로만 볼 수 있다

팟맨 / 포드맨 / 파드맨/ 왜 안보이나, / 왜 컨테이너가 안보이지 /

podman 에서는 해당 계정에서 만든 container들은 해당 계정으로만 볼 수 있다

update 2023-08-02: 자세한 내용은 rootless Podman 를 ‘sudo’, ‘su’ 로 사용하면 안되는 이유 를 보자

podman 에서는 해당 계정에서 만든 container들은 해당 계정으로만 볼 수 있다. 그래서 당연히 실행도 해당계정으로만 해야 한다. 그래서 만약 user1 로 만든 container는 다른 계정에서 sudo 등을 이용해서 실행할 수 없다.

그리고, root 도 non-root user가 실행한 container를 볼 수 없다.(ref. 1)

ref. 3 을 보면, 자세한 설명이 나온다. 여기서 간략하게 추려보면

  • podman 은 실행시점에 temp directory 가 필요한데 이 temp directory 로 /run/user/$UID 를 이용한다. 만약 이 directory 를 사용할 수 없다면, /tmp를 이용하게 된다. 그런데 이 결정은 podman 이 처음 run 할때 정해지고는 변경되지 않는다.
  • 이 directory 는 login 할 때 만들어진다. 그리고 logout 하면 지워진다.
  • 그런데 susudo를 이용하면 login session 이 만들어지지 않는다. 그래서 /run/user/$UID 가 없다.
  • root container 들은 괜찮다. 그들은 /run/user/$UID 대신에 /run을 사용하기 때문에.
  • 그래서 susudo를 이용해서 rootless container를 실행하는 것은 권장하지 않는다.
  • 그래서 첫 실행에 /tmp를 실행하도록 만들기도 한다. 그러나 이것도 문제는 있다. 이부분은 ref. 3 을 확인하자.

Reference

  1. podman cannot see all running containers - Stack Overflow
  2. podman - How to list rootless containers of ALL the users? - Stack Overflow
  3. Why can’t I use sudo with rootless Podman? | Enable Sysadmin

[컴][네트워크] 웹소켓 WebSocket

웹소켓 / 프로토콜 / 정의 / 동작 / 테스트

웹소켓 WebSocket

http 에서 server쪽에서 client 쪽에 update 를 하려면?

이것은 기본적으로 불가능하다. 단순하게 생각하면, server 가 request 를 보낸다고 하면, 그것을 받아줄 server가 존재해야 한다. 그럼 어떻게 할까?

예전에는 이것을 위해서 계속 client 에서 물어보는 방법을 택했다.

1. polling

  • 계속 client 에서 server에게 물어보는 것이다. 너 나한테 뭐 보낼것 없니? 라고. 그러다 서버가 그래 이것좀 처리해줘 하고 주면, 그것을 받아서 처리한다.
  • 이것의 문제는 쓸데없이 자꾸 server에게 request 를 보내야 한다는 것이다. 그냥 단순히 물어보기만 하는 request 라도 서버는 그것이 단순히 물어보는 request인지를 판별하기 위한 resource를 소모한다. 그러므로 많은 client 가 동시에 그런일을 한다고 가정한다면 이것은 꽤나 부하가 될 수 있다.
  • ref. 1 에 long polling 이 있는데, 그것은 short polling 보다 비효율적이다. long polling 은 request 를 받은 서버가 client 와 connection 을 오랜시간동안 유지한다는 이야기인데, 이렇게 되면 특정시간내에 더 적은client를 처리할 수 밖에 없다.

그런데 이제 browser에서 SSE 라는 것을 지원하기 시작했다.(2009년 4월)

2. SSE(Server Sent Event)

  • client 에서 한번 서버와 연결한 이후 주기적으로 서버는 이벤트를 보낼 수 있게 된다.
  • 양방향(full duplex)는 아니다. 서버에서만 보내게 된다.
  • 예를 들어, 주식의 실시간차트 같은 것들을 이 SSE로 구현할 수 있다.

3. WebSocket

  • WebSocket protocol 은 2011년에 표준화 됐다.[ref. 3]
  • 서버측에서 client 로 event를 보내는 방법을 제공해 준다.
  • 기본적으로 http 가 사용하는 TCP 는 양방향 통신이 가능했지만, HTTP 는 그렇지 않다.
  • 그래서 이 WebSocket은 TCP 위에 새로운 protocol 을 만들어서, polling을 하지 않고, client server간의 event를 수시로 아무때나 주고받을 수 있도록 connection 을 열어놓도록 설계했다.

WebSocket

connection 을 맺을 때는 http header에 특정 field를 넣어줘야 한다. 그러면 browser에서 이 header를 보고 WebSocket protocol 로 전환한다. 그리고 나서 browser가 서버쪽에 Sec-WebSocket-Key에 random하게 만든 key를 base64-encoding 해서 보내게 된다. 그러면 서버는 응답에 Sec-WebSocket-Accept 를 보내준다. 그리고 이후로 data를 WebSocket protocol 을 이용해서 주고받게 된다.(아래 그림 참고)

client --> server
--------------->

Sec-WebSocket-Version: 13\r\n
Sec-WebSocket-Key: lXp6HCmTm4HP8Roz/CmoOA==\r\n
Connection: Upgrade\r\n
Upgrade: websocket\r\n
Host: localhost:8080\r\n


client <-- server
<----------------

Upgrade: websocket\r\n
Connection: Upgrade\r\n
Sec-WebSocket-Accept: K/PoUF1zivlox8EW6nXD9C9qYDM=\r\n

websocket의 packet

예시

Reference

  1. Network Protocols behind Server Push, Online Gaming, and Emails
  2. 쿠…sal: [컴][웹] 간단하게 주기적으로 message 를 수신하는 web page
  3. WebSocket - Wikipedia

[컴] JunoDB 의 overview

주노 / 페이팔 디비 / 대규모 / 처리 / 대용량 처리 디비 

JunoDB 의 overview

paypal 에서 자신들이 사용하던 NoSQL DB 를 open source 로 공개했다.

여기선 ref.1 의 내용을 정리한다.

  • 사실상 paypal 의 모든 core back-end service 가 JunoDB에 의존하다.
  • JunoDB 를 이용해서, application 들은 data 를 효과적으로 저장하고, cache 를 해서 RDS 와 다른 서비스들에 대한 빠른 access 와 부하를 줄일 수 있다.
  • 단일 스레드 C++ 프로그램으로 시작
  • 이후 동시성이 높고 멀티코어 친화적인 Golang으로 재작성
  • 인메모리의 짧은 TTL(Time To Live) 데이터 저장소
    --> 에서 긴 TTL을 지원하는 영구 데이터 저장소로 발전
  • 온디스크 암호화와 전송 중 TLS를 통해 향상된 데이터 보안을 제공
  • 데이터 재분배(data redistribution)를 통한 빠른 확장(quick scaling)
  • 현재, 시스템 가용성이 99.999%에 달하는 JunoDB
  • 매일 3,500억 건의 요청을 처리.(평균 초당 40,510건)

Juno DB 사용하는 경우

  • 캐싱
  • Idempotency 로 사용 : 여러 요청이 와도 오직 하나의 동작만 하도록
  • counter : 특정 자원의 한도를 정하고 사용할 수 있게 해준다.
  • SoR(System of Record, 기록시스템) : 영구적인 기록은 아니지만, 몇년정도의 장기적인 기록을 위해 사용한다.
  • latency bridging
    • JunoDB의 빠른 클러스터간 복제(replication)는, Oracle 처리중의 복제 지연들 해결해준다.
    • 그래서 거의즉시(near-instant)적으로 모든 곳에서 일관된 읽기를 가능하게 해준다.
    • latency bridging 기술은 “계정/사용자 생성 과 결제 처리를 위한 멀티 데이터 센터 active-active processing” 에서 사용돼서 높은 가용성, 신뢰성, 읽기확정성(read sacling)을 보장해 준다.

JunoDB Architecture: A High-Level Overview

JunoDB 는 매끄럽게 함께 작동하는 3개의 중요 component들로 구성돼 있다.

  1. JunoDB client library : 이것은 DB를 사용하는 Client 쪽에서 사용하는 library
  2. JunoDB proxy :
    • JunoDB proxy instance들은 load balancer 에 의해 제어된다.
    • client 의 request들과 다른 site들로 부터의 replication traffic 을 받는다.
    • 각각의 proxy 는 모든 JunoDB storage server instance들에게 연결되고,
    • 각 request 를 storage server instance들의 group 으로 전달한다.
    • 이 group은 shard mapping 을 기반으로 만들어져 있다.
    • ETCD에서 유지된다. 이 ETCD는 JunoDB cluster 설정(configurations)들을 저장하는 data store 다.
  3. JunoDB storage server
    • JunoDB storage server instance들은 proxy 로 부터 오는 작업 요청들을 수락하고 RocksDB 를 이용해서 memory 또는 영구 저장소(persistent storage)에 data 를 저장한다. 각 storage server instance 는 shard들의 집합을 담당해서 원활하고, 효과적인 data 저장과 관리를 보장한다.

Scalability 달성

PayPal 은 십수년전에 지속적인 빠른 고객 및 결제 건수의 성장을 지원하기 위해, application layer 에서 horizontally scale 되는 마이크로서비스들의 구조로 변경했다. 그래서 키-값 저장소로 지속적으로 인바운드 연결 수가 증가했다.

필요한 규모를 처리하기 위한 상용 또는 오픈 소스 솔루션이 제공되지 않았기 때문에, 직접만들었다.

분산 key-value store들에서 2가지 주요한 scaling 요구를 처리한다.

  1. 성장하는 클라이언트 연결 수를 수용하여, 원활한 액세스와 시스템 응답성을 보장합니다.
  2. 데이터 양과 액세스 속도가 증가함에 따라 데이터 증가을 처리하고 효율적인 읽기 및 쓰기 처리량(throughput)을 보장한다.

Scaling for Client Connections

  • 가로 연결 확장을 용이하게 하기 위해 proxy 기반 아키텍처가 선택되었다.
  • 이 구성에서 클라이언트는 가벼워서 모든 저장 노드와 연결을 설정할 필요가 없다.
  • 클라이언트 연결이 한도에 도달하는 경우, 추가적인 프록시를 간단히 추가할 수 있다.
  • 이 방법은 약간의 지연을 초래할 수는 있지만, 확장성에 매우 효과적인 해결책을 제공한다.

데이터 크기에 대한 Scaling 과 Access 처리량

  • 데이터 크기가 커짐에 따라, 효율적인 저장 및 검색을 보장하기 위해 ‘데이터 파티셔닝’ 방식을 통해 데이터를 여러 storage nodes 또는 servers에 분산하는 것이 필수적이다.
  • JunoDB는 일관된 해싱을 활용하여 고정된 파티션(샤드)을 효과적으로 분할하며, 이러한 샤드는 샤드 맵을 사용하여 물리적인 저장 노드에 할당된다.
  • 클러스터 내 노드 수가 ‘추가’ 또는 ’제거’로 인해 변경될 때는 오직 적은 수의 샤드만 다른 저장 노드로 재할당되어야 한다. 또한, 각 샤드 내에, 마이크로 샤드(micro-shard)를 도입했다. 이 micro-shard는 데이터 재분배의 기본 구성 요소로 작용한다.
  • 전체 샤드 수는 충분히 크고 클러스터의 수명 동안 일정하게 유지되어야만 한다.
  • 실무에서는 일반적으로 1,024개의 샤드를 사용한다. 샤드 맵은 사전에 생성되어 ETCD에 저장된다.
  • 샤드 맵의 변경은 데이터 재분배 처리(data redistribution process)를 일으킨다.
  • 우리의 효율적인 데이터 재분배 과정은 JunoDB 클러스터를 빠르게 점진적으로 확장하여 트래픽 성장에 대응할 수 있게 한다.
  • 현재, 대규모 JunoDB 클러스터는 200개 이상의 저장 노드로 구성될 수 있다. 이것은 하루에 1000억 건 이상의 요청을 처리한다.

고가용성 (Ensuring Availability)

  • JunoDB storage node들은 논리적인 grid 로 구성된다.
  • 각 열(column)은 zone이고, 각행(row)은 storage group 이다.
  • 데이터는 shard로 partitioned되고 storage group에 할당된다.
  • 저장 그룹 내에서 각 샤드는 quorum protocol 에 기반해서 다양한 zone으로 동기적으로(synchronously) replicated 된다.

quorum protocol 은 저장 그룹 내에서 값에 대한 합의(consensus)를 도출하기 위해 사용한다. 데이터 일관성을 보장하기 위해 다음 2가지 rule을 준수해야 한다.

quorum protocol :

  1. read quorum(R)과 write quorum(W)의 합이 존의 수(N)보다 커야 합니다: W+R > N.
    • 이는 read quorum이 데이터의 가장 최신 버전을 포함하는 노드를 적어도 하나 이상 포함하도록 보장합니다.
  2. write quorum은 존(zone) 수의 절반보다 많아야 합니다: W > N/2.
    • 이는 동시에 두 개의 쓰기 작업이 동일한 키에 대해 수행되는 것을 방지합니다. 일반적으로 PayPal은 5개 zone, read quorum 3, write quorum 3의 구성을 사용합니다.

노드 오작동이 발생하는 경우, JunoDB의 장애 조치 프로세스는 자동적이고 즉각적으로 이루어지므로 리더를 다시 선출하거나 데이터를 재분배할 필요가 없습니다.

프록시는 연결이 끊기거나 읽기 시간 초과를 통해 노드 장애를 감지할 수 있으며, 일반적으로 읽기 시간 초과가 100밀리초 이하로 구성됩니다.

JunoDB는 동일한 행/그룹 내에서 두 번 이상 장애가 발생하지 않는 한 클러스터에서 여러 노드 장애를 견딜 수 있습니다.

또한, 이러한 설계를 통해 다운타임 없이 소프트웨어 또는 OS 업그레이드와 같은 유지보수 목적으로 전체 영역을 오프라인으로 전환할 수 있습니다.

Cross-data center replication between clusters

데이터센터 간 복제도 구현, 각 클러스터의 proxy들 사이의 데이터를 비동기적으로 복제 하도록 해서 구현했다.

Performance at Scale

  • JunoDB는 가장 부하가 높은 시점에서(the most demanding workloads)도 한 자릿수 밀리초의 응답 시간과 원활한 사용자 경험을 유지하도록 해준다.
  • 애플리케이션이 성능 저하 없이 선형 확장성(linear scalability)을 달성할 수 있도록 지원하여, 높은 처리량과 짧은 지연 시간을 제공한다.
  • junodb/juno_performance_bench.md at main · paypal/junodb · GitHub

High security

  • JunoDB 시스템은 ‘전송 중인 데이터’(in transit)와 ‘미사용 데이터’(at rest)를 모두 보호하도록 설계
  • 전송 중 데이터 보안을 위해 TLS 가 활성화 되어 있다.
    • 클라이언트 <–> proxy 사이
    • 다른 데이터 센터에 위치한 proxy 사이(데이터 복제를 할 때)
  • 페이로드 암호화는 클라이언트 또는 proxy 수준(스토리지 서버 외부)에서 수행되어 동일한 데이터가 여러번 암호화되는 것을 방지한다. 이상적으로는, 암호화는 클라이언트 측에서 수행돼야만 하지만, 클라이언트가 실행하지 않는 경우 proxy는 메타데이터 플래그를 통해 이를 감지하고 암호화를 수행한다.
  • 스토리지 서버가 받아서 스토리지 엔진에 저장되는 모든 데이터는 암호화되어 유휴 상태에서의 보안을 유지(maintain security).
  • Key management module
    • TLS, 세션, 암호화 키 배포를 위한 인증서를 관리하고
    • 키 교체(key rotation)를 용이하게 하기 위해 사용된다.

junodb 빌드방법

See Also

  1. https://github.com/paypal/junodb/tree/main

Reference

  1. Unlocking the Power of JunoDB: PayPal’s Key-Value Store Goes Open-Source | by Yaping Shi | The PayPal Technology Blog | May, 2023 | Medium

[컴] 왜 javascript 에서 object key 의 순서는 보장되지 않는가?

 

js object key order

왜 javascript 에서 object key 의 순서는 보장되지 않는가?

간만에 의문이 들어 찾아봤다. 아래 StackOverflow 답변(ref.1)이면 충분할 듯 하다.

정리를 하면,

  • 아직 ECMA-262 에 key 가 보장된다는 이야기가 없다.
  • ES6 spec 에 Object.keys 가 ownPropertyKeys 에 의존한다고 정의했다. 그래서 보장된다고 볼 수도 있다.
  • ref. 3 글을 보면, 여전히 문제는 compatability 인 듯 하다.

from : ES5, https://262.ecma-international.org/5.1/#sec-15.2.3.14

15.2.3.14 Object.keys ( O )

When the keys function is called with argument O, the following steps are taken:

If the Type(O) is not Object, throw a TypeError exception.
Let n be the number of own enumerable properties of O
Let array be the result of creating a new Object as if by the expression new Array(n) where Array is the standard built-in constructor with that name.
Let index be 0.
For each own enumerable property of O whose name String is P
    Call the [[DefineOwnProperty]] internal method of array with arguments ToString(index), the PropertyDescriptor {[[Value]]: P, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}, and false.
    Increment index by 1.
Return array.

If an implementation defines a specific order of enumeration for the for-in statement, that same enumeration order must be used in step 5 of this algorithm.

아래처럼 변경됐다.

from : ES6, https://262.ecma-international.org/6.0/#sec-object.keys

19.1.2.14 Object.keys ( O )

When the keys function is called with argument O, the following steps are taken:

Let obj be ToObject(O).
ReturnIfAbrupt(obj).
Let nameList be EnumerableOwnNames(obj).
ReturnIfAbrupt(nameList).
Return CreateArrayFromList(nameList).

If an implementation defines a specific order of enumeration for the for-in statement, the same order must be used for the elements of the array returned in step 4.

개인적인 결론

아직까지는 object 의 key 순서는 보장되지 않음을 전제로 프로그래밍을 해야 하지 않을까 싶다.

See Also

  1. Does JavaScript guarantee object property order? - Stack Overflow : key 의 순서가 보장된 object 를 원한다면, Map 을 사용하면 된다고 한다.

Reference

  1. Javascript - maintain key order when going from object -> array - Stack Overflow
  2. Property order is predictable in JavaScript objects since ES2015 | Stefan Judis Web Development
  3. Property order is predictable in JavaScript objects since ES2015 : javascript

[컴] 정보이론에서 entropy

information theory

정보이론에서 entropy

자세한 이야기는 ref. 1, 2, 3 을 통해 배우자.

모든 사건이 같은 확률로 일어나는 것이 가장 불확실하다.즉, 랜덤하게 사건이 일어나는 것이 제일 확실하다. 예를 들어, 우리가 동전던지기를 한다고 했을 때도, 한번은 앞면, 다른 한번은 뒷면, 이렇게 계속 ’앞뒤앞뒤’로 나오는 것이 가장 불확실하다.

’엔트로피(entropy)’는 이 불확실한 정도를 이야기 한다. 즉,

  • 사건이 랜덤하게 일어나는 경우
    –> 각 사건이 발생하는 확률이 random하다.
    –> 제일 확실한 경우이다.
    –> 불확실성이 낮다.
    –> entropy 값이 가장 낮다.
  • 사건이 규칙적으로 일어나는 경우
    –> 각 사건이 발생하는 확률이 다 같다.
    –> 제일 불확실한 경우이다.
    –> 불확실성이 높다.
    –> entropy 값이 가장 높게 된다.

entropy 란, 최적의 전략하에서 그 사건을 예측하는 데에 필요한 질문 개수를 의미한다.
이 말은 곧 최적의 전략 하에서 필요한 질문개수에 대한 기댓값을 구하면 된다는 뜻이다.
entropy 가 감소한다는 것은 우리가 그 사건을 맞히기 위해 필요한 질문의 개수가 줄어든다는 이야기다.

위의 사건이 발생하는 경우와 연관지어서 이야기해보면,

  • 사건의 발생확률이 한쪽으로 몰려있다.
    --> 필요한 질문 개수의 평균은 줄어들게 된다.
    --> 적은 질문으로 어떤 사건이 일어날 지 맞출 수 있기 때문이다.
    --> 그 사건을 예측하는데 필요한 질문 개수가 작다

만약 아래와 같은 이산확률분포(discrete probability distribution)가 있다고 하자.

  • A 가 나오는 확률은 50% = 1/2
  • B 가 나오는 확률은 12.5% = 1/8
  • C 가 나오는 확률은 12.5% = 1/8
  • D 가 나오는 확률은 25% = 1/4

그러면, 처음에 나오는 문자가 뭔지를 알려면 몇번의 질문을 해야 할까? 이것에 대한 자세한 설명은 ref. 1 을 보면 된다.

결과적으로, 아래처럼 1개의 글자를 추려내기 위해서는 평균적으로 1.75개의 질문을 하면 된다는 결론이 난다.

\(p(A) \cdot 1 + p(B) \cdot 3 + p(C) \cdot 3 + p(D) \cdot 2 = 1.75\)

각 문자를 판단하기 위해 하는 질문횟수는 다음과 같다.

  • A는 1번 : \(\log_2(\frac{1}{1/2})\)
  • B는 3번 : \(\log_2(\frac{1}{1/8})\)
  • C는 3번 : \(\log_2(\frac{1}{1/8})\)
  • D는 2번 : \(\log_2(\frac{1}{1/4})\)

즉, 아래와 같은 식이 성립한다.

\((사건발생확률) \cdot log_{2}(\frac{1}{사건발생확률})\)

그리고, 이것을 모든 경우에 대한 값을 합산하면 아래와 같이 된다. 이것이 entropy 이다. 이 entropy 를 \(H\)라고 정의하자.

\(\begin{align} H & = \sum (사건\,발생확률) \cdot \log_2(\frac{1}{사건\,발생확률}) \\ & = \sum_i p_i \ \log_2(\frac{1}{p_i})\\ & = - \sum_i p_i \ \log_2(p_i) \end{align}\)

Reference

  1. 초보를 위한 정보이론 안내서 - Entropy란 무엇일까

[컴][머신러닝] Markov Chains 설명

Markov chain / 마르코브 / 마르코프 체인 /

Markov Chains 설명

각 사건의 확률이 이전 사건에서 얻은 상태에만 의존하는 일련의 가능한 사건들을 설명하는 확률적 모델(stochastic model)이다. 비공식적으로 이것은 “그 다음에 무슨 일이 일어날지는 오직 지금 상황에 달려 있다”고 생각할 수 있다.[ref. 4]

See Also

  1. 쿠…sal: [컴] 조건부 확률 설명, conditional probability : '마르코브 체인'은 현재사건의 확률이 이전 사건에서 나온 상태에서 시작한다. 그래서 '조건부확률' 과 같은 뜻이라고 볼 수 있다. $P(A_{2}|A_{1})$ 라고 하면 '$P(A_{1})$ 이 일어난 후, 그 사건을 100%로 놓은 상황에서 $P(A_{2})$ 가 일어날 확률'이 되기 때문이다.
  2. 조건부 확률부터 마르코프까지 - 4) 마르코프 체인: '마르코프체인'과 나이브베이'의 차이를 이야기 해준다. 참고로, 둘다 조건부확률이다.

Reference

  1. Markov Chains explained visually
  2. Examples of Markov chains - Wikipedia
  3. 마르코프 체인 (Markov Chain) 정의 및 예시
  4. Markov chain - Wikipedia

[컴] ko_KR, ko-KR 중 어느것이 맞을까?

 

en_US vs en-US /

ko_KR, ko-KR 중 어느것이 맞을까?

RFC 5646: Tags for Identifying Languages 에서는 -(하이픈) 을 사용한다고 돼있다. RFC 문서의 예제에서도 en-US 를 확인할 수 있다.

en-USIETF language tag - Wikipedia 라고 한다.

java 에서는 표준을 en-US 로 인식하는 듯

ref. 1 에 있는 예제처럼 java Locale 에서도 ‘en_US’ 를 넣으면 Locale 설정이 되지 않는다고 한다.

Locale.forLanguageTag("en-US").toString();
>> en_US
Locale.forLanguageTag("en_US").toString()
>> null

android, ios 에서는 _ 가 표준

ref. 4 에서 보면 android 에서 언어_국가 를 표준처럼 쓰이는 듯 하다.

그런데 이것이 ios 에서도 비슷하다. ref. 5 에 보면 [language designator]_[region designator] 를 확인할 수 있다.

다만 ios 에서는 script designator 라는 개념을 더 사용한다. 그래서 ‘-’(하이픈, hyphen) 도 사용된다. ref. 5 에서 script subtags 에 ISO 15924, ISO/IEC 10646 and Unicode 대한 글을 확인할 수 있다.

References

  1. java - What is the difference between creating a locale for en-US and en_US? - Stack Overflow
  2. java - Should I use _ (underscore) or - (dash) while using locale ID? - Stack Overflow
  3. RFC 5646: Tags for Identifying Languages
  4. Language and locale resolution overview | Android Developers
  5. Language and Locale IDs

[컴][웹] http reqeust timeouts 기본값은?

 

http timeout default value

Http reqeust timeouts 기본값은?

결론적으로 기본값은 없다. client 에서 값을 set 하는 것이라서 사용하는 client library 의 기본값은 있겠지만 protocol 상에서 정해놓은 값은 없다.

다음은 ref.1 의 내용이다.

HTTP 는 client-driven protocol(클라이언트 기반 프로토콜) 이다. 클라이언트가 그것을 요청할 때 서버는 단순히 클라이언트에서 정보를 제공할 수 있다.

서버들이 클라이언트들에게 낮은 laytency 의 알림들을 제공하기 위해 long-polling 의 방식을 이용하는 것이 일반적이다.

long-polling 은 client 가 서버에 대해 "열려있는 request(open request)" 를 유지하는 것이 필요하다. 서버는 관련있는 event 가 발생할때까지 이 request 에 대한 응답(response) 를 보류한다.

open request 는 서버가 이벤트가 발생하자마자 메시지를 보낼 수 있게 해준다. notification을 가지고 있는 response 를 받은 후, 클라이언트는 즉시 다른 open request 를 연다.

long-polling 은 많은 HTTP request 들과 많이 다르다. http requst 들은 request 와 response 사이에 큰 delay 를 가지고 있지 않다. long-polling 요청은 처리가 이뤄지는 동안에 connection 을 독점한다. 이런 이유로 서버 또는 중간자(intermediary)는 long-polling reqeust 를 식별하는 것이 유용하다. long-polling request 를 실벽하는 것은 long-polling reqeust 들에 대한 특별한 resource 관리가 가능하다. 예를 들면, 분리된 연결 할당과 관리, 더 긴 timeout 시간, 그리고 long-poll 최적화 등이 가능하다.

long-polling 에 사용되는 연결도 특정 기간 idle 상태인 것으로 나타납니다. idle connection의 관리는 long-polling 의 작동을 방해할 수 있다. 많은 중개자들은 “클라이언트가 outgoing connection들에 대한 사용을 독점하는 것”을 피하기 위해 적합하게 timeout들을 사용한다. 중개인이 요청에 대해 timeout 이 되어버리면, 서버는 응답을 보낼 수 없다.

reponse을 제공하는 데 가능한 시간을 알면 timeout 문제를 방지할 수 있다. 현재 구현에서는 30초에서 120초 사이의 시간, 즉 경험적으로 안전하다고 판단된 시간을 선택한다. 서버는 이 시간 전에 null response을 제공하여 중개인에게 연결이 active 상태로 나타나도록 한다. 각 null response 및 후속 요청이 트래픽을 생성하므로 timeout 의 값이 낮으면 낭비가 많이 생길 가능성이 있다. 그 response 가 서버가 통지에 사용할 채널을 제거하기 때문에 null reponse 는 또한 notification 들의 latency 를 증가 시킬 수 있다.

Section 3. 에 정의된 Request-Timeout 헤더는 특정 요청에 대한 응답을 제공하는 데 서버가 사용할 수 있는 최대 시간을 갖고 있다.

client library 마다 timeout 값은 다르다

기본적으로 client 에서 timeout 값을 정하는 것이기에 그것은 library 가 정하기 나름이다. 다음 2가지 경우만 봐도 http request 의 timeout 값은 library 마다 다르다는 것을 알 수 있다.

Reference

  1. Hypertext Transfer Protocol (HTTP) Timeouts

[컴] host name 과 domain name 의 차이

host name vs domain name / 호스트 / 도메인 정의 / underbar / underscore / host name 만들기 전에 주의할 점

host name 과 domain name 의 차이

결론을 이야기 하면, domain name 이 좀 더 넓은 범위라거 보면 될 것 같다.

domain name > host name

host name 은 말그대로 host 에 붙이는 name 이라고 할 수 있는데, host 라는 것은 특정 ip address 를 소유한 객체로 보면 될 것 같다.( “RFC 1035 - Domain names - implementation and specification, section-2.3.1 Preferred name syntax” 에서도 host name 을 ip address 와 mapping 되는 객체로 보고 이야기 한다.)

그래서 host name 은 이 특정 ip address 와 mapping 된 이름(name) 이라 할 수 있겠다.

dns 의 table 을 생각하면 쉽다. A record 와 AAAA record 에 쓰이는 것이 host name 이라고 생각하면 된다.

그 외에 CNAME, MX record 들에 쓰이는 domain 들은 host name 이 아니라고 보면 될 것 같다. 이 들은 더 넓은 범주인 domain name 이긴 하다

host name 을 만들때 주의할 점

RFC 1035, 2.3.1 Preferred name syntax” 를 보면, domian name 을 지을 때 다른 조건도 만족할만한 이름을 짓는게 낫다고 이야기 하면서 예시로 main domain 과 host name 이름을 지을때 어떤 조건을 만족하는게 좋은 지 이야기 해주고 있다.

mail domain같은 경우는 domain name 의 조건도 만족해야 하지만 "RFC 822 - STANDARD FOR THE FORMAT OF ARPA INTERNET TEXT MESSAGES" 의 조건도 만족해야 한다.

host name 을 정할 때는 옛날 hosts.txt 에서 사용했던 규칙을 따르라고 한다. 이유는 옛날에 host 의 이름에 대해 사용했던 규칙으로 옛날 프로그램들이 만들어져 있어서 이 규칙에 맞지 않는 host name 은 host name 이 틀렸다고, 규칙에 맞지 않다고 error 를 던질 수 있기 때문이다.

그 규칙이 아래와 같다.

<domain> ::= <subdomain> | " "

<subdomain> ::= <label> | <subdomain> "." <label>

<label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]

<ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>

<let-dig-hyp> ::= <let-dig> | "-"

<let-dig> ::= <letter> | <digit>

<letter> ::= any one of the 52 alphabetic characters A through Z in
upper case and a through z in lower case

<digit> ::= any one of the ten digits 0 through 9

domain name에 대,소문자 사용은 가능하지만, 같은 철자로 된 domain name 이면, 대문자로 된 domain name 이나 소문자로 된 domain name 이나 같은 것으로 취급한다.

label 들은 ARPANET host name rules 을 따라야만 한다. - letter 로 시작해야만 하고 - letter 나 digit 으로 끝나야만 한다. - 그 사이의 문자는 letter, digit, hyphen 만 가능하다. - 길이는 63 자 이하여야 한다.

RFC1123 를 보면, 이전의 label 의 제약 사항중에 letter로 시작해야만 했던것은 digit으로 시작해도 된다고 바뀌었다.

From RFC-1039
     <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
To RFC-1123
      <name>  ::= <let-digit>[*[<let-or-digit-or-hyphen>]<let-or-digit>]

underscore(_) 문제

몇몇 library 들에서 host name 에 _ underscore 가 들어간 경우 문제가 된다. 특히 오래된 library 일수록 그런 경향이 있는 듯 보인다. 그러니 위의 오래된 hostname 규칙을 따르는 것이 낫다.

See Also

  1. HostNamingRules < CF < TWiki : host naming rule 과 관련한 RFC 문서 내용을 모아놓았다.

Refrences

  1. dns - Can (domain name) subdomains have an underscore "_" in it? - Stack Overflow

[컴] RxJS 는 어떤 이득이 있는가

react / reactorx / reactor extension / 원리 / RxJS 에 대한 이해 / frp 의 이득 / benefit / 무엇이 더 나은가 / 무엇때문에 왜 frp 를 쓰는가.

RxJS 는 어떤 이득이 있는가

개인적으로 이 programming model 이 기존의 방법론과 어떤 점이 다른지에 대한 개념을 잡기가 어려웠다. Observable sequence 로 굳이 만들지 않아도 충분히 코드를 짤 수 있다. 그런데 굳이 왜 이것을 사용하는가? 에 대한 의문을 해소하고 싶어서 글을 쓴다.

MS 가 만든 programming model

이 prgramming model 은 ms 에서 처음에 만들어서 (Erik Meijer and his team) open source로 공개했다고 한다. 아래는 관련글이다.

언제 사용할까?

현재까지 판단하기로 이것은 data 를 asynchronous 하게 공급하는 것들을 감싸서 event 를 날리는 어떤 객체로 만들어 쓸 수 있다.

즉 data stream (예를 들면, file read, network request 등)을 만들어 주는 library 라고 보면 될 것 같다. synchrnouse 하게 작업할 때 blocking 이 생길만한 요소가 있다면, 그것을 data stream 으로 만들면 되는 것이다.

data stream

이것을 data stream 이라고 표현하는 이유는, 만약 우리가 event emitter로 구성하면 그것은 callback function 하나를 등록하는 것으로 여길 수 있다. 그런데 stream 이라면 이것은 마치 list 에 대해 loop 을 돌면서 data 를 처리하는 개념으로 이것을 이해하고, 구성할 수 있다.

개념이 달라지기에 code 의 모습도 조금 달라진다. 하지만 결국 특정 event 가 오면 그에 대한 처리를 한다는 점에서는 event-subscribe pattern 과 맥이 닿아 있다. 하지만 모습이 바뀌고, 개념이 바뀌면서 code를 좀 다른 모양으로 할 수 있다. (event listner 를 등록하는 code와 list 를 iterate 하는 code는 사실 data 가 있을때 동작한다는 점은 같지만 다른 모습으로 구현되어 있다. 그리고 그 code를 읽는 우리의 이해도 다르다.)

간략한 코드로 설명

간략하게 code를 보면 아래처럼 myObservable을 만들게 된다. 이렇게 Obserable로 감싸면 그냥 단순하게 data-list 가 된다고 이해하자.(다만 그 data 를 주는 시점은 알수없는)

const myObservable = new rxjs.Observable(subscriber => {
  data = requestData()
  subscriber.next(data);
});

그리고 우리는 이 data-list 에서 data 가 있다고 event 를 줄때 해야 할 일을 subscribe으로 정해놓을 수 있다.

myObservable.subscribe( data => {
    // data 를 가지고 할 일
    console.log(data)
})

개념

  • Bridging to Callbacks · rxjs : callback 을 Observable sequence 형태로 변경하고, Observable sequence 를 callback 형태로 변경하는 예제를 확인할 수 있다.
  • Bridging to Promises · rxjs : Promise 을 Observable sequence 형태로 변경하고, Observable sequence 를 Promise 형태로 변경하는 예제를 확인할 수 있다.

이 Rxjs 를 이해하기에는 ref.1 의 글이 가장 좋았다.

이 rxjs 를 iterable pattern 을 구현하는 것에 대입해보자. iterable pattern 은 간단하게 생각하면 array 처럼 traversing 할 수 있도록 만들어주는 것이다. 실제로 data 가 있는데, 이것의 traverse 방법을 정의해주는 것으로 보면 될 것 같다.

이 때 우리는 next() 함수를 만들어서 그 다음의 element(value) 를 가져오게 된다. observer.next() 를 이 next() 와 같은 느낌으로 보면 이해하면 된다. 이 observer.next() 를 호출하는 시점에 특정한 value 를 “subscribe 시점에 등록해 놓은 callback 함수”에 그 value 를 전달해 주는 것이다.

const myarr = []
for(let i=0; i<myarr.length; i++){
  console.log(myarry[i])
}
class MyIterator{

  private data: {value: number, index: number}[]
  constructor(data){
    this.data = data
  }

  public next(): any{
    while(this.cursor < this.data.length){
      const curdata = this.data[this.cursor]
      this.cursor++
      if(curdata.index % 2 === 0){
        return curdata
      }
    }
  }

  public hasNext(): any{
    const cursor0 = this.cursor
    while(cursor0 < this.data.length){
      const curdata = this.data[cursor0]
      cursor0++
      if(curdata && curdata.index % 2 === 0){
        return true
      }
    }
    return false
  }

}

const miter = new MyIterator([{index:0, value: 1}, {index:1, value: 10}, {index:2, value: 100}, {index:3, value: 1000}]);

while(miter.hasNext()){
  console.log(miter.next())
}

이제 우리가 자주 쓰는 ajax 로 server에서 data 를 requst하는 부분을 봐보자. 대체로 아래와 같은 모습을 하고 있다.

var request = new XMLHttpRequest();
request.open('GET', url);
request.onload = () => {

    if (request.status === 200) {
        // do something
        console.log('success')
    } else {
        console.log(new Error(request.statusText))
    }
};

request.send();

아래처럼 observable 로 만들 수 있다. (ref. 1 참고)

const request = Observable.create((observer: Observer<any>) => {

    var request = new XMLHttpRequest();
    request.open('GET', url);
    request.onload = () => {

        if (request.status === 200) {
            observer.next(request.responseText);
            observer.complete();
        } else {

            observer.error(new Error(request.statusText));
            observer.complete();

        }

    };

    request.send();

});

request.subscribe(function onSuccess(text: string){
    console.log(text)
})

data 를 얻어오는 대리자(observerable)가 존재한다.

위의 코드에서 observer.next() 를 호출하면 우리가 subscribe 에 등록한 callback 함수를 호출하게 된다.(see also. 1 참고) 이 observer.next() 를 호출하는 주체는 Observable 이다.

Functional Reactive Programming(FRP) 에서는 observable.subscribe() 이 iterable 의 next() 의 역할을 한다고 보면 될 듯 하다. iterable 에서는 직접적으로 next()를 호출했다면, FRP 에서는 subscribe 를 한번 거쳐서 next() 를 호출하는 차이가 있다.

어쨌든 결과적으로 subscribe을 호출하면, iterable.next() 를 호출하는 것처럼, data 가 넘어오게 된다. 그러면 그것에 대한 처리를 해주면 된다.

조금 다른 점은 iterable 은 synchronous 하게 next() 로 값을 얻어온 후, 그 다음 우리가 필요한 작업(operation) 을 바로 수행하게 된다. 즉, next() 가 synchronous 하게 동작하는 것을 전제로 programming 을 작성한다.

그런데 subscribe 을 호출하면, 이것은 우리가 직접적으로 next() 를 호출하는 것이 아니기에, 실제로 값을 전달해주는 next() 가 언제 호출될 지 모른다. 바로 전달될 수도 있지만, 한참있다가 전달될 수도 있다. 그래서 우리는 asynchronous 하게 처리하기 위해 직접 subscribe 을 호출한 후 return 값을 받아서 값을 처리하지 않고, callback function 을 전달한다.

FRP 가 data traversing 의 변경이 조금 더 수월하다.

우리는 이 next() 가 호출되기 위해서는 observable.subscribe() 을 호출해야 한다. 즉 직접적으로 next() 를 호출하지 않는다. 그래서 우리는 iterable pattern 에서처럼 직접적으로 next() 를 호출하지 않고, next() 를 호출해 주기 위해 그저 subscribe할 뿐이다.

iterable pattern 에서는 우리가 직접 next() 가 어떻게 동작하는지에 대한 code를 작성했다. FRP 에서는 이 부분을 우리가 observable 에 작성한다고 보면 된다.

이 observable 에 이것을 작성해 놓으면, obserable 에서 제공하는 다양한 함수들(operation) 을 이용해서 observer.next() 를 호출하기 전에 조작이 가능하다. 예를 들면, 특정 범위에 대해서만 observer.next()를 호출하거나, 일정시간후(delay)에 observer.next()를 호출하는 등이 가능해 진다.

즉 next() 를 호출하기 전에, 우리가 만든 observable 이 전달하는 data 의 모습을 더 쉽게 변경할 수 있다. iterable 에서는 next() 를 직접호출하기 때문에, next() 를 직접 수정해서 어떤 data 를 넘겨줄 것인가를 정하게 되지만(또는 ’상속’등을 통해서 next()를 override하거나), FRP 에서는, Observable 이 제공하는 operation 등을 통해서, 어떤 iterable 을 하는 조건을(예를 들면, filtering 이나, delay 등) 쉽게 적용할 수 있도록 해주는 것이다.(see also 2 참고)

iterable pattern 이나 FRP 나 data traverse 를 어떻게 할 것인가 대해서는 똑같이 user 가 직접 작성한다. 하지만 FRP 는 이부분 중간에 layer를 하나둬서, user가 작성한 data traverse 를, 또는 data 자체의 모양을, 좀 더 쉽게 변경할 수 있도록 해놓은 것이라고 보면 될 것 같다.

code를 보면 이해하기 더 쉬울수 있다. observable 에 우리가 subscribe을 호출하는데, chaining을 통해서 이 observable 를 다른 모습으로 변경시키고, 그것을 우리가 subscribe 하기 때문이다. (see also 2 를 보면 이해가 더 쉬울 듯 하다.)

아래 코드를 보면, myObservable2 은 순수한 rxjs.Observable 이 아니다. rxjs.Observable 뒤에 .pipe() 에 의해 변형된 Observable 이다. 우리는 이 변형된 Obeservable 에 subscribe 을 하게 된다.

const myObservable2 = new rxjs.Observable(proxyObserver => {
    console.log('Observalbe');
    proxyObserver.next(42);
    proxyObserver.next(100);
    proxyObserver.complete();
  }
).pipe(
  rxjs.operators.observeOn(rxjs.asyncScheduler)
);


myObservable2.subscribe({
    next(x){
        console.log(x)
    },
    ...
});

See Also

  1. 쿠…sal: [컴] Rx.js 에서 Observable.subscribe 의 동작
  2. 쿠…sal: [컴] Rx.js 에서 새로운 스케쥴러를 사용하는 경우
  3. The introduction to Reactive Programming you've been missing · GitHub : 좋은 글, 이해가 쉽다.

Reference

  1. RxJs — An Introduction in TypeScript — Part 1 | by Yarlen Mailler | The Startup | Medium

P