🏢 Figma 기술 스택 분석

22 min read
figmac++webassemblywebglrusttypescriptreactawswebsocketcrdtdesign-toolscollaborationvector-graphics

피그마가 브라우저에서 실시간 협업 디자인 툴을 구현한 기술 스택 심층 분석 - C++, WebAssembly, WebGL로 만든 차세대 디자인 플랫폼

🏢 Figma 기술 스택 분석

브라우저에서 네이티브 앱 수준의 디자인 툴을 구현한 Figma의 기술 스택을 심층 분석합니다.
"Design, prototype, and collaborate in real time" - 웹 기술의 한계를 넘어서는 혁신


📊 회사 개요

서비스 규모

  • 월간 활성 사용자: 400만+ 명
  • 생성된 디자인 객체: 30억+ 개
  • 팀: 100만+ 개
  • 동시 편집자: 파일당 200+ 명
  • 렌더링 성능: 60fps 유지

엔지니어링 조직

  • 엔지니어: 300명+ (Adobe 인수 후 확대)
  • 문화: "Make design accessible to everyone"
  • 기술 철학: 웹 기술의 한계 돌파
  • 오픈소스: 제한적 (핵심 기술은 비공개)

기술적 도전과제

  1. 브라우저 성능: 네이티브 앱 수준 속도
  2. 실시간 협업: 수백 명 동시 편집
  3. 벡터 렌더링: 복잡한 그래픽 실시간 처리
  4. 대규모 파일: GB 단위 디자인 파일

🏗️ 아키텍처 Overview

시스템 다이어그램

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Browser   │────►│  WebSocket  │────►│Multiplayer  │
│  (Client)   │     │   Server    │     │   Engine    │
└─────────────┘     └─────────────┘     └─────────────┘
       │                    │                    │
       │              ┌─────▼─────┐       ┌─────▼─────┐
       │              │   Render   │       │ Document  │
       │              │   Server   │       │   Store   │
       │              └───────────┘       └───────────┘
       │                    │                    │
       └────────────────────┼────────────────────┘
                      ┌─────▼─────┐
                      │WebAssembly│
                      │  Engine   │
                      └───────────┘

핵심 설계 원칙

  1. Performance First: 60fps 렌더링 목표
  2. Web-Native: 플러그인 없는 순수 웹
  3. Real-time Sync: 밀리초 단위 동기화
  4. Infinite Canvas: 무한 확장 가능
  5. Version Control: Git 같은 디자인 버전 관리

기술 진화

  • 2012-2013: WebGL 프로토타입
  • 2014-2015: C++ 엔진 개발
  • 2016-2017: WebAssembly 이전
  • 2018-2019: 멀티플레이어 엔진
  • 2020-2021: 대규모 최적화
  • 2022-현재: AI 기능 통합

🧩 기술 스택 상세

렌더링 엔진 (C++ → WebAssembly)

벡터 그래픽 엔진

// Figma의 렌더링 엔진 핵심 (C++)
class FigmaRenderer {
private:
    std::unique_ptr<Canvas> canvas;
    std::unique_ptr<Scene> scene;
    RenderCache cache;
    GPUContext* gpu_context;

public:
    void render(const Frame& frame) {
        // 1. 더티 영역 계산
        auto dirty_regions = calculate_dirty_regions(frame);

        // 2. 렌더 트리 구축
        auto render_tree = build_render_tree(frame.root);

        // 3. 레이어 최적화
        optimize_layers(render_tree);

        // 4. GPU 명령 생성
        auto commands = generate_gpu_commands(render_tree, dirty_regions);

        // 5. WebGL로 렌더링
        gpu_context->execute(commands);

        // 6. 캐시 업데이트
        cache.update(dirty_regions);
    }

    // 효율적인 히트 테스트
    Node* hit_test(const Point& point) {
        // Spatial indexing으로 빠른 검색
        auto candidates = spatial_index.query(point);

        for (auto node : candidates) {
            if (node->contains_point(point)) {
                // 정확한 경로 테스트
                if (precise_hit_test(node, point)) {
                    return node;
                }
            }
        }
        return nullptr;
    }

    // 벡터 경로 래스터화
    void rasterize_path(const Path& path, const Transform& transform) {
        // Bezier 곡선을 삼각형으로 분해
        auto triangles = tessellate_path(path);

        // 안티엘리어싱 적용
        apply_msaa(triangles, 4); // 4x MSAA

        // GPU 버퍼에 업로드
        gpu_context->upload_triangles(triangles);
    }
};

// 효율적인 텍스트 렌더링
class TextRenderer {
private:
    struct GlyphCache {
        std::unordered_map<GlyphKey, GlyphData> glyphs;
        TextureAtlas atlas;
    };

    GlyphCache cache;

public:
    void render_text(const TextNode& text) {
        // 글리프 준비
        prepare_glyphs(text.content, text.font);

        // 레이아웃 계산
        auto layout = calculate_layout(text);

        // SDF (Signed Distance Field) 렌더링
        for (const auto& glyph_run : layout.runs) {
            render_glyph_run(glyph_run);
        }
    }

    void render_glyph_run(const GlyphRun& run) {
        // 배치 렌더링으로 드로우콜 최소화
        auto batch = create_glyph_batch(run);

        // 서브픽셀 안티엘리어싱
        apply_subpixel_aa(batch);

        gpu_context->draw_instanced(batch);
    }
};

WebAssembly 브릿지

// TypeScript에서 WebAssembly 모듈 사용
export class FigmaEngine {
  private wasmModule: WebAssembly.Module;
  private wasmInstance: WebAssembly.Instance;
  private memory: WebAssembly.Memory;
  private renderFunc: (framePtr: number) => void;

  async initialize() {
    // WebAssembly 모듈 로드
    const response = await fetch("/figma-engine.wasm");
    const bytes = await response.arrayBuffer();

    // 컴파일 및 인스턴스화
    this.wasmModule = await WebAssembly.compile(bytes);

    // 메모리 공유
    this.memory = new WebAssembly.Memory({
      initial: 256, // 16MB
      maximum: 16384, // 1GB
      shared: true, // SharedArrayBuffer for threading
    });

    // Import 객체 설정
    const imports = {
      env: {
        memory: this.memory,
        // WebGL 바인딩
        glCreateShader: (type: number) => gl.createShader(type),
        glShaderSource: (shader: number, source: number) => {
          const sourceStr = this.readString(source);
          gl.shaderSource(shaders.get(shader), sourceStr);
        },
        glCompileShader: (shader: number) =>
          gl.compileShader(shaders.get(shader)),
        // ... 더 많은 WebGL 함수들
      },
      // JavaScript 콜백
      js: {
        onRenderComplete: () => this.handleRenderComplete(),
        requestAnimationFrame: (callback: number) => {
          requestAnimationFrame(() => {
            this.wasmInstance.exports.invokeCallback(callback);
          });
        },
      },
    };

    this.wasmInstance = await WebAssembly.instantiate(this.wasmModule, imports);
    this.renderFunc = this.wasmInstance.exports.render as any;
  }

  render(sceneData: SceneData) {
    // Scene 데이터를 WASM 메모리에 복사
    const scenePtr = this.allocateScene(sceneData);

    // C++ 렌더링 함수 호출
    this.renderFunc(scenePtr);

    // 메모리 정리
    this.freeScene(scenePtr);
  }

  // 효율적인 메모리 관리
  private allocateScene(scene: SceneData): number {
    const size = this.calculateSceneSize(scene);
    const ptr = this.wasmInstance.exports.malloc(size);

    // 구조체 데이터 직렬화
    const view = new DataView(this.memory.buffer, ptr, size);
    this.serializeScene(scene, view);

    return ptr;
  }
}

실시간 협업 시스템

멀티플레이어 엔진

// Rust로 구현된 고성능 동기화 서버
use tokio::net::{TcpListener, TcpStream};
use dashmap::DashMap;
use bytes::{Bytes, BytesMut};

#[derive(Clone)]
struct FigmaDocument {
    id: String,
    nodes: Arc<RwLock<HashMap<NodeId, Node>>>,
    operations: Arc<Mutex<Vec<Operation>>>,
    clients: Arc<DashMap<ClientId, Client>>,
}

#[derive(Debug)]
enum Operation {
    Create { node_id: NodeId, data: NodeData },
    Update { node_id: NodeId, changes: Vec<Change> },
    Delete { node_id: NodeId },
    Move { node_id: NodeId, parent_id: NodeId, index: usize },
}

struct MultiplayerServer {
    documents: Arc<DashMap<String, FigmaDocument>>,
    connections: Arc<DashMap<ClientId, Connection>>,
}

impl MultiplayerServer {
    async fn handle_client(self: Arc<Self>, stream: TcpStream, client_id: ClientId) {
        let (reader, writer) = stream.split();

        loop {
            match read_message(&mut reader).await {
                Ok(message) => {
                    self.process_message(client_id, message).await;
                }
                Err(_) => {
                    self.disconnect_client(client_id).await;
                    break;
                }
            }
        }
    }

    async fn process_message(&self, client_id: ClientId, message: Message) {
        match message {
            Message::Operation { doc_id, op } => {
                self.handle_operation(doc_id, client_id, op).await;
            }
            Message::Subscribe { doc_id } => {
                self.subscribe_to_document(client_id, doc_id).await;
            }
            Message::Cursor { position } => {
                self.broadcast_cursor(client_id, position).await;
            }
        }
    }

    async fn handle_operation(
        &self,
        doc_id: String,
        client_id: ClientId,
        operation: Operation
    ) {
        let doc = self.documents.get(&doc_id).unwrap();

        // 1. 변환 (Operational Transform)
        let transformed = self.transform_operation(operation, &doc).await;

        // 2. 적용
        self.apply_operation(&doc, &transformed).await;

        // 3. 브로드캐스트
        self.broadcast_operation(&doc, client_id, &transformed).await;

        // 4. 영속성
        self.persist_operation(&doc_id, &transformed).await;
    }

    // OT (Operational Transformation) 구현
    async fn transform_operation(
        &self,
        op: Operation,
        doc: &FigmaDocument
    ) -> Operation {
        let concurrent_ops = doc.operations.lock().await;

        let mut transformed = op;
        for concurrent_op in concurrent_ops.iter() {
            transformed = self.transform_pair(transformed, concurrent_op);
        }

        transformed
    }

    fn transform_pair(&self, op1: Operation, op2: &Operation) -> Operation {
        match (op1, op2) {
            (Operation::Move { node_id: id1, parent_id: p1, index: i1 },
             Operation::Move { node_id: id2, parent_id: p2, index: i2 }) => {
                if id1 == *id2 {
                    // 같은 노드 이동 - 나중 것 우선
                    op1
                } else if p1 == *p2 {
                    // 같은 부모로 이동
                    if i1 > *i2 {
                        Operation::Move {
                            node_id: id1,
                            parent_id: p1,
                            index: i1 - 1
                        }
                    } else {
                        op1
                    }
                } else {
                    op1
                }
            }
            // ... 더 많은 변환 규칙
            _ => op1
        }
    }
}

// 효율적인 diff 알고리즘
fn calculate_diff(before: &Node, after: &Node) -> Vec<Change> {
    let mut changes = Vec::new();

    // 속성 비교
    if before.properties != after.properties {
        changes.push(Change::Properties {
            old: before.properties.clone(),
            new: after.properties.clone(),
        });
    }

    // 자식 노드 비교 (Myers diff algorithm)
    let child_diff = myers_diff(&before.children, &after.children);
    changes.extend(child_diff);

    changes
}

Frontend 기술

React 기반 UI

// Figma의 React 컴포넌트 시스템
import { useEffect, useRef, memo } from "react";
import { FigmaEngine } from "./wasm/engine";

interface CanvasProps {
  document: FigmaDocument;
  onSelectionChange: (selection: NodeId[]) => void;
}

export const Canvas = memo(({ document, onSelectionChange }: CanvasProps) => {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const engineRef = useRef<FigmaEngine>();
  const [selection, setSelection] = useState<NodeId[]>([]);

  useEffect(() => {
    // WebAssembly 엔진 초기화
    const initEngine = async () => {
      const engine = new FigmaEngine();
      await engine.initialize();
      engineRef.current = engine;

      // WebGL 컨텍스트 설정
      const gl = canvasRef.current!.getContext("webgl2", {
        alpha: false,
        antialias: false, // MSAA는 직접 구현
        preserveDrawingBuffer: false,
        powerPreference: "high-performance",
      });

      engine.setGLContext(gl);
    };

    initEngine();
  }, []);

  // 렌더링 루프
  useEffect(() => {
    let frameId: number;

    const renderLoop = () => {
      if (engineRef.current && document) {
        // 프레임 준비
        const frameData = prepareFrameData(document, selection);

        // WASM 엔진으로 렌더링
        engineRef.current.render(frameData);

        // 성능 모니터링
        measurePerformance();
      }

      frameId = requestAnimationFrame(renderLoop);
    };

    renderLoop();

    return () => cancelAnimationFrame(frameId);
  }, [document, selection]);

  // 입력 처리
  const handleMouseDown = useCallback(
    (e: MouseEvent) => {
      const point = { x: e.clientX, y: e.clientY };
      const hitNode = engineRef.current?.hitTest(point);

      if (hitNode) {
        if (e.shiftKey) {
          // 다중 선택
          setSelection((prev) => [...prev, hitNode.id]);
        } else {
          setSelection([hitNode.id]);
        }

        onSelectionChange(selection);
      }
    },
    [onSelectionChange]
  );

  return (
    <div className="canvas-container">
      <canvas
        ref={canvasRef}
        className="figma-canvas"
        onMouseDown={handleMouseDown}
        onMouseMove={handleMouseMove}
        onWheel={handleWheel}
      />
      <SelectionOverlay selection={selection} />
      <Rulers zoom={zoom} offset={offset} />
    </div>
  );
});

// 선택 영역 오버레이
const SelectionOverlay = ({ selection }: { selection: NodeId[] }) => {
  const overlayRef = useRef<SVGSVGElement>(null);

  useEffect(() => {
    // 선택된 노드들의 경계 상자 계산
    const bounds = selection.map((nodeId) => {
      const node = getNode(nodeId);
      return calculateBounds(node);
    });

    // SVG로 선택 영역 렌더링
    renderSelectionBounds(overlayRef.current!, bounds);
  }, [selection]);

  return (
    <svg
      ref={overlayRef}
      className="selection-overlay"
      style={{ position: "absolute", inset: 0, pointerEvents: "none" }}
    />
  );
};

성능 최적화

가상화 및 LOD (Level of Detail)

// 대규모 디자인 파일 최적화
class ViewportManager {
  private visibleNodes: Set<NodeId> = new Set();
  private lodLevels: Map<NodeId, LODLevel> = new Map();

  updateViewport(viewport: Viewport, sceneRoot: Node) {
    // Quadtree로 빠른 공간 검색
    const candidates = this.quadtree.query(viewport.bounds);

    // 뷰포트에 보이는 노드만 렌더링
    const newVisible = new Set<NodeId>();

    for (const node of candidates) {
      const distance = this.calculateDistance(node, viewport);

      if (distance < viewport.radius) {
        newVisible.add(node.id);

        // LOD 레벨 결정
        const lod = this.calculateLOD(node, viewport.zoom);
        this.lodLevels.set(node.id, lod);
      }
    }

    // 차이 계산
    const added = difference(newVisible, this.visibleNodes);
    const removed = difference(this.visibleNodes, newVisible);

    // 증분 업데이트
    this.handleNodesAdded(added);
    this.handleNodesRemoved(removed);

    this.visibleNodes = newVisible;
  }

  calculateLOD(node: Node, zoom: number): LODLevel {
    const pixelSize = node.bounds.width * zoom;

    if (pixelSize < 10) {
      return LODLevel.INVISIBLE;
    } else if (pixelSize < 50) {
      return LODLevel.SIMPLIFIED;
    } else if (pixelSize < 200) {
      return LODLevel.NORMAL;
    } else {
      return LODLevel.DETAILED;
    }
  }
}

// 텍스처 아틀라스 관리
class TextureAtlasManager {
  private atlases: Map<string, TextureAtlas> = new Map();
  private lruCache: LRUCache<string, AtlasRegion> = new LRUCache(1000);

  async getTexture(imageId: string): Promise<AtlasRegion> {
    // 캐시 확인
    const cached = this.lruCache.get(imageId);
    if (cached) return cached;

    // 이미지 로드
    const image = await this.loadImage(imageId);

    // 적절한 아틀라스 찾기
    const atlas = this.findSuitableAtlas(image.width, image.height);

    // 아틀라스에 패킹
    const region = atlas.pack(image);

    // 캐시 저장
    this.lruCache.set(imageId, region);

    return region;
  }
}

기술 스택 요약:

  • 렌더링: C++ → WebAssembly, WebGL 2.0
  • Frontend: React, TypeScript, MobX
  • Backend: Rust, PostgreSQL, Redis
  • 실시간: WebSocket, Operational Transform
  • 인프라: AWS, Cloudflare Workers
  • 빌드: Bazel, custom toolchain

💡 핵심 기술 인사이트

1. WebAssembly의 혁신적 활용

  • 네이티브 성능을 웹에서 구현
  • C++ 코드 재사용
  • 메모리 효율적 관리

2. 커스텀 렌더링 파이프라인

  • GPU 가속 벡터 그래픽
  • 효율적인 캐싱 전략
  • 적응형 품질 조절

3. 실시간 협업의 정교함

  • Operational Transform 알고리즘
  • 충돌 없는 동시 편집
  • 최소 지연 동기화

📈 성과 & 지표

기술적 성과

  • 렌더링 성능: 60fps (복잡한 디자인에서도)
  • 로딩 시간: < 3초 (대규모 파일)
  • 동시 편집: 200+ 명 지원
  • 지연 시간: < 50ms (같은 지역)
  • 메모리 효율: 네이티브 앱 대비 70%

비즈니스 영향

  • 시장 점유율: 디자인 툴 시장 1위
  • Adobe 인수: $20B (2022년)
  • 사용자 만족도: NPS 70+
  • 개발 속도: 기존 툴 대비 3배

🎓 Figma에서 배울 점

✅ 적용 가능한 패턴

  1. WebAssembly 활용: 고성능 웹 앱
  2. Custom 렌더링: WebGL 직접 제어
  3. OT 알고리즘: 실시간 협업
  4. LOD 시스템: 대규모 콘텐츠 처리
  5. Spatial Indexing: 효율적 히트 테스트

❌ 주의사항

  1. 높은 기술 장벽: C++/WASM 전문성
  2. 브라우저 제약: 메모리, API 한계
  3. 복잡한 상태 관리: 동기화 로직
  4. 성능 최적화: 지속적 튜닝 필요

📚 추천 리소스


🔮 미래 전망

현재 집중 분야

  1. AI 디자인 도구: 자동 레이아웃, 생성
  2. Dev Mode: 개발자 핸드오프 개선
  3. 3D 지원: 3D 디자인 도구
  4. 더 큰 파일: TB급 파일 지원
  5. 플러그인 생태계: 더 강력한 API

기술 투자 영역

  • WebGPU: 차세대 GPU API
  • WASM SIMD: 벡터 연산 가속
  • SharedArrayBuffer: 멀티스레딩
  • AI/ML: 디자인 인텔리전스
  • 분산 시스템: 글로벌 확장

"우리는 디자인 도구를 만드는 것이 아니라, 창의성을 위한 플랫폼을 만들고 있습니다."

  • Figma Engineering

마지막 업데이트: 2025-01-28

Found this helpful? Share it with others!
Tweet

🔗 Related Content

You might also be interested in these articles

🏢 company

🏢 Notion 기술 스택 분석

노션이 수천만 사용자에게 All-in-One 워크스페이스를 제공하는 기술 스택 심층 분석 - 블록 기반 에디터, 실시간 협업, 그리고 확장 가능한 데이터베이스

24 min read
notion, typescript +11
Read more
🏗️ stack

🔄 Realtime Collaboration Stack

Figma, Notion 같은 실시간 협업 앱 구축을 위한 기술 스택 - WebSocket, CRDT, WebRTC로 만드는 동시 편집 시스템

21 min read
nodejs, socketio +13
Read more
🏢 company

🏢 Canva 기술 스택 분석

캔바가 1억 명 이상의 사용자에게 쉽고 강력한 디자인 도구를 제공하는 기술 스택 심층 분석 - Java, TypeScript, AWS로 구축한 대규모 디자인 플랫폼

32 min read
canva, java +10
Read more
🏢 company

🏢 Airbnb 기술 스택 분석

에어비앤비가 전 세계 400만 개의 숙소를 연결하는 글로벌 마켓플레이스 기술 스택 심층 분석 - Ruby on Rails에서 Service-Oriented Architecture로의 진화

24 min read
airbnb, ruby +12
Read more
🏢 company

🏢 Discord 기술 스택 분석

디스코드가 전 세계 1.5억 명에게 실시간 음성/비디오 통화와 채팅을 제공하는 기술 스택 심층 분석 - Elixir, Rust, WebRTC로 구축한 차세대 커뮤니케이션 플랫폼

24 min read
discord, elixir +12
Read more
🏢 company

🏢 Netflix 기술 스택 분석

넷플릭스가 전 세계 2.4억 사용자에게 스트리밍 서비스를 제공하는 기술 스택 심층 분석 - 마이크로서비스, AWS, Chaos Engineering의 교과서

9 min read
netflix, microservices +11
Read more

Found this helpful?

Help us improve this content by contributing on GitHub or sharing your feedback with the community.