웹브라우저 기반 IPFS 네트워크에 관심이 많습니다.
웹브라우저만으로 IPFS 네트워크를 구축할 수 있다면, 서버 없는 서비스(웹3 서비스)가 가능해질 것입니다.
IPFS 블로그(https://blog.ipfs.tech/)에서 이와 관련된 기사를 엄선하여 요약합니다.

The State of Dapps on IPFS: Trust vs. Verification

https://blog.ipfs.tech/dapps-ipfs/
2024-01-29

  • SPA 또는 MPA 형태로 개발된 Dapp은 IPFS로 쉽게 배포할 수 있다
  • Helia는 브라우저를 IPFS 노드로 만들어주는 라이브러리다
  • Helia가 브라우저에서 제공하는 기능은 'CID 데이터 관리'와 'CID 데이터에 대한 Verified Retrieval' 2가지다
    • CID 데이터 관리: 데이터를 CID 데이터로 만들고 해석하는 기능을 제공한다
    • CID 데이터에 대한 Verified Retrieval: CID로 지정된 데이터를 Bitswap 또는 IPFS Gateway를 통해 가져올 수 있다
    • 브라우저 IPFS 노드는 보통 수명주기가 짧기 때문에, CID 데이터를 업로드할 때는 1) pinning 서비스를 이용하거나, 2)직접 운영하는 IPFS 노드(서버)를 이용하는 것이 좋다

 

Verified IPFS Retrieval in Browsers with @helia/verified-fetch

https://blog.ipfs.tech/verified-fetch/
2024-04-18

  • IPFS Gateway는 브라우저에서 IPFS 데이터를 가져올 때 특히 유용한 기술이다
  • IPFS 데이터에 대한 검증(Verification: IPFS Gateway가 전송한 데이터가 내가 요구한 그 데이터가 맞는가에 대한 검증)을 브라우저가 수행한다면, 브라우저는 IPFS Gateway를 신뢰하지 않더라도 문제 없이 이용할 수 있다 (Trustless Gateway 사용이 가능하다)
  • 이를 위해 Interplanetary Shipyard팀(프로토콜랩으로부터 분사한 개발조직)이 @helia/verified-fetch 라이브러리를 개발/배포한다
  • Shipyard팀의 다음 목표는 WebRTC와 WebTransport 프로토콜을 이용해서 브라우저에서 직접 Kubo IPFS 노드와 통신하는 것이다

 

IPFS on the Web in 2024: Update From Interplanetary Shipyard

https://blog.ipfs.tech/2024-shipyard-improving-ipfs-on-the-web/
2024-11-25

  • 우리(Interplanetary Shipyard)가 관심을 갖는 주제는 웹에서 IPFS를 사용하는 것이다
  • 다시 말해 웹브라우저에서 다른 IPFS 노드에 연결할 수 있게 만드는 것이다
  • 이를 위해 다음과 같은 프로젝트를 진행하고 있다
    • Verified Fetch: 브라우저의 fetch API와 유사한 API를 제공, 이를 통해 IPFS 데이터를 검증/수신하는 기능을 제공한다
    • Browser Transport: 브라우저에서 사용할 수 있는 WebRTC와 WebTransport 프로토콜을 기반으로 외부 IPFS 노드와 통신하는 기능을 제공한다
    • AutoTLS: 브라우저는 보안 통신을 위해 CA 공인 인증서를 요구하나 IPFS 노드는 통상 인증서 없이 운영된다. AutoTLS는 이 갭을 메꾸는 기능을 한다
    • Delegated Routing: 브라우저가 IPFS 기능을 호출할 때 이용할 수 있는 https://delegated-ipfs.dev/routing/v1 엔드포인트를 프로토콜랩이 운영/제공한다

 

Browser P2P Connectivity with WebRTC and js-libp2p

https://docs.libp2p.io/guides/getting-started/webrtc/
2024-06-12

  • 브라우저 p2p 커넥션을 만들려면 서버의 지원이 필요하다
    • STUN: 브라우저 노드의 public ip를 알아내기 위해 필요하다
    • TURN: 공개 IP가 부여된 서버가 브라우저-to-브라우저 통신을 중계한다 (서버 비용이 든다. 이 글에서는 TURN 서버 대신 GossipSub 프로토콜 이용을 추천한다)
    • Signaling: libp2p의 WebRTC 통신 초기 연결을 위해서 필요하다
    • Libp2p relay: 브라우저 노드도 PubSub 프로토콜(GossipSub)을 이용하면 서로 연결될 수 있다 (데모 용도로는 좋으나 배틀 테스트를 거치지 않아서 제품 용도로는 신뢰할 수 없다)
  • GossipSub 프로토콜을 이용한 브라우저 간 libp2p 통신 수립 가이드 (실제 동작하는 데모, 강추!)
    • 스텝1: 소스레포 복사 및 라이브러리 설치
    • 스텝2: js-libp2p node.js relay 실행
    • 스텝3: 브라우저에서 js-libp2p 실행
    • 스텝4: 브라우저에서 relay로 연결
    • 스텝5: Circuit Relay를 이용, 브라우저를 dialable하게 만들기
    • 스텝6: relay를 브라우저 앱의 부트스트랩 피어로 설정
    • 스텝7: WebRTC를 listen하여 direct connection 만들기
    • 스텝8: PubSub 피어 찾기

 

결론
1)인푸라 등에서 제공하는 무료 IPFS 노드와 2)Helia 라이브러리와 3)PubSub(GossipSub) 프로토콜을 이용하면 비용 없이 브라우저-to-브라우저 IPFS 네트워크를 구축하는 것이 가능합니다.
다시 말해, 비용 없이 웹3 서비스를 실현할 수 있습니다. 이제 필요한 건 당신의 상상력입니다.

Posted by ingeeC
,

IPFS Camp 2024 요약

Dev 2024. 12. 12. 11:24

유튜브 세션 모음을 요약합니다.
벨기에 브뤼셀에서 있었던 IPFS Camp 2024 행사 동영상입니다.

3줄 요약

  • IPFS 기술에 대한 이야기보다 블록체인 응용사례에 대한 이야기가 많았다 (다소 실망)
  • 개발 업체가 망했다가 오픈소스로 되살아난 '탈중앙화 인증기술, UCAN'과 개발 활동이 멈췄다가 되살아난 '탈중앙화 DB, OrbitDB'가 흥미로왔다
  • 트위터의 대안 서비스 'BlueSky'가 널리 쓰이는 것 같았다 (BlueSky 계정 소개가 많았다)

TOP3 세션

The State of IPFS in JS

  • 12분 동영상
  • kubo(IPFS go 구현체)와 helia(IPFS js 구현체) 개발팀이 PL에서 Interplanetary Shipyard로 분사했다 (후원 바란다)
  • helia의 성능 지표들이 kubo와 비교해서도 양호하다 (helia를 써라)
  • OrbitDB가 돌아왔다 (한때 개발이 중단됐다가 다시 재개됐으며, Helia를 기반으로 한다)

Decentralised Super app. Local First + Blockchain

  • 17분 동영상
  • 중앙 서버에 의존하는 소셜네트워크는 검열을 피할 수 없다 (Post Cloud 기술에 기반한 소셜네트워크가 필요하다)
  • 오프라인에서도 동작하는 소셜네트워크 데모가 좋았다 소스레포

What's New in UCAN 1.0

  • 22분 동영상
  • Fission 회사가 망했어도 UCAN 스펙은 커뮤니티를 기반으로 지속되고 있다
  • JWT와 UCAN의 관계를 청산해서 문제를 단순화시켰다 (JWT 포맷 대신 IPLD를 이용한다)
  • 관련 자료: Ucanto 소스레포, Ucan 워킹그룹

기타등등

그밖에

  • 과학 분야의 대용량 데이터 스토리지 운영 사례,
  • 게이밍 분야의 블록체인 응용 사례,
  • 법률 분야의 분쟁 해소 자동화 사례
    등이 발표됐으나 흥미롭지 않았다.

(이상입니다)

Posted by ingeeC
,

https://www.youtube.com/playlist?list=PLfW9my7NCey-y5_j6QGCtGoigQuVlZ3Bj

3줄 요약

  • IPFS는 캐즘을 넘는 중이다
  • 중국 발표자가 많았다 (국가 규제를 극복하기 위한 서비스를 실험하는 것 같았다)
  • CAR 파일 포맷과 UCAN 인증 기술에 대한 언급이 많았다

 

TOP3 세션

The State of Helia: IPFS in the Browser

 

IPFS + Browsers: Synergistic Decentralization for the Masses

  • https://www.youtube.com/watch?v=GR-SvYSrslE&list=PLfW9my7NCey-y5_j6QGCtGoigQuVlZ3Bj&index=12
  • 2023-11-23, 25분 동영상
  • 발표자는 브레이브 브라우저 개발자
  • 브레이브가 웹3 브라우저인 이유
    • 멀티체인 월렛이 내장되어 있다
    • ENS/ UD/ SNS/ IPNS 도메인 해석 기능이 내장되어 있다
    • 사용자에게 BAT 인센티브를 준다
    • IPFS node가 내장되어 있다
    • IPFS URL을 HTTP IPFS gateway로 맵핑하는 기능을 기본으로 제공한다

 

Event based Mutability on IPFS

 

기타등등 세션

Opening Session with Boris Mann

 

Privacy-Focused User Access for Consumer dApps with Vijay Krishnavanshi

Web3 서비스 아키텍처

 

Juan Benet of Protocol Labs

 

Working IPFS Systems: A Love/War Story with Hannah Howard

 

Transitioning from Curiosity to Commodity - Mainstreaming IPFS

 

Modelling the Scientific Record as a DAG with Edvard Hübinette

 

A decentralized social graph made with IPNS with Guo Liu

 

Dataverse Computer with Qibing Li

 

Saturn — A new Web3 CDN built on IPFS and Filecoin with Ansgar Grunseid

 

Structured Data with IPLD with Carson Farmer

 

Debox - Enabling More Decentralized Storage Acceptance Among Web2 Users

 

What is Content Addressing & Why is it the 2nd Best Thing in the World?

 

IPFS over Storj Backend

 

Cars and Other Forms of Transportation

 

Boom!

 

Infinite Compression, Zero Knowledge Auth and Merkle Proofs

 

Iroh - Take IPFS to Millions of New Places

Iroh 네트워크 솔루션 소개

 

IPVM - Bringing Wasm-Based Edge Compute to IPFS

 

Indexing Co and The Road to Data 3.0

 

Deploy a dApp to IPFS in less than 15 minutes

 

IPFS Connect Istanbul 2023 Recap Video

 

(이상입니다)

Posted by ingeeC
,

FedCM API (Federated Credential Management API)

개요

  • 웹에서의 사용자 인증을 표준화/간소화하기 위한 기술
  • 브라우저가 idP를 통한 인증을 주관하여 '제3자 쿠키'에 의한 사용자 추적을 방지 (프라이버시 강화)
  • 브라우저가 사용자 identity 정보를 관리하여 보안성 강화
  • FedCM API는 브라우저 벤더가 개발하여 브라우저에 탑재하는 것

 

표준화 현황

 

브라우저 지원 현황

  • Can I Use FederatedCredential Now?
  • 일부 브라우저 미지원 (그럼에도 불구하고 전세계 사용자의 75%를 커버)
  • Chrome, Edge, Android 브라우저가 지원 (Safari, Firefox, iOS 브라우저가 미지원)

 

FedCM API를 이용한 인증 Flow

  • OAuth2/OIDC의 'authZ code flow'와 비슷
  • RP와 idP 간의 리다이렉션이 제거됨 (그래서 오류 가능성과 정보 유출 가능성이 낮아짐)

 

원 모어 띵: navigator.credentials.get() 메소드

  • 브라우저에서 일어나는 인증 과정의 시작점
  • get() 메소드에 전달하는 인자만 바꾸면 id/pswd 인증, 소셜 로그인 인증, 패스키 인증, FedCM 인증을 일관되게 처리 가능
  • 다양한 인증 기술들이 제안되고, 실험되다가 종국에는 브라우저로 수렴되는 느낌 (브라우저는 웹3 세계의 OS)

 

Posted by ingeeC
,

서론

JavaScript로 메모리에 직접 접근할 수 있을까?
메모리에 직접 접근한다는 말의 의미를 다음과 같이 정의한다면, 가능하다.

  1. 원하는 크기의 메모리 블록을 할당할 수 있다.
  2. 그리고 해당 메모리 블록을 원하는 방식으로 잘라 데이터를 읽거나 쓸 수 있다.

 

메모리 할당

할당된 메모리를 표현하는 JavaScript 객체는 ArrayBuffer다.
다음 코드로 원하는 크기의 메모리를 할당할 수 있다.

const buffer = new ArrayBuffer(1024) // 1024 바이트의 메모리를 할당

 

메모리에 데이터 읽기 쓰기

할당된 메모리는 View를 통해서 조작한다. View는 ArrayBuffer가 할당한 메모리 블록을 들여다보는 창이다. View 기능을 위해 TypedArray 객체가 사용된다. JavaScript 랭귀지가 지원하는 TypedArray에는 Int8Array, Uint8Array, Int16Array, Uint16Array 등이 있다. TypedArray는 단지 ArrayBuffer를 들여다보는 창일뿐이다. ArrayBuffer를 기반으로 한 TypedArray 객체를 여러 개 생성해도 메모리 중복이 발생하지 않는다 (낭비되지 않는다). 여러 개의 TypedArray 객체가 동일한 메모리 블록을 들여다볼 뿐이다.

const buffer = new ArrayBuffer(1024)      // 1024 바이트 메모리 할당
const byteArray = new Uint8Array(buffer)  // buffer를 바이트 배열로 다루는 view 생성
byteArray[0] = 1          // 1 바이트 메모리 쓰기
const data = byteArray[0] // 1 바이트 메모리 읽기

 

ArrayBuffer와 TypedArray 사이의 변환

ArrayBuffer와 TypedArray 사이의 변환은 숨 쉬는 것만큼 쉽다.

const buffer = new ArrayBuffer(1024)      // 1024 바이트 메모리 할당
const byteArray = new Uint8Array(buffer)  // ArrayBuffer로부터 TypedArray 생성
const buffer2 = byteArray.buffer          // TypedArray로부터 ArrayBuffer 획득
console.log(buffer === buffer2)           // true
  // TypedArray로부터 획득한 버퍼가 원본 버퍼와 같은 것인지 확인

그래서 crypto 객체 같은 데이터 처리 모듈은 처리할 데이터를 인자로 받을 때, ArrayBuffer와 TypedArray를 구분하지 않고 모두 허용한다.

const buffer = new ArrayBuffer(1024)      // 1024 바이트 메모리 할당
const byteArray = new Uint8Array(buffer)  // ArrayBuffer로부터 TypedArray 생성
// (생략) 메모리 블록에 데이터 채우기
// (생략) 암호화에 필요한 iv, key 확보
const encryptedResult = await window.crypto.subtle.encrypt(
  { name: 'AES-GCM', iv }, key,
  buffer
)   // OK
const encryptedResult2 = await window.crypto.subtle.encrypt(
  { name: 'AES-GCM', iv }, key,
  byteArray
)   // OK

 

요약

메모리에 직접 접근하는 작업을 위한 전통적인 랭귀지는 C/C++였다. 이제 JavaScript로도 메모리에 직접 접근하는 작업이 가능하다. 랭귀지 사이의 우월을 논하는 것이 점점 의미 없어지고 있다. 어쩌면 이는 JavaScript의 성능과 활용도가 크게 향상됐음을 의미한다. 수많은 기계에 이미 탑재되어 있는, JavaScript를 지원하는 웹브라우저야말로 웹3 세상을 여는 강력한 플랫폼이 될 것이다.

 

Ref.

  1. JavaScript 메모리 특강
  2. 만화로 소개하는 ArrayBuffer 와 SharedArrayBuffer

Posted by ingeeC
,

한국 Mozilla Hacks에 올렸던 글을 편집해서 복사합니다.

한국 모질라 기술 블로그의 상태가 회복되기를 기원합니다.

 

---

Atomics 를 이용해서 SharedArrayBuffer 레이스 컨디션 피하기

 

이 글은 3부작 시리즈의 세번째 글입니다.

  1. 메모리 특강
  2. 만화로 소개하는 ArrayBuffer 와 SharedArrayBuffer
  3. Atomics 를 이용해서 SharedArrayBuffer 레이스 컨디션 피하기

 

지난 글에서, 저는 SharedArrayBuffer 를 사용할 때 레이스 컨디션이 발생할 수 있다고 이야기했습니다. 레이스 컨디션 때문에 SharedArrayBuffer 는 다루기 어렵습니다. 그래서 우리는 어플리케이션 개발자가 SharedArrayBuffer 를 직접 사용하리라고 생각하지 않습니다.

하지만 다른 랭귀지를 사용해서 멀티스레드 프로그래밍을 해 본 경험이 있는 라이브러리 개발자라면 새로 만들어진 이 저수준(low-level) API를 이용해서 고수준(higher-level) 도구를 만들 수 있을 것입니다. 그러면 어플리케이션 개발자들은 SharedArrayBuffer 나 Atomics 를 직접 건들지 않고도 이렇게 만들어진 도구를 이용할 수 있을 것입니다.

 

'SharedArrayBuffer + Atomics' & 'JS 라이브러리 + WebAssembly'의 계층 구조

 

당신은 대부분의 경우 SharedArrayBuffer 와 Atomics 를 직접 사용하지 않을 것입니다. 그래도, SharedArrayBuffer 와 Atomics 의 동작 방식을 이해하는 것은 여전히 재미 있는 일입니다. 그래서 이번 글을 통해, 발생 가능한 레이스 컨디션의 종류와 Atomics 을 이용해서 이를 해결하는 방법에 대해 설명하려고 합니다.

우선, 레이스 컨디션이 뭐죠?

 

두 스레드가 메모리를 향해 달려간다 (racing)

 

레이스 컨디션 (Race conditions): 전에 한번 했던 얘기

레이스 컨디션은 당신이 2개의 스레드 사이에서 변수를 공유할 때 발생합니다. 여기 한 스레드가 파일을 읽고 다른 스레드는 그 파일이 존재하는지 체크하는 경우가 있다고 가정해봅시다. 두 스레드는 통신을 위해 fileExists 변수를 공유합니다.

우선, fileExists 변수의 초기값을 false 로 설정하기로 합시다.

 

두 스레드가 어떤 코드를 실행한다. 스레드1은 fileExists 변수가 true 이면 파일을 로딩하고, 스레드2는 fileExists 변수를 설정한다.

 

Thread 2 의 코드가 먼저 실행되는 한, 그 파일은 읽혀질 것입니다.

 

스레드2가 먼저 실행되면 파일 로딩은 성공한다

 

하지만 Thread 1 의 코드가 먼저 실행되면, log 구문이 사용자에게 에러 문구를 출력해서 파일이 존재하지 않는다고 알릴 것입니다.

 

스레드1이 먼저 실행되면 파일 로딩은 실패한다

 

하지만 이것은 큰 문제가 아닙니다. 파일이 존재하지 않는 것은 아니니까 어떻게든 해결할 수 있습니다. 정말 문제가 되는 것은 레이스 컨디션입니다.

방금 예를 든 종류의 레이스 컨디션은 싱글스레드 코드를 작성하는 JavaScript 개발자들도 겪는 문제입니다. 이런 종류의 레이스 컨디션은 멀티스레드 프로그래밍이 아닐 때도 발생합니다.

하지만, 싱글 스레드 코드에서는 발생하지 않는 종류의 레이스 컨디션이 있습니다. 그런 종류의 레이스 컨디션은 스레드를 여러 개 사용할 때, 그리고 그 스레드들이 메모리를 공유할 때 발생합니다.

 

다른 종류의 레이스 컨디션과 Atomics 를 이용한 문제 해결

이제 멀티스레드 코드에서 발생할 수 있는 몇 가지 레이스 컨디션들을 살펴 봅시다. 그리고 Atomics 를 이용해서 레이스 컨디션 문제를 해결하는 방법을 알아 봅시다. 이 글은 레이스 컨디션의 모든 것을 설명하지 않습니다. 다만 Atomics API 를 제공하는 이유를 설명하는 것이 목적입니다.

시작하기 전에, 한번 더 이야기 하고 싶습니다. Atomics 를 직접 사용하지 마세요. 멀티스레드 코드를 작성하는 것은 익히 알려진 바대로 힘든 일입니다. 대신, 믿을 수 있는 라이브러리들을 이용하세요. 그러면 멀티스레드 상황에서 메모리를 공유할 때 발생하는 문제에 적절히 대응할 수 있습니다.

 

주의!

 

그러면 시작합니다.

 

단일 연산에서의 레이스 컨디션

당신이 2개의 스레드를 가지고 어떤 변수를 증가시킨다고 가정해봅시다. 당신은 아마도 어떤 스레드가 먼저 실행되든 상관 없이 결과가 동일할 거라고 생각할 것입니다.

 

두 스레드가 교대로 한 변수의 값을 증가시킨다

 

하지만, 소스 코드에서 변수를 증가시키는 연산이 단일 연산처럼 보이는 이 경우도, 컴파일된 코드를 살펴보면 단일 연산이 아닌 것을 알게 될 것입니다.

CPU 레벨에서 보면, 어떤 값을 증가시키기 위해 3개의 명령을 실행해야 합니다. 왜냐하면 컴퓨터가 롱텀 메모리(long-term memory)와 숏텀 메모리(short-term memory)를 사용하기 때문입니다. (이에 대해서는 다른 글에서 자세히 설명하겠습니다).

 

CPU와 RAM

 

롱텀 메모리는 모든 스레드들이 공유합니다. 하지만 숏텀 메모리 (즉, 레지스터)는 스레드들이 공유하지 않습니다.

각 스레드는 롱텀 메모리로에서 값을 읽어와 숏텀 메모리에 저장합니다. 그런 다음, 각 스레드는 숏텀 메모리에 있는 값에 대해 연산을 수행합니다. 그리고 나서 각 스레드는 숏텀 메모리의 결과 값을 롱텀 메모리에 옮겨 저장합니다.

 

변수 값이 메모리에서 레지스터로 옮겨져서, 연산을 수행한 다음, 다시 메모리로 옮겨진다

 

만약 Thread 1 의 모든 연산이 완료되고, 그 다음 Thread 2 의 모든 연산이 실행된다면, 우리는 원하는 결과를 얻게 될 수 것입니다.

 

한 스레드의 모든 명령이 실행된 다음 다른 스레드의 명령이 실행되는 플로우 챠트

 

하지만 각 스레드의 연산들이 섞여서 진행되면, Thread 2 가 롱텀 메모리에서 연산이 마무리되지 않아 올바르지 못한 값을 레지스터로 가져오게 됩니다. 이는 Thread 2 가 Thread 1 의 연산 결과를 반영하지 못하게 됨을 의미합니다. 그 결과, Thread 2 가 Thread 1 의 롱텀 메모리 저장 값을 덮어쓰게 됩니다.

 

두 스레드의 명령이 뒤섞여 실행되는 플로우 챠트

 

Atomics 연산이 하는 일들 중 하나가 이처럼 사람들은 단일 연산이라고 생각하지만 컴퓨터에서는 여러 개의 명령들로 수행되는 연산을 컴퓨터도 단일 연산으로 취급하게 만드는 것입니다.

이것이 Atomics 연산입니다. Atomics 연산은 여러 개의 명령들을 하나의 작업으로 정의합니다. 각 명령들 각각은 잠시 멈춰질 수도 있고 다시 시작될 수도 있지만, 모든 명령들이 한 덩어리로 실행됩니다. 그래서 모든 명령들을 하나의 연산으로 취급할 수 있습니다. 마치 원자(Atom)처럼 더이상 나눠지지 않습니다.

 

하나의 아톰으로 묶여진 명령들

 

Atomics 연산을 이용하면, 변수 값을 증가시키는 코드가 조금 달라집니다.

 

Atomics.add(sabView, index, 1)

 

이제 우리가 Atomics.add 코드를 사용했기 때문에, 변수 값을 증가시키는데 사용된 여러 단계의 연산들이 스레드 사이에서 섞이지 않게 됩니다. 대신, 어느 한 스레드가 Atomics 연산을 수행하는 동안에는 다른 스레드가 관련 연산을 할 수 없게 금지됩니다. 한 스레드가 Atomics 연산을 종료해야 다른 스레드가 자신의 Atomics 연산을 시작할 수 있습니다.

 

아톰으로 묶여진 명령들이 실행되는 플로우 챠트

 

이런류의 레이스 컨디션을 방지하기 위한 Atomics 객체의 메소드는 다음과 같습니다.

이 목록이 무척 제한적이라고 느껴지시나요? 이 목록에는 심지어 나눗셈과 곱셈 연산도 포함되어 있지 않습니다. 하지만 라이브러리 개발자라면, 그런 종류의 유사-Atomics 연산들을 만들 수 있을 것입니다.

유사-Atomics 연산들을 만들려면, Atomics.compareExchange 메소드를 이용해야 합니다. 이 메소드를 이용하면, 당신은 SharedArrayBuffer 에서 값을 가져와서, 가져온 값에 대해 어떤 연산을 수행하고, 다른 스레드가 SharedArrayBuffer 의 값을 변경하지 않은 경우에만 결과 값을 SharedArrayBuffer 에 저장합니다. 만약 다른 스레드가 값을 변경시켰다면, 변경된 값을 가져와서 다시 연산을 시도해야 합니다.

 

여러 개의 연산에 걸친 레이스 컨디션

이런 Atomics 연산은 “단일 연산”에서 레이스 컨디션을 회피하는데 도움을 줍니다. 하지만 종종 당신은 어떤 객체에 속한 여러 개의 변수값들을 (여러 개의 연산을 사용해서) 바꾸길 원합니다. 그리고 다른 연산이 해당 객체를 동시에 수정하지 않도록 보장 받기를 원합니다. 기본적으로, 이것은 객체를 수정하는 모든 구간에서 객체를 잠그고 그래서 다른 스레드가 접근하지 못하도록 막아야 한다는 것을 의미합니다.

Atomics 객체는 이런 상황을 직접 다룰 수 있는 어떤 도구도 제공하지 않습니다. 하지만 Atomics 객체는 라이브러리 제작자들이 이런 상황에 대처할 때 이용할 수 있는 도구들을 제공합니다. 라이브러리 제작자들은 이를 이용해서 Lock 객체를 만들 것입니다.

 

Lock을 공유하는 두 스레드

 

만약 어떤 코드가 Lock 객체로 잠겨진 데이터에 접근하고자 한다면, 해당 코드는 해당 데이터에 대한 Lock 객체의 소유권을 획득해야 합니다. 그러면 해당 코드는 Lock 객체를 이용해서 다른 스레드들이 접근하지 못하도록 막을 수 있습니다. 해당 코드는 Lock 객체를 획들했을 때만 해당 데이터에 접근하거나 해당 데이터를 수정할 수 있습니다.

Lock 객체를 만들기 위해, 라이브러리 제작자는 Atomics.waitAtomics.wake, 그리고 Atomics.compareExchangeAtomics.store 같은 도구들을 이용할 것입니다. 만약 이 도구들이 어떻게 동작하는지 알고 싶다면, 여기 기본적인 Lock 객체 구현 방법을 보세요.

이 경우, Thread 2 는 데이터에 접근하기 위해 Lock 객체를 획득하고 locked 의 값을 true 로 설정합니다. 이는 Thread 2 가 Lock 객체를 해지하기 전에는 Thread 1 이 데이터에 접근할 수 없음을 의미합니다.

 

스레드2가 Lock을 획득하고 공유 메모리를 사용한다

 

만약 Thread 1 이 해당 데이터에 접근해야 한다면, Thread 1 은 Lock 객체를 획득하려고 시도할 것입니다. 하지만 Lock 객체가 이미 사용 중이기 때문에, Thread 1 은 Lock 객체 획득에 실패합니다. 그러면 Thread 1 은 Lock 객체가 사용 가능해질 때까지 기다려야 합니다 (즉 block 됩니다).

 

스레드1은 Lock이 풀릴 때까지 기다린다

 

Thread 2 가 일을 끝내면, Thread 2 는 unlock 함수를 호출할 것입니다. 그럼 lock 함수는 기다리고 있는 스레드를 깨워서 데이터에 접근 가능함을 알릴 것입니다.

 

스레드1은 Lock이 풀렸다는 알림을 받는다

 

깨어난 스레드는 Lock 객체를 획득해서, 데이터를 쓰는 동안 다른 스레드가 접근하지 못하게 막을 것입니다.

 

스레드1이 Lock을 사용한다

 

Lock 라이브러리는 Atomics 객체에 존재하는 여러 메소드들을 이용할 것입니다. 그 중 중요한 것만 고르면 다음과 같습니다.

 

명령어 재배치로 인해 발생하는 레이스 컨디션

Atomics 로 해결해야 하는 3번째 동기화 문제가 있습니다. 이 문제는 놀라울 것입니다.

당신은 아마도 이 문제를 인식하지 못했을 것입니다. 하지만 아주 많은 경우 당신이 작성한 코드는 당신이 기대하는 순서대로 실행되지 않습니다. 컴파일러와 CPU 모두 실행 속도를 높이기 위해 명령어의 호출 순서를 재배치합니다.

예를 들어, 어떤 값들을 더해서 총합을 계산하는 코드를 가정해봅시다. 우리는 계산이 끝나면 플랙 값을 바꿔서 계산이 종료됐음을 표시하려고 합니다.

 

subTotal = price + fee; total += subTotal; isDone = true

 

이를 컴파일하자면, 먼저 각 변수 값을 저장하기 위해 어떤 레지스터를 이용할 지 결정해야 합니다. 그래야 우리는 소스 코드를 기계어 명령어로 바꿀 수 있습니다.

 

주어진 코드와 동등한 어셈블리 표현

 

여기까지는 모든 것이 예측 대로 입니다.

컴퓨터가 칩 레벨에서 어떻게 동작하는지 이해하지 못했을 때 (그리고 CPU 가 명령어를 실행시킬 때 어떤 방식으로 파이프라인을 사용하는지 이해하지 못했을 때) 어렵게 느껴지는 것은, 우리 코드의 2번째 라인이 실행 전에 잠시 기다려야 한다는 사실입니다.

대부분의 컴퓨터들은 명령어 처리 과정을 여러 단계로 잘게 나눕니다. 이렇게 하면 모든 시간에 걸쳐 CPU 의 모든 구성요소가 쉬지 않고 일하게 만들 수 있습니다. 그래서 CPU 활용률을 극대화시킬 수 있습니다.

여기 명령어를 잘게 나누는 방식의 한 예가 있습니다

  1. 메모리로부터 명령어 가져오기
  2. 명령어가 지시하는 의미를 파악하고 (즉 명령어를 디코딩하고), 레지스터로에서 값을 가져오기
  3. 명령어 실행하기
  4. 결과 값을 레지스터에 기록하기

 

파이프라인 스테이지 1: 명령을 가져온다

 

파이프라인 스테이지 2: 명령을 해석하고 레지스터 값을 가져온다

 

파이프라인 스테이지 3: 연산을 실행한다

 

파이프라인 스테이지 4: 결과값을 저장한다

 

그래서 이것이 한 명령어가 파이프라인을 거쳐 실행되는 방식입니다. 이상적으로, 우리는 2번째 명령어가 즉시 실행되기를 원합니다. 2번째 단계 실행을 위해, 다음번 명령어를 가져와야 합니다.

문제는 명령어 #1 과 명령어 #2 사이에 의존 관계가 존재한다는 점입니다.

 

파이프라인에 존재하는 데이터 위험 구간 (data hazard in the pipeline)

 

우리는 명령어 #1 이 레지스터에 존재하는 subTotal 값의 수정을 완료할 때까지 CPU 를 잠시 멈춰야 할 것입니다. 하지만 이렇게 하면 처리 속도가 느려질 것입니다.

좀 더 효율적으로 처리하기 위해, 많은 컴파일러들과 CPU 들은 명령어의 처리 순서를 재배치합니다. 많은 컴파일러들과 CPU 들은 subTotal 또는 total 을 사용하지 않는 다른 명령어들을 찾습니다. 그래서 찾은 명령어들을 앞의 2개 라인 사이로 옮깁니다.

 

어셈블리 코드의 3번째 라인이 1번째 라인과 2번째 라인 사이로 옮겨진다

 

이렇게 하면 파이프를 따라 움직이는 명령어의 흐름이 안정적이 됩니다.

3번째 라인이 1번째 라인이나 2번째 라인의 어떤 값에도 의존하지 않기 때문에 컴파일러 또는 CPU 는 명령어를 이렇게 재배치하는 것이 안전하다고 판단합니다. 싱글 스레드 상황에서 코드를 실행시킨다면, 다른 어떤 코드도 전체 함수가 완료되기 전에는 중간 단계의 이 값들을 보지 못할 것입니다.

하지만 다른 프로세서에서 동시에 실행되는 다른 스레드가 있는 상황에서는 이야기가 달라집니다. 다른 스레드는 함수가 종료될 때까지 기다릴 필요가 없기 때문에 이런 중간 단계의 변화된 값을 보게 됩니다. 변화된 값이 메모리에 기록되자마자 바뀐 값을 보게 될 것입니다. 그래서 해당 스레드는 total 값이 계산되기도 전에 isDone 값이 설정됐다고 인식하게 됩니다.

만약 당신이 isDone 변수를 total 값이 계산됐어 다른 스레드가 사용해도 됨을 알리는 플랙으로 사용했다면, 이런 종류의 명령어 재배치는 레이스 컨디션을 일으킬 것입니다.

Atomics 는 이런 버그를 해결하려고 시도합니다. 당신이 Atomics 를 이용해서 어떤 값을 기록하려고 하는 것은, 당신 코드의 2개 부분에 담장을 두르는 것과 같습니다.

Atomics 오퍼레이션은 명령어 재배치를 허용하지 않습니다. 그래서 다른 명령어가 그 주변에 끼어들지 않습니다. 특히, 순서를 지키도록 강제할 때 다음과 같은 함수를 사용합니다.

소스 코드에서 Atomics.store 구문 이전 위치에서 실행되는 모든 변수값 변경 작업은 Atomics.store 구문이 어떤 값을 메모리에 기록하기 전에 실행되는 것을 보장 받습니다. non-Atomics 명령들의 실행 순서가 재배치되더라도 결코 소스 코드 상에서 아래에 있는 Atomics.store 구문 이후로는 재배치되지 않을 것입니다.

그리고 함수에서 Atomics.load 구문 이후 읽어오는 모든 변수값은 Atomics.load 구문이 값을 읽은 이후 실행되는 것을 보장 받습니다. 다시 한번, non-Atomics 명령들이 재배치되더라도 결코 소스 코드 상에서 위에 있는 Atomics.load 구문 이전으로는 재배치되지 않을 것입니다.

 

Atomics.store와 Atomics.load가 순서를 유지한다

 

Note: 여기서 예시한 while 루프는 스핀락(spinlock)이라고 불리며 매우 비효율적입니다. 만약 스핀락이 메인 스래드에 위치한다면, 당신의 어플리케이션을 비정상 종료시킬지도 모릅니다. 실제 코드에서는 사용하지 않기를 바랍니다.

다시 한번 언급하지만, 이 메소드들은 어플리케이션에서 직접 호출할 메소드들이 아닙니다. 대신, 라이브러리들이 이 메소드들을 이용해서 Lock 객체를 구현할 것입니다.

 

결론

메모리를 공유하는 멀티스레드 프로그램을 만드는 것은 힘듭니다. 거기에는 고려해야 할 매우 다양한 종류의 레이스 컨디션들이 존재합니다.

 

경고: 드래곤이 있음

 

그렇기 때문에 당신은 어플리케이션을 만들 때 SharedArrayBuffer 와 Atomics 를 직접 사용하면 안됩니다. 대신, 멀티스레드 경험이 많고 메모리 모델에 대해 오래 연구한 개발자가 만든 검증된 라이브러리를 이용하는 것이 좋습니다.

SharedArrayBuffer 와 Atomics 는 아직 발표된지 얼마 안됩니다. 그래서 아직 검증된 라이브러리들이 나오지 않았습니다. 그래도 이 새로운 API 들이 그런 라이브러리를 만드는 토대가 될 것입니다.

 

이 글은 Lin Clark이 쓴 Avoiding race conditions in SharedArrayBuffers with Atomics의 한국어 번역본입니다.

한국 Mozilla Hacks에 올렸던 글을 편집해서 복사합니다.

 

Posted by ingeeC
,

한국 Mozilla Hacks에 올렸던 글을 편집해서 복사합니다.
한국 모질라 기술 블로그의 상태가 회복되기를 기원합니다.

 

---

만화로 소개하는 ArrayBuffer 와 SharedArrayBuffer

 

이 글은 3부작 시리즈의 두번째 글입니다.

  1. 메모리 특강
  2. 만화로 소개하는 ArrayBuffer 와 SharedArrayBuffer
  3. Atomics 를 이용해서 SharedArrayBuffer 레이스 컨디션 피하기

 

지난 글에서는, JavaScript 같은 메모리 자동 관리 랭귀지의 메모리 관리 방식을 설명했습니다. 그리고 또 C 같은 메모리 수동 관리 랭귀지의 메모리 관리 방식도 설명했습니다.

ArrayBuffersSharedArrayBuffers 에 대해 이야기 하려고 하는데, 왜 이런 얘기가 필요한 걸까요?

왜냐하면 ArrayBuffers 를 이용하면 JavaScript 를 사용하는 경우에도 데이터를 수동으로 관리할 수 있는 여지가 생기기 때문입니다. JavaScript 랭귀지가 메모리 자동 관리 랭귀지이지만 말입니다.

어떨 때 메모리를 수동으로 관리하고 싶어질까요?

지난 글에서 이야기한 것처럼, 메모리 자동 관리 방식에는 장단점이 존재합니다. 메모리 자동 관리 방식이 개발자가 쓰기에는 편리하지만, 실행 성능 측면에서는 약간의 오버헤드를 감수해야 합니다. 어떤 상황에서는 이런 오버헤드가 문제 될 수 있습니다.

 

저울의 눈금이 '메모리 자동 관리' 방식의 경우, 쓰기 쉽지만 실행 속도를 높이기 어렵다는 사실을 말해준다

 

예를 들어, 우리가 JS 에서 변수를 만들 때, JS 엔진은 변수의 타입과 변수 값의 표현하는 방식을 추측해야 합니다. 이것이 단지 추측이기 때문에, JS 엔진은 보통 변수가 정말로 필요로 하는 것보다 많은 공간을 확보합니다. 변수에 따라, 필요한 공간보다 2~8배 많은 메모리 슬롯(memory slot)을 확보합니다. 상당히 많은 공간이 낭비됩니다.

그리고 JS 객체를 만들고 사용하는 어떤 종류의 패턴은 가비지 컬렉터를 힘들게 만듭니다. 만약 우리가 메모리 수동 관리 방식을 사용한다면, 우리의 유즈케이스(use case)에 꼭 맞는 메모리 할당 정책과 해지 정책을 선택할 수 있습니다.

대부분의 경우는, 이런 일로 고민할 필요가 없습니다. 대부분의 유즈케이스는 메모리 수동 관리 방식을 선택할만큼 실행 성능에 민감하지 않습니다. 일반적인 유즈케이스에서는 메모리 수동 관리 방식의 실행 성능이 오히려 늦을 수도 있습니다.

하지만 우리가 작성한 코드에서 성능 최고치를 뽑아내기 위해 깊숙한 레벨(low-level)에서 일해야 하는 경우, ArrayBuffers 와 SharedArrayBuffers 가 도움 될 것입니다.

 

저울의 눈금이 '메모리 수동 관리' 방식의 경우, 성능을 높일 수 있는 여지는 높지만 더 많은 생각과 계획이 필요하다는 사실을 말해준다

 

그래서 ArrayBuffer 는 어떻게 동작하나요?

기본적으로 ArrayBuffer 는 다른 JavaScript 배열과 비슷하게 동작합니다. 다만, ArrayBuffer 를 이용할 때, 우리는 객체나 문자열 같은 JavaScript 가 제공하는 타입(type)들을 ArrayBuffer 에 저장할 수 없습니다. ArrayBuffer 에 저장할 수 있는 것은 오로지 (숫자로 표현할 수 있는) 바이트열(bytes) 뿐입니다.

 

2개의 배열, 일반 배열(array)은 숫자, 객체, 문자열 등을 담을 수 있고, ArrayBuffer는 bytes만 담을 수 있다

 

여기서 분명히 해둘 것은 우리가 바이트 값을 ArrayBuffer 에 직접 추가하지 않는다는 것입니다. ArrayBuffer 스스로는 바이트열의 크기가 얼마나 큰지, 또 바이트열로부터 어떤 타입의 숫자를 변환해야 하는지 알지 못합니다.

ArrayBuffer 자체는 단지 0 과 1 이 한 줄로 나열된 덩어리일 뿐입니다. ArrayBuffer 는 배열 첫째 요소와 둘째 요소 사이의 구분이 어디에 위치하는지도 모릅니다.

 

1과 0의 덩어리가 한 줄로 나열되어 있다

 

문맥에 맞는 정보를 제공하려면, 그러니까 이 덩어리를 적절한 규격의 박스로 나누려면, ArrayBuffer 를 view 라고 불리는 것으로 감싸야 합니다. 우리는 view 로 표현된 데이터를 typed array (형식화 배열)에 추가할 수 있습니다. JavaScript 는 view 를 다룰 수 있는 다양한 typed array (형식화 배열)를 제공합니다.

예를 들어, 우리는 Int8 typed array (Int8 형식화 배열)를 이용해서 데이터 덩어리를 8-bit 단위의 바이트 값들로 나눌 수 있습니다.

 

8-bit 단위의 상자로 구분된 1과 0

 

또는 unsigned Int16 배열을 이용해서 데이터 덩어리를 16-bit 단위의 바이트 값들로 나눌 수 있습니다. 그래서 나뉘어진 값들을 unsigned integer 값들로 다룰 수 있습니다.

 

16-bit 단위의 상자로 구분된 1과 0

 

심지어 우리는 한개의 버퍼에 여러개의 view 를 적용하는 것도 할 수 있습니다. 적용하는 view 가 달라지면 동일한 연산의 수행 결과도 달라집니다.

예를 들어, 우리가 Int8 view 를 통해 ArrayBuffer 에서 0 번째 요소와 1 번째 요소를 가져오는 경우, 그 값은 Uint16 view 를 통해 가져오는 값과 다를 것입니다. ArrayBuffer 가 완전히 동일한 bit 값들을 갖고 있음에도 불구하고 말이죠.

 

8-bit 단위의 상자와 16-bit 단위의 상자로 구분된 1과 0

 

이런 방식으로, ArrayBuffer 는 기본적으로 메모리 자체인 것처럼 동작합니다. ArrayBuffer 는 C 같은 랭귀지를 사용할 때처럼 메모리를 직접 다루는 방식과 비슷한 효과를 만들어 냅니다.

아마 당신은 프로그래머에게 메모리를 직접 다루는 수단을 주지 않고 이런 추상적인 계층을 만든 이유가 궁금할 것입니다. 메모리에 대한 직접적인 접근을 허용하면 보안 측면에서 헛점이 노출되기 쉽습니다. 다음에 다른 글을 통해 이 주제에 대해 설명하겠습니다.

 

SharedArrayBuffer 는 또 뭔가요?

SharedArrayBuffer 를 설명하기 전에, JavaScript 를 이용한 병렬 처리 코드에 대해 조금 설명해야 할 것 같습니다.

우리는 프로그램을 좀더 빠르게 만들기 위해, 또는 프로그램이 사용자 이벤트에 좀더 빠르게 반응하도록 만들기 위해 병렬 처리 코드를 사용합니다. 이를 위해, 우리는 작업을 분할합니다.

통상적인 어플리케이션에서는 작업을 한 사람(즉, 메인 스레드)이 처리합니다. 제가 예전에 이에 대해 설명한 적이 있는데, 그때 저는 메인 스레드를 풀-스택 개발자에 비유했었습니다. 메인 스레드가 JavaScript 실행, DOM 처리, 레이아웃 처리 등 모든 일을 담당합니다.

메인 스레드의 작업을 보조하는 것이라면 무엇이더라도 작업 효율을 개선합니다. 이런 상황에서는, ArrayBuffer 가 메인 쓰레드의 작업을 보조할 수 있습니다.

 

책상에 앉은 메인 스레드가 서류 더미를 처리하고 있다. 방금 서류 더미의 맨 윗 부분을 처리했다.

 

하지만 언젠가 메인 쓰레드의 작업을 보조하는 것으로 충분하지 않은 때가 옵니다. 무엇인가를 결단해야 하는 순간… 그러니까 작업을 분리해야 하는 순간이 옵니다.

대부분의 프로그래밍 랭귀지들의 경우, 스레드(thread)라는 것을 이용해서 작업을 분할합니다. 이것은 기본적으로 여러 명이 한 프로젝트를 함께 수행하는 것과 비슷합니다. 만약 다른 작업들과 특별히 연관 없는 어떤 작업이 존재한다면, 해당 작업을 별도의 스레드로 처리할 수 있습니다. 그러면, 2개의 쓰레드가 분리된 작업을 동시에 처리합니다.

JavaScript 에서는 이런 일은 web worker 를 이용해서 구현합니다. web worker 는 다른 랭귀지의 스레드와 조금 다릅니다. 기본적으로 web worker 는 메모리를 공유하지 않습니다.

 

2개의 스레드가 나란히 책상에 앉아 있다. 각자의 서류 더미 높이는 예전의 절반이다. 각자의 발밑에 메모리 덩어리가 놓여있다. 2개의 메모리 덩어리는 연결되어 있지 않다.

 

이는 만약 우리가 다른 스레드와 어떤 데이터를 공유하고 싶다면, 그 데이터를 복사해서 전달해야만 한다는 뜻입니다. 이 작업은 postMessage 함수에 의해 처리됩니다.

postMessage 에 어떤 객체를 전달하면, postMessage 함수는 그것을 직렬화해서(serialize) 다른 web worker 에 전달합니다. 그러면 데이터를 전달 받은 web worker 가 데이터를 풀어서(deserialize) 메모리에 복사합니다.

 

스레드1이 스레드2와 메모리를 공유한다. 공유할 메모리를 직렬화(serialize)하고, 전송해서, 스레드2의 메모리에 복사한다.

 

이건 매우 느린 작업입니다.

ArrayBuffer 같은 메모리의 경우, 메모리 전달하기(transferring memory)라는 것이 가능합니다. 메모리 전달하기란 메모리의 특정 블록 소유권을 다른 web worker 로 이전하는 것입니다.

그러면 원래 해당 메모리 블록을 소유하고 있던 web worker 는 더이상 그 블록에 접근할 수 없게 됩니다.

 

스레드1이 메모리 전달하기(trasfer) 방법으로 스레드2에게 메모리를 공유한다. 스레드1은 더이상 그 메모리에 접근할 수 없다.

 

어떤 유스케이스에서는 이 방식이 적절합니다. 하지만 고성능 병렬 처리 코드가 필요한 많은 경우, 우리가 정말 원하는 것은 공유 메모리 (shared memory)입니다.

SharedArrayBuffer 가 바로 이 기능을 제공합니다.

 

두 스레드가 모두 함께 접근할 수 있는 공유 메모리를 얻는다

 

SharedArrayBuffer 를 쓰면 2개의 web work (즉 2개의 스레드) 모두가 메모리의 같은 영역을 읽고 쓸 수 있습니다.

이는 더이상 postMessage 를 쓸 때 감수해야 했던 통신 오버헤드와 시간지연을 겪지 않아도 된다는 뜻입니다. 2개의 web worker 모두가 데이터에 즉시 접근할 수 있습니다.

이렇게 2개의 스레드가 동시에 즉각적으로 접근할 수 있기 때문에 어떤 위험한 상황을 감수해야 합니다. 즉 레이스 컨디션(race condition)이 발생할 수 있습니다.

 

두 스레드가 메모리를 향해 달려간다 (racing)

 

레이스 컨디션에 대해서는 다음 글에서 설명하겠습니다.

 

SharedArrayBuffer 의 현재 상태를 알고 싶은가요?

곧 SharedArrayBuffer 를 모든 주요 브라우저들이 지원할 것입니다.

 

하이 파이브 하고 있는 메이저 브라우저들

 

Safari (Safari 10.1) 는 이미 SharedArrayBuffer 를 지원합니다. Firefox 와 Chrome 은 7월/8월 버전에서 SharedArrayBuffer 를 지원하기 시작할 것입니다. Edge 는 가을에 있을 윈도우즈 업데이트를 통해 SharedArrayBuffer 를 지원할 것입니다.

ArrayBuffer 와 SharedArrayBuffer 가 모든 주요 브라우저들에서 지원되더라도, 어플리케이션 개발자가 ArrayBuffer 와 SharedArrayBuffer 를 직접 이용하지는 않을 것입니다. 사실, 우리는 그러지 않는 것을 추천합니다. 당신은 가능한 가장 높은 수준의 추상화 도구를 선택하는 것이 좋습니다.

우리는 JavaScript 라이브러리 개발자들이 당신을 위해 SharedArrayBuffer 를 쉽고 안전하게 사용할 수 있는 라이브러리를 개발하리라고 기대합니다.

덧붙여, 일단 SharedArrayBuffer 가 플랫폼에 장착되면, WebAssembly 가 이를 이용해서 스레드를 지원할 수 있게 됩니다. 그게 현실이 되면, 우리는 컨커런시(concurrency)를 추상적인 레벨에서 쉽게 사용할 수 있습니다. 마치 Rust 같은 랭귀지처럼요. Rust 랭귀지는 두려움 없이 컨커런시를 쓰는 것을 목표로 하는 랭귀지 입니다.

다음 글에서, 우리는 도구들 (Atomics)을 살펴볼 것입니다. 이 도구들은 라이브러리 개발자들이 레이스 컨디션을 회피하는 용도로 사용하는 것입니다.

 

SharedArrayBuffer + Atomics와 JS 라이브러리 + WebAssembly의 계층 구조

 

이 글은 Lin Clark이 쓴 A cartoon intro to ArrayBuffers and SharedArrayBuffers의 한국어 번역본입니다.

한국 Mozilla Hacks에 올렸던 글을 편집해서 복사합니다.

 

Posted by ingeeC
,

JavaScript 메모리 특강

Dev 2024. 5. 25. 00:06

한국 Mozilla Hacks 기술 블로그에서 번역 활동을 했다. 원체 좋은 글이 많이 올라오는 곳이라서 번역하면서 많이 배웠다. 내가 한 일이라곤 잘 갖춰진 커뮤니티 인프라 위에 숟가락 하나 얹은 것뿐이었다. 그런데 오랜만에 생각나는 글이 있어 살펴보니, 한국 Mozilla Hacks의 현재 상태가 별로 안 좋다. 블로그 기사에서 이미지가 누락되어 안 보인다. 숟가락 하나만 들고 있는 나로서는 할 수 있는 일이 없다. 어쩔 수 없이 Hacks의 글을 편집해서 여기에 복사본을 만든다. 모질라 커뮤니티가 건강하게 오래 가길 빈다.

 

---

메모리 특강

 

이 글은 3부작 시리즈의 첫번째 글입니다.

  1. 메모리 특강
  2. 만화로 소개하는 ArrayBuffer 와 SharedArrayBuffer
  3. Atomics 를 이용해서 SharedArrayBuffer 레이스 컨디션 피하기

 

ArrayBuffer 와 SharedArrayBuffer 가 JavaScript 에 추가된 이유를 이해하려면, 우리는 메모리 관리에 대해 조금 알아야할 필요가 있습니다.

머신에 탑재된 메모리를 상자 더미로 생각해 봅시다. 나는 이것이 사무실에 있는 사서함이나, 유치원에서 있는 사물함과 비슷하다고 생각합니다.

만약 원생들에게 어떤 선물을 남겨주고 싶다면, 우리는 선물을 사물함 상자에 넣어 두면 됩니다.

 

어떤 아이가 사물함 상자에 무언가 넣고 있다

 

각 상자들 옆면에는 숫자가 붙어 있는데, 이것을 메모리 주소라고 부릅니다. 그것이 바로 선물을 어디다 남겨야 하는지 식별하는 방식입니다.

이 상자들 각각은 모두 같은 크기이며 일정량의 정보를 보관할 수 있습니다. 상자의 크기는 머신의 종류에 따라 결정되는데, 워드 사이즈(word size)라고 부릅니다. 워드 사이즈는 통상 32-bit 또는 64-bit 입니다. 하지만 그림으로 쉽게 표현하기 위해 저는 워드 사이즈를 8-bit라고 가정하겠습니다.

 

8개 칸이 있는 상자

 

만약 숫자 2를 어떤 상자에 저장하고 싶다면? 쉽습니다. 숫자는 이진수로 표현하기가 쉽습니다.

 

숫자 2를 이진수 00000010으로 변환하여 상자에 넣는다

 

숫자가 아닌 어떤 것을 저장하고 싶다면 어떻게 할까요? 알파벳 H 같은 것 말이죠.

알파벳 H 를 숫자로 표현할 방법이 필요합니다. 그러기 위해 만든 것이 인코딩입니다. UTF-8 같은 것 말이죠. 이제 알파벳 H 를 숫자로 인코딩해줄 도구가 필요합니다. 예를 들어 인코더-링(encoder ring) 같은 것 말입니다. 이제 우리는 알파벳 H 를 저장할 수 있습니다.

 

알파벳 H를 인코더-링으로 변환하여 72를 얻고, 이를 이진수로 변환하여 상자에 넣는다

 

저장했던 정보를 다시 가져오고 싶을 때에는, 정보를 변환해주는 디코더를 사용해서 상자에서 꺼낸 정보를 다시 알파벳 H 로 변환합니다.

 

메모리 자동 관리

JavaScript 로 일할 때 우리는 메모리에 대한 생각을 할 필요가 없습니다. 메모리 문제는 전적으로 JavaScript 가 책임집니다. 이는 우리가 메모리에 직접 접근할 수 없다는 뜻입니다.

대신, JS 엔진이 중재자 역할을 합니다. JS 엔진이 우리 대신 메모리를 관리합니다.

 

사물함 상자 앞에 로프가 쳐져있고 JS엔진이 경비처럼 서있다

 

어떤 JS 코드(가령 React 같은 코드)가 변수를 만드는 상황을 생각해봅시다.

 

위와 같은 상황에서 React가 JS엔진에게 변수 생성을 요구한다

 

그러면 JS 엔진은 그 변수에 저장할 값(value)을 인코더에 넣고 돌려서 해당 값(value)에 대한 이진수 표현을 얻어냅니다.

 

JS엔진이 인코더-링으로 문자열에 대한 이진수 표현을 만든다

 

그런 다음 JS 엔진은 비어 있는 메모리 공간을 찾아서 이진수로 표현된 값(value)을 거기에 넣습니다. 이것이 메모리 할당이라고 불리는 과정입니다.

 

JS엔진이 이진수 표현을 저장할 사물함 상자를 찾는다

 

이제부터 JS 엔진은 이 변수가 프로그램에 의해 사용되고 있는지 계속 확인합니다. 만약 더이상 변수가 참조되지 않으면 변수가 차지하고 있던 메모리 공간이 반환됩니다. 그러면 JS 엔진이 반환된 메모리 공간을 다시 사용할 수 있게 됩니다.

 

가비지 컬렉터가 메모리를 청소한다

 

이렇게 (문자열, 객체, 기타 모든 값들을 저장하는) 변수를 감시하다가, 더이상 변수가 사용되지 않을 때 청소하는 작업을 가비지 컬렉션(garbage collection)이라고 합니다.

JavaScript 처럼 개발자가 메모리 관리를 직접 하지 않는 랭귀지를 메모리-매니지드 랭귀지(memory-managed language)라고 부릅니다.

개발자에게는 메모리 자동 관리 방식이 편합니다. 하지만 (가비지 컬렉션으로 인한) 성능 저하를 감수해야 합니다. 그리고 보통 예상치 못한 시점에 성능 저하가 발생합니다.

 

메모리 수동 관리

메모리를 직접 관리하는 랭귀지는 다릅니다. 예를 들어, React 코드가 C 랭귀지로 작성됐다면 어떤 방식으로 메모리를 다루는지 알아봅시다 (WebAssembly 덕분에 실제로 정말 있을 수 있는 일입니다).

C 랭귀지에는 JavaScript 가 메모리 관리를 위해 제공하는 추상화 계층이 없습니다. 우리는 메모리에서 직접 값을 읽어 올 수도 있고, 직접 값을 저장할 수도 있습니다.

 

React의 웹어셈블리 버전이 메모리를 직접 억세스한다

 

C 또는 다른 랭귀지를 WebAssembly 로 컴파일할 때 우리가 사용하는 도구가 WebAssembly 결과물에 헬퍼 코드(helper code)를 추가합니다. 예를 들어, 바이트(byte) 값을 인코딩하고 디코딩하는 코드 말입니다. 이런 코드를 런타임 환경(runtime environment)이라고 부릅니다. 런타임 환경을 통해 JS 엔진이 JS 코드를 처리할 때 제공하는 편의 기능들의 일부를 제공받을 수 있습니다.

 

웹어셈블리 .wasm 파일의 일부로 제공되는 인코더-링

 

하지만 런타임은 가비지 컬렉션 기능은 제공하지 않습니다.

이것이 모든 것을 프로그래머 혼자 처리해야 한다는 뜻은 아닙니다. 메모리 수동 관리 방식을 따르는 랭귀지도 런타임으로부터 약간의 도움을 제공 받습니다. 예를 들면, C 랭귀지에서, 런타임 환경은 소위 프리-리스트(free list)라고 불리는 메모리 주소 구조체를 제공합니다.

 

사물함 옆의 프리-리스트에 비어있는 상자 목록이 적혀있다

 

우리는 malloc (memory allocate 의 단축 표현) 함수를 사용해서 런타임에게 데이터를 저장할 메모리 주소를 달라고 요청합니다. 그러면 이 함수는 프리-리스트에서 적당한 주소를 가져다 전해줍니다. 우리가 데이터 처리를 끝내고 나면, 우리는 free 함수를 호출해서 해당 메모리를 반환합니다. 그러면 그 주소는 다시 프리-리스트에 추가됩니다.

우리는 이 함수들의 호출 시점을 결정해야 합니다. 즉, 메모리를 스스로 관리해야 합니다. 그래서 이런 처리 방식을 메모리 수동 관리 방식이라고 부릅니다.

개발자로서 언제 어떤 메모리를 반환해야 하는지 매번 결정하는 것이 번거로울 수 있습니다. 적절하지 않은 시점에 메모리를 반환하면, 버그가 발생하거나 심각한 보안문제(security hole)가 발생할 수 있습니다. 만약 개발자가 사용한 메모리를 전혀 반환하지 않으면 시스템의 메모리가 모두 소진될 것입니다.

그래서 많은 수의 최신 랭귀지들은 메모리 자동 관리 방식을 사용합니다. 인간의 실수를 예방하기 위해서 입니다. 그러기 위해 실행 성능을 대가로 지불합니다. 다음 글에서 좀 더 자세히 설명하겠습니다.

 

이 글은 Lin Clark이 쓴 A crash course in memory management의 한국어 번역본입니다

한국 Mozilla Hacks의 글을 편집해서 복사합니다.

 

 

Posted by ingeeC
,
웹3 세상에서는 개인이 identity와 data에 대한 통제권을 갖습니다.
그래서 웹3 세상을 열기 위해서는 identity와 data 관리에 대한 고민이 필수적입니다.
그런 맥락에서 현실 세계의 대표적인 identity 관리 방식인 OAuth2와 OpenID Connect을 살펴봅니다.

 

OAuth2 표준과 OpenID Connect 표준

  • OAuth2는 Authorization(이후 authZ)에 대한 표준이다.
  • 반면 OpenID Connect(이후 OIDC)는 Authentication(이후 authN)에 대한 표준이다.
  • OIDC 표준은 OAuth2 표준을 기반으로 정의되었다.

 

OIDC AuthZ code flow

그림1. OAuth2 시퀀스 그림2. OIDC 시퀀스
  • 그림1은 OAuth2 authZ code grant type의 동작 시퀀스다.
  • 그림2는 OIDC authZ code flow의 동작 시퀀스다.
  • 두 시퀀스는 사실상 동일하다. 두 시퀀스의 차이점은 id-provider 서버(그림의 'KEYCLOAK')로부터 받아 오는 토큰이다(양쪽 그림의 5번 'Exchange authZ code...' 화살표).
    • 이때 OAuth2 프로토콜은 Access Token을 발급하고, OIDC 프로토콜은 ID Token을 발급한다.

 

OIDC JWT 표준

  • ID Token의 규격은 OIDC 표준이 정한다.
    • ID Token은 JWT(JSON Web Token) 포맷을 따른다.
  • JWT 토큰에는 토큰 발급자(issuer, 이후 iss)의 priv-key를 이용해서 만든 서명이 포함되어 있어서, 누구나 iss의 pub-key를 이용해서 토큰의 무결성을 검증할 수 있다.
  • Access Token 관련해서는 정해진 규격이 없다.
    • OAuth2 표준은 Access Token의 포맷을 규정하지 않는다. 그래서 어떤 포맷을 사용하더라도 표준에 부합한다.
    • Access Token 포맷으로 JWT를 사용하는 경우가 많다.
  • 관련용어
    • JWT: JSON Web Token
    • JWS: JSON Web Signature
    • JWKS: JSON Web Key Set

 

JWT 토큰 검증

https://jwt.io

https://jwt.io 페이지에서 JWT 포맷으로 발급된 토큰을 검증할 수 있다.

  1. 로컬머신에 OIDC id-provider 서버를 띄우고 JWT 포맷의 Access Token을 발급 받는다.
  2. 발급 받은 토큰을 https://jwt.io/ 에 Copy & Paste 한다.
  3. "Signature Verified" 문구를 확인할 수 있다.
토큰샘플 eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtlT0tCZGNNdVNGcnhWU0dTZlhBcFNmLXlYR3BRMEFmQmx1YnU4OVJ1R1kifQ.eyJleHAiOjE2OTk1OTY3NjQsImlhdCI6MTY5OTUxMDM2NCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MzgwIiwic3ViIjoidGVzdC1hY2Nlc3MifQ.psH3-wi6cztRaVaXe0Exh9Aoo_qkvB7_UQhcs1Kzlk8zcLpOZrb20-BLJPs8cV4vlr1jkpm2E_q-cHfAq41ged48zRp0O3bwEPekTinE2T1qfKG3t_8WJu80Wn5Q4d64P8xQCPA8Ly_Q-Q8pRzdmjQBeUK9jZxzEmeqa9BGadna1IHM-WBd7LzCq5pCjS9gkphIy9t5J_bW2f6Nhp67T-VY9gneNy8izTgrja_ulXtifPqHZNaAK7IFFpyJxCvM__3RMOeEFuHYmtmGN7_pT2gT9QJ5cYwGRFbox2V1q95dUfLQWn0nQmC7l1VxOKBRMGcSgGtvvMBu8x-nPalY3gw
JWT 검증OK

잠깐, 뭘로 검증했지?

  • JWT 토큰에 포함된 Signature는 iss의 priv-key를 이용해 만들어진 값이다.
  • 이를 검증하려면 iss의 pub-key가 필요하다.
  • 그런데 JWT 토큰 안에는 iss의 pub-key가 존재하지 않는다. 어디서 가져왔을까?

JWKS URI

JWT 토큰을 검증하려면 iss의 pub-key를 확보해야 한다. 그 절차는 다음과 같다.

  1. 토큰에 포함된 iss 속성값으로부터 well-known URL을 유추한다 (iss + '/.well-known/openid-configuration').
  2. (예시로 든 토큰샘플의 iss는 http://localhost:8380 이다. 이 토큰을 검증해서 Signature Verified 결과를 얻으려면 http://localhost:8380/.well-known/openid-configuration 엔드포인트를 호스팅하는 id-provider가 떠 있어야 한다.)
  3. well-known URL에서 JWKS uri를 확보한다.
  4. JWKS uri에서 iss의 pub-key를 얻을 수 있다.

http://localhost:8380/.well-known/openid-configuration 엔드포인트의 응답

{
  "issuer": "http://localhost:8380",
  "authorization_endpoint": "http://localhost:8380/authz",
  "token_endpoint": "http://localhost:8380/token",
  "userinfo_endpoint": "http://localhost:8380/userinfo",
  "jwks_uri": "http://localhost:8380/jwks"
}

 

http://localhost:8380/jwks 엔드포인트의 응답

{
  "keys": [
    {
      "kty": "RSA",
      "kid": "keOKBdc...",
      "use": "sig",
      "alg": "RS256",
      "e": "AQAB",
      "n": "xPyyJCGj..."
    }
  ]
}

 

Ref

Keycloak Identity and Access Management for Modern Applications (2nd Ed.) 책

Understanding ID Token

How do you choose between OAuth2 and OpenID Connect for web authorization?

JWKs and node-jose

Validate an OpenID Connect JWT using a public key in JWKS

What is OIDC Discovery .well-known end-point?

 

Posted by ingeeC
,

IPFS 2023 컨퍼런스 요약

Dev 2023. 10. 16. 16:27

 

이름만으로 가슴 뛰게 만드는 프로젝트가 있습니다.
IPFS (InterPlanetary File System) 얘기입니다.
지난 4월 벨기에에서 IPFS 2023 컨퍼런스가 열렸습니다.
관련 내용을 요약합니다.

모든 발표 동영상은 유튜브에 올려져있습니다.
https://www.youtube.com/playlist?list=PLuhRWgmPaHtQ-TO65P62tqfUM85HCIqSj

 

IPFS þing 2023 - IPFS on the Web

 

www.youtube.com

 

IPFS on the Web in 2023 (so far) - Dietrich Ayala (30분)

https://youtu.be/dn8PssXkRbY

  • 행사 개요와 연사들을 소개한다
  • Brave 브라우저가 IPFS를 적극 지원하고 있다 - Brave는 IPFS엔진을 내장하고 있다
  • 모바일 IPFS 플랫폼 DURIN을 개발하고 있다 - 모바일에서 IPFS 컨텐츠를 볼 수 있다
  • IPFS를 인공위성에서 운영하는 실험을 하고 있다 - https://github.com/ipfs-shipyard/space

What Is The Web? - Robin Berjon (30분)

https://youtu.be/s878bm15mrk

  • 웹의 형태를 바꾸자 - 지금은 서버가 기능을 제공, 서버에 권력이 있다
  • 2023-03-28에 "IPFS Principles" 표준을 출간했다 - IPFS가 웹의 미래다

A better web: secure, private, p2p apps with user-owned data and identity - Ian Preston, 30분 동영상

https://youtu.be/mSElk2jcFqY

  • 감시와 통제, 아이덴티티, 데이터 소유권에 관한 문제를 해결하고자 Peergos(회사)가 노력하고 있다
  • 서버 없는 REST API, "Sandbox"를 잠깐 언급한다

WNFS: Versioned and Encrypted Data on IPFS - Philipp Krüger (30분)

https://youtu.be/LBMyRp4Ywew

  • fission(회사)가 데이터를 암호화해서 IPFS에 저장하는 서비스를 제공한다
  • 커뮤니티 프로젝트다, 참여하라 - https://github.com/wnfs-wg/

Content Based Addressing and the Web Security Model - Fabrice Desré (40분)

https://youtu.be/H_1JVGDnctI

  • 처음부터 Security를 고려한 파일시스템 Web Tiles를 소개한다
  • Web Tiles는 IPFS와 닮았지만 다르다

Hello Helia - achingbrain (30분)

https://youtu.be/T_FlhkLSgH8

  • js-ipfs와 js-libp2p 메인테이너가 "브라우저 IPFS 구현체"의 미래/마일스톤을 직접 소개한다
  • js-ipfs를 대치하는 Helia가 릴리즈되어 지금 당장 쓸 수 있다 - https://github.com/ipfs/helia

JavaScript performance - how to wring the most out of your Helia deployment - achingbrain (20분)

https://youtu.be/zPeLYosZ3Ak

  • 새로운 "브라우저 IPFS 구현체" Helia의 성능 측정 자료를 공개한다

Connecting everything, everywhere, all at once with libp2p - Prithvi Shahi (20분)

https://youtu.be/4v-iIB0C9_8

  • libp2p 프로토콜로 모든 소프트웨어와 모든 네트워크를 한번에 연결하자
  • libp2p가 QUIC, WebRTC, WebTransport를 어떻게 이용하는지 소개한다
  • 더 알고 싶다면 github를 참조하라 - github.com/libp2p/universal-connectivity

The Incredible Benefits of libp2p + HTTP - Marten Seemann & Marco Munizaga (15분)

https://youtu.be/Ixyo1G2tJZE

The Name Name Service - Blaine Cook (30분)

https://youtu.be/CHiCEd36KtI

  • NNS (Name Name Service)는 탈중앙화된 DNS를 목표로 한다
  • NNS는 DID를 이용한다 - NNS의 Name은 DID가 아니지만, 적어도 1개의 DID를 갖는다
  • Decentralized, Human Readable, Secure 사이의 상충을 보여주는 Zooko's Triangle을 소개한다

Building decentralized websites on IPFS - Ryan Shahine (30분)

https://youtu.be/TeFAHmzvIdg?si=2vGIcNfwYDBi58VV

  • giliam.eth 같은 ENS에 웹사이트를 생성/배포하는 도구를 개발했다 - Portrait
  • Brave 브라우저는 giliam.eth 같은 ENS를 해석/처리할 수 있다

ODD.js, a technical overview - icidasset (10분)

https://youtu.be/ByQbY3lNAck?si=gcAHRhoPqRDysdoy

  • ODD.js는 백엔드가 없는 어플리케이션을 위한 프레임워크다
  • ODD.js는 DID, WNFS, UCAN을 이용한다

IPFS native frontend development using Importmaps - Dilip Shukla (20분)

https://youtu.be/4HY_7DxScMo?si=5dp82haOtmpm3XHk

  • 웹페이지를 위한 package.json 역할을 하는 importmap을 소개한다
  • importmap을 지원하는 jspm 도구를 개발했다 - npm을 대체할 수 있다

Explorations into Decentralized Publishing - David Justice (10분)

https://youtu.be/fn5QNvRXMIo?si=eojMK9sD7jlbuwOb

  • IPFS와 FVM을 이용해서 팀블로그를 만들었다 - GH: meandavejustice/blog-builder
  • IPFS에 호스팅된 SPA 형태의 Viewer와 FVM에서 동작하는 스마트컨트랙트를 이용했다
  • 짧은 발표와 긴 Q&A가 인상적

 


기타 번외로 참조한 IPFS 동영상들을 나열합니다.

How IPFS deals with files - IPFS Camp Workshop

https://youtu.be/Z5zNPwMDYGg

  • 2019-09-18, 1시간 동영상
  • IPFS가 데이터를 다루는 방식을 자세히 소개한다
  • CID와 CID 탐색기를 소개한다 - cid.ipfs.tech
  • DAG와 DAG 빌더를 소개한다 - dag.ipfs.tech

libp2p NAT Hole Punching Success Rate - @dennis-tra - Measuring IPFS

https://youtu.be/fyhZWlDbcyM

  • 2022-08-10, 30분 동영상
  • 방화벽과 NAT 장벽을 넘어 p2p 연결을 달성하는 Hole Punching에 대해 소개한다
  • kubo v0.11 부터 kubo가 릴레이 역할을 함께 수행한다 (kubo는 go-ipfs의 새 이름)

Intro to libp2p: helping with real world application problems - Max Inden

https://www.youtube.com/watch?v=J7ZWbpo2AZk

  • 2022-11-01, 15분 동영상
  • libp2p 프로토콜은 IFPS, Ethereum2, Poladot 등에 적용되어 있다
  • 현재 libp2p 팀은 브라우저에서의 연결성 개선을 위해 노력하고 있다

Browser connectivity state of union and demo

https://youtu.be/qG_1bYVtGO4

  • 2022-11-01, 20분 동영상
  • 브라우저의 문제는 TCP 프로토콜과 QUIC 프로토콜을 쓸 수 없다는 점이다
  • libp2p의 브라우저 연결성 개선에 관하여 WebTransport 프로토콜에 기대를 걸고 있다

libp2p NAT Hole Punching

https://youtu.be/bzL7Y1wYth8

  • 2022-11-01, 30분 동영상
  • Hole Punching 개념과 DCUtR 프로토콜의 개요를 설명한다

WebRTC signalling data over QR codes

https://youtu.be/EQFUPZK9CwI

  • 2022-11-02, 10분 동영상
  • 일련의 QR코드 스트림(동영상)을 이용하여 기기와 기기가 WebRTC 통신을 수행하는 데모를 시연한다 - 재밌음

Why WebRTC

https://youtu.be/ZBIFFuakFHQ

  • 2022-11-02, 10분 동영상
  • libp2p 브라우저 연결성 개선을 위해 시도한 노력의 내용과 현황을 소개한다
  • 아직 완벽하지 않다

이상입니다.

 

Posted by ingeeC
,