![[번역] 대부분 golang과 sqlite로 충분하다](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJRCJE%2FbtsJmLkO7jv%2F4vfIGZbzD67sy3vqvAlIwk%2Fimg.png)
https://crawshaw.io/blog/one-process-programming-notes
crawshaw - 2018-07-30
One process programming notes (with Go and SQLite) 2018 July 30 Blog-ified version of a talk I gave at Go Northwest. This content covers my recent exploration of writing internet services, iOS apps, and macOS programs as an indie developer. There are sever
crawshaw.io
위의 글을 번역해서 가져왔습니다.
----
구글에서 일을 하던 시점에서 Small 비지니스를 만들기 위해서 한명의 개발자로 일했습니다.
실리콘 밸리의 큰 회사들과 VC 회사들에서는 많은 훌륭한 엔지니어링 기술들이 존재합니다.
다만, 한명의 개발자로써는 그런 기술들을 사용할만큼 충분한 시간과 여유가 없습니다.
그래서 무엇을 지켜야하고 무엇으로 나아가야할 지 아래의 경험을 통해서 결정했습니다.
두 가지 Key 기술이 있습니다.
Golang과 SQlite입니다.
SQlite의 간략한 소개
SQlite는 SQL의 구현입니다만, PostgreSQL 혹은 MySQL의 전통적인 데이터 베이스 구현과 다르게
프로그램에 내장되도록 설계된 독립형 C 라이브러리입니다.
그리고 2000년에 Release 되어 24년 이상 Open-source로 발전해왔습니다.
Golang에서의 SQlite 사용
아래와 같이 golang에서 sqlite를 쉽게 사용할 수 있습니다.
일반적으로 가장 유명한 프로젝트는 github.com/mattn/go-sqlite3 입니다.
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/mattn/go-sqlite3"
)
func main() {
db, err := sql.Open("sqlite3", "shakespeare.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
stmt, err := db.Prepare(`
SELECT play, act, scene, plays.text
FROM playsearch
INNER JOIN plays ON playsearch.playrowid = plays.rowid
WHERE playsearch.text MATCH ?;`)
if err != nil {
log.Fatal(err)
}
var play, text string
var act, scene int
err = stmt.QueryRow("whether tis nobler").Scan(&play, &act, &scene, &text)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s %d:%d: %q\n", play, act, scene, text)
}
SQLite는 전체 텍스트 검색을 통해 SELECT, INSERT, UPDATE, DELETE의 기본을 뛰어넘어 SQL 문만으로는 접근할 수 없는 몇 가지 흥미로운 기능과 확장 기능을 갖추고 있습니다. 이러한 기능에는 특수한 인터페이스가 필요하며, 이러한 인터페이스 중 상당수는 기존 드라이버에서 지원되지 않습니다.
그래서 직접 작성했습니다. crawshaw.io/sqlite에서 다운로드할 수 있습니다. 특히 스트리밍 블롭 인터페이스인 세션 확장을 지원하고 연결 풀의 공유 캐시를 잘 활용하기 위해 필요한 sqlite_unlock_notify 메커니즘을 구현합니다. 클라이언트와 클라우드의 두 가지 사용 사례 연구를 통해 이러한 기능을 살펴보겠습니다.
Go and SQlite for the Client
거의 Golang으로만 작성되어있고, web view로 UI를 사용하는 IOS app을 만들려고 합니다.
이 앱에는 사용자 데이터의 전체 사본이 있으며, 인터넷 서버에 대한 Thin view가 아닙니다.
즉, 대량의 로컬 구조화 데이터 저장, 기기 내 전체 텍스트 검색, UI를 방해하지 않는 방식으로 데이터베이스에서 작업하는 백그라운드 작업, 클라우드의 백업에 DB 변경 사항을 동기화해야 합니다.
클라이언트가 처리해야 할 부분이 너무 많습니다. 자바스크립트로 작성하고 싶을 때보다 더 많고, Swift로 작성했다가 안드로이드 앱을 빌드할 때 즉시 다시 작성해야 하는 것보다도 더 많습니다. 더 중요한 것은 서버가 Go로 되어 있고 저는 독립적인 개발자라는 점입니다. 개발 환경에서 움직이는 부품의 수를 가능한 한 최소한으로 줄이는 것이 절대적으로 중요합니다. 따라서 서버와 똑같은 기술을 사용하여 클라이언트의 (큰 부분을) 구축하는 데 많은 노력을 기울이고 있습니다.
클라우드의 Go 및 SQlite
1 VM, 1 Zone, 1 프로세스 프로그래밍으로 요약할 수 있습니다.
현재 (2018년 기준) Amazon에서 얻을 수 있는 최고의 서버는 대략 다음과 같습니다
- ~4GHz에서 128개의 CPU 스레드
- 4TB 램
- 25Gbit 이더넷
- 10Gbps NAS
- 연간 몇시간의 다운타임
이는 단일 프로세스 프로그래밍의 엄청난 잠재적 단점입니다. 그러나 저는 그게 좋은 한계라고 생각합니다.
일반적인 서비스는 이 Scaling 제한에 도달하지 않는다고 단언 할 수 있습니다.
소규모 비지니스를 구축하는 경우 수년 동안 위의 리소스 한도 내에서 대부분의 제품은 성장하고 수익을 낼 수 있습니다.
1~2년 내에 이 리소스 한도에 도달하는 것이 보인다면 당신은 한,두명의 개발자 혹은 새로운 팀을 고용할 매출이 있을것입니다. 그리고 새로운 팀은 급격하게 변화하는 비지니스에 직면할 수 있는데 그때 서비스를 재작성 하면됩니다.
이런 리소스 한도에 도달하는것은 좋은 문제이며 당신은 이를 해결하는데 필요한 리소스와 충분한 인적 자원을 갖게 될것입니다.
하지만, 소규모 비지니스의 초기에는 그렇지 않으며 대부분의 시간은 이런 리소스 한도가 아니라 고객의 Needs를 찾는데 몰두해야할 것 입니다.
여기서 작동하는 원칙은 다음과 같습니다.
1개의 서버로 가능한 서비스일때 N개의 서버를 사용하지 마십시오.
단일 가용성 영역에서 AWS에서 단일 VM을 실행합니다.
VM에는 3개의 EBS 볼륨(NAS의 Amazon 이름)이 있습니다.
1. OS, 로그, 임시 파일, 및 생성된 임시 SQLite 데이터베이스 기본 데이터베이스(예: FTS 테이블).
2. 기본 서비스에 대한 기본 SQLite 데이터베이스입니다.
3. 고객 동기화 SQLite 데이터베이스를 보유합니다.
기본 EBS 볼륨은 사용자 지정에 따라 매우 정기적으로 S3에 백업됩니다 WAL 캐시를 플러시하는 코드입니다.
해당 내용은 아래에서 추가로 설명하겠습니다.
서비스는 이 VM에서 실행되는 단일 Go Binary 파일입니다. 컴퓨터에는 Linux의 디스크 캐시에서 사용하는 추가 RAM이 많이 있습니다. (그리고 그것은 서비스의 두 번째 복사본이 회전하는 데 사용할 수 있습니다. 가동 중지 시간이 적은 교체를 위해.)
그 결과 연간 최대 수십 시간의 다운타임이 발생하고, RAID5 어레이가 있는 물리적 컴퓨터만큼의 블록 손실이 발생하며, 대규모 팀이 구축 및 유지 관리하는 분산 시스템에 몇 분마다 활성 오프사이트 백업이 수행되는 서비스가 탄생했습니다.
놀랍도록 간단한 시스템입니다.
저는 하나의 리눅스 머신을 사용했고, 10 줄 길이의 서비스에 대한 배포 스크립트를 사용했습니다.
대부분의 Performance 관련 작업은 pprof (Golang의 profiling툴) 로 완료했습니다.
단 몇 시간의 성능 튜닝만으로도 중간 크기의 VM에서는 5-6 천 개의 동시 요청을 클럭 할 수 있습니다.
AWS가 보유한 가장 큰 시스템에서는 수만개의 요청이 처리가 가능합니다.
이제는 세부 스택에 대해서 얘기해보려고합니다.
Shared Cache and WAL
서버의 동시성을 극대화하기 위해 제가 사용하는 두 가지 중요한 SQLite 기능이 있습니다.
첫 번째는 공유 캐시로, 데이터베이스 페이지 캐시에 하나의 큰 메모리 풀을 할당하여 많은 동시 연결이 동시에 사용할 수 있게 해줍니다. 이를 위해서는 사용자 코드가 잠금 이벤트를 처리할 필요가 없도록 드라이버에서 sqlite_unlock_notify에 대한 일부 지원이 필요하지만, 최종 사용자 코드에는 투명하게 표시됩니다.
두 번째는 미리 쓰기 로그(Write Ahead Log) 입니다. 이것은 SQLite가 연결 시작 시 노크할 수 있는 모드로, 트랜잭션을 디스크에 쓰는 방식을 변경합니다. 데이터베이스를 잠그고 롤백 저널과 함께 수정하는 대신, 새로운 변경 사항을 별도의 파일에 추가합니다. 이를 통해 독자는 작성자와 동시에 작업할 수 있습니다. WAL은 데이터베이스를 잠그고 변경 내용을 기록하는 SQLite에 의해 주기적으로 플러시되어야 합니다. 이를 위한 기본 설정이 있습니다.
저는 이를 재정의하고 완료되면 S3 스냅샷도 트리거하는 패키지에서 수동으로 WAL 플러시를 실행합니다. 이 패키지의 이름은 reallyfsync이며, 제대로 테스트하는 방법을 알아낼 수 있다면 오픈 소스로 공개할 예정입니다.
Incremental Blob API
작지만 제 특정 서버 기능에 중요한 또 다른 기능은 SQLite의 Incremental Blob API입니다. 이 기능을 사용하면 모든 바이트를 메모리에 동시에 저장하지 않고도 바이트 필드를 DB에서 읽고 쓸 수 있는데, 이는 각 요청이 수백 메가바이트로 작동할 수 있지만 수만 개의 잠재적 동시 요청이 필요할 때 중요합니다.
이 부분이 바로 드라이버가 cgo에 가까운 wrapper에서 벗어나 좀 더 Go-like 해집니다.
type Blob
func (blob *Blob) Close() error
func (blob *Blob) Read(p []byte) (n int, err error)
func (blob *Blob) ReadAt(p []byte, off int64) (n int, err error)
func (blob *Blob) Seek(offset int64, whence int) (int64, error)
func (blob *Blob) Size() int64
func (blob *Blob) Write(p []byte) (n int, err error)
func (blob *Blob) WriteAt(p []byte, off int64) (n int, err error)
이것은 파일과 매우 비슷해 보이며 실제로 파일처럼 사용할 수 있지만, 한 가지 주의할 점은 블롭의 크기가 생성될 때 설정된다는 점입니다. (그렇기 때문에 저는 여전히 임시 파일이 유용하다고 생각합니다.)
Designing with one process programming
정말로 N개의 서버가 필요한가요?
실제로 필요한 문제도 있습니다. 예를 들어, 4TB의 RAM만으로는 공용 인터넷의 지연 시간이 짧은 인덱스를 구축할 수 없습니다. 훨씬 더 많은 것이 필요합니다. 이러한 문제는 매우 재미있어서 많은 이야기를 나누고 싶지만 전체 코드 중 상대적으로 적은 양을 차지합니다. 지금까지 제가 Google 이후 개발해 온 모든 프로젝트는 컴퓨터 한 대에 적합했습니다.
한 대의 컴퓨터로 해결하기 어려운 하위 문제도 더 많이 있습니다. 글로벌 고객 기반이 있고 서버에 짧은 지연 시간이 필요한 경우 빛의 속도가 방해가 됩니다. 하지만 이러한 문제 중 상당수는 비교적 간단한 CDN 제품으로 해결할 수 있습니다.
빛의 속도에 대한 또 다른 훌륭한 솔루션은 지리적 샤딩입니다. 여러 데이터센터에 서비스의 완전하고 독립적인 복사본을 보유하고 사용자의 데이터를 가까운 데이터센터로 이동합니다. 이는 하나의 작은 글로벌 리디렉션 데이터베이스(지리적 중복 NFS의 SQLite일 수도 있습니다!)가 사용자를 {us-east, us-west}.mservice.com과 같은 특정 DNS 이름으로 리디렉션하는 것만큼 간단할 수 있습니다.
대부분의 문제는 어느 정도까지는 한 대의 컴퓨터에서 해결됩니다. 그 지점이 어디인지 파악하는 데 시간을 투자하세요. 몇 년이 걸린다면 한 대의 컴퓨터로 충분할 가능성이 높습니다.
Indie dev techniques for the corporate programmer
이 특정 기술 스택에서 코드를 작성하지 않고 독립 개발자가 아니더라도 여기에는 가치가 있습니다.
하나의 큰 VM, 하나의 영역, 하나의 프로세스 Go, SQLite 및 스냅샷 백업 스택을 가상 도구로 사용하여 설계를 테스트할 수 있습니다.
따라서 설계 프로세스에 가상의 단계를 추가하세요:
1. 이 스택에서 한 대의 컴퓨터로 문제를 해결한다면 얼마나 멀리 갈 수 있을까요?
2. 얼마나 많은 고객을 지원할 수 있을까요?
3. 소프트웨어를 다시 작성하려면 어느 정도의 크기가 필요할까요?
이 인디 사이즈의 작은 스택으로 몇 년 동안 비즈니스를 운영할 수 있다면 최신 클라우드 소프트웨어의 도입을 미루는 것이 좋습니다.
자본력이 탄탄한 회사의 프로그래머라면 소규모 내부 프로젝트나 실험적인 프로젝트의 개발 방식도 고려할 수 있습니다. 정책상의 이유로 동료들이 크고 복잡한 분산 시스템을 사용해야 하나요? 이러한 프로젝트 중 상당수는 컴퓨터 한 대 이상으로 확장할 필요가 없거나, 확장하더라도 변화하는 요구 사항을 처리하기 위해 다시 작성해야 할 것입니다. 이 경우 파일 시스템을 갖춘 Linux 가상 머신인 인디 스택을 프로토타이핑과 실험에 사용할 수 있는 방법을 찾아보세요.
'관심 분야 센싱 > 다른 사람 포스팅 구경하기' 카테고리의 다른 글
[번역] PageRank의 시대는 끝났다 (0) | 2025.01.26 |
---|---|
[번역] Shopify - 탄력 있는 결제 시스템을 위한 10가지 팁 (2) | 2024.12.21 |
개발 및 IT 관련 포스팅을 작성 하는 블로그입니다.
IT 기술 및 개인 개발에 대한 내용을 작성하는 블로그입니다. 많은 분들과 소통하며 의견을 나누고 싶습니다.