NanoID: 작고 빠른 고유 식별자 생성기

NanoID: 작고 빠른 고유 식별자 생성기

웹, 모바일, 서버 등 다양한 환경에서 확장이 용이한 애플리케이션을 개발하기 위해서 전통적인 숫자 시퀀스 기반의 식별자 대신 랜덤 문자열 기반의 식별자를 많이 사용하는 추세인데요.

이번 포스팅에서는 이러한 문자열 기반의 고유 식별자를 빠르게 생성하기 위한 간편한 도구인 NanoID에 대해서 알아보겠습니다.

NanoID란?

고유 문자열 식별자 생성기(unique string ID generator)기 하면 아마도 UUID(Universally Unique IDentifier)를 떠올리시는 분들이 많을 것 같은데요. 대표적인 식별자 생성기인 UUID는 오랫동안 사용되어 왔지만 36바이트로 길이가 길고 계산 비용이 큰 편이라서 하드웨어 리소스가 한정적이거나 고성능을 애플리케이션에서는 문제가 될 수 있었습니다.

a9714803-f8d4-4660-b00b-87324c2ae7aa // UUID
8Fn2JgUWUn57lPGk7L2b3 // NanoID

따라서 자연스럽게 좀 더 작고 빠른 고유 식별자 생성기에 대한 수요가 있었으면 이를 충족시키기 위해 등장한 것이 바로 NanoID입니다. NanoID는 21 바이트 길이로 UUID보다 적은 비용으로 계산이 가능하기 때문에 최근에는 많은 프로젝트에서는 UUID 대신에 NanoID를 식별자 생성기로 채택하고 있습니다.

NanoID는 다음과 같이 다양한 언어에서 라이브러리 패키지의 형태로 제공되기 때문에 설치해서 사용할 수 있습니다.

본 포스팅에서는 이 중에서 현재 GitHub에서 ⭐을 제일 많은 받은 자바스크립트 용 라이브러리로 실습을 진행해보겠습니다.

UUID에 대한 자세한 설명은 별도 포스팅을 참고 바랍니다.

NanoID의 기본 사용법

NanoID 패키지의 사용법은 정말 간단한데요.

우선 npm으로 nanoid라는 패키지를 설치합니다.

$ npm install nanoid

이제 nanoid 패키지로 부터 nanoid()라는 함수를 불러온 후에 호출하기만 하면 됩니다.

import { nanoid } from "nanoid"; // ESM
// const { nanoid } = require("nanoid"); // CommonJS

const id = nanoid();
console.log(id); // '8SbSCpLGGyCaMNXlfb1ZS'

이제 nanoid() 함수가 반환하는 랜덤 문자열을 고유 식별자로 사용할 수 있습니다. 따로 설정할 필요 없이 바로 사용할 수 있어서 진짜 간편하죠? 😁

NanoID가 만들 수 있는 조합의 수

NanoID가 어떻게 짧은 길이만으로도 충분한 고유성을 보장하는지 궁금하실 텐데요. 이를 이해하려면 한 자리에 들어갈 수 있는 후보 문자의 가짓수와 거기서 만들어낼 수 있는 조합의 수를 따져봐야 합니다.

NanoID는 기본적으로 URL에서 안전하게 사용할 수 있는 64가지 문자를 사용합니다.

  • 영문 대문자 26자 (A-Z)
  • 영문 소문자 26자 (a-z)
  • 숫자 10자 (0-9)
  • 특수문자 2자 (_, -)

이렇게 한 자리에 들어갈 수 있는 후보 문자가 정확히 64가지, 다시 말해서 2의 6제곱이기 때문에 한 자리당 6비트의 정보를 담을 수 있는데요. 기본 길이인 21자리로 식별자를 만들면 총 21 × 6 = 126비트의 무작위성을 확보하게 됩니다.

따라서 NanoID로 만들 수 있는 조합의 수는 다음과 같이 계산할 수 있는데요.

64^21 = 2^126 ≈ 8.51 × 10^37

이는 매우 큰 숫자라서 충돌 가능성이 사실상 없다고 봐도 무방합니다. 참고로 UUID v4는 122비트의 무작위성을 제공하므로, 21자리 NanoID는 길이가 더 짧음에도 UUID v4보다 16배 더 많은 조합을 만들어낼 수 있습니다.

NanoID의 사용자화

뿐만 아니라 NanoID는 다양한 옵션을 제공하기 때문에 사용자가 자신의 요구에 맞게 설정할 수 있는데요. 많이 사용되는 옵션만 간단히 다뤄보도록 하겠습니다.

우선 NanoID는 기본적으로 21자리 길이의 ID를 생성하는데요. 이 길이를 변경하고 싶다면 nanoid() 함수의 인수로 생성할 ID의 길이를 지정해주면 됩니다.

예를 들어, 10자로 NanoID이 길이를 줄여보겠습니다.

const id = nanoid(10);
console.log(id); // EsTMGzIeLH

단, 식별자의 길이가 짧아지면 복붙하기 쉬워지고 저장 공간을 아낄 수 있지만 식별자 간에 충돌 위험성이 높아진다는 큰 단점도 있습니다. 따라서 식별자의 길이를 줄일 때는 이러한 trade-off를 반드시 고려해야겠습니다.

NanoID는 기본적으로 URL-safe 문자열을 사용하여 ID를 생성하는데요. 그러나 사용자는 customAlphabet() 함수를 통해서 이를 커스터마이징할 수 있습니다.

예를 들어, 16진수를 구성하는 문자(0-9, A-F)로만 8자리 NanoID를 생성해보겠습니다.

import { customAlphabet } from "nanoid";

const nanoid = customAlphabet("0123456789ABCDEF", 8);
const id = nanoid();
console.log(id); // D690B3F7

이렇게 후보 문자를 줄이거나 길이를 짧게 하면 충돌 위험이 빠르게 커지므로 신중하게 선택해야 합니다.

NanoID 구현 살펴보기

NanoID 라이브러리는 막상 들여다보면 의외로 코드가 단출한데요. 핵심 로직만 추리면 단 몇 줄로 동작 원리를 파악할 수 있습니다.

const urlAlphabet =
  "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";

const nanoid = (size = 21) => {
  let id = "";
  let i = size;
  while (i--) {
    id += urlAlphabet[(Math.random() * 64) | 0];
  }
  return id;
};

위 코드는 보안이 그다지 중요하지 않은 환경에서 사용되는 non-secure 버전을 거의 그대로 옮긴 것인데요. 한 줄씩 따라가 보면 다음과 같습니다.

  • urlAlphabet: URL에서 안전하게 쓰일 수 있는 후보 문자 64가지를 미리 모아둔 문자열입니다.
  • Math.random() * 64: 0 이상 64 미만의 난수를 하나 뽑습니다.
  • | 0: 소수점 부분을 잘라내어 정수 인덱스로 바꿉니다.
  • urlAlphabet[...]: 해당 인덱스의 문자를 골라 결과 문자열에 이어 붙입니다.

이 과정을 21번 반복하면 기본 길이의 NanoID가 완성되는 셈이죠.

다만 식별자가 보안과 직결될 수 있기 때문에 NanoID는 기본적으로 좀 더 안전한 난수 발생기인 crypto.randomFillSync()를 사용합니다.

import crypto from "crypto";

const urlAlphabet =
  "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";

const nanoid = (size = 21) => {
  const bytes = crypto.randomFillSync(Buffer.allocUnsafe(size));
  let id = "";
  for (let i = 0; i < size; i++) {
    id += urlAlphabet[bytes[i] & 63];
  }
  return id;
};

crypto.randomFillSync()로 채운 바이트에서 0부터 63 사이의 값만 남기기 위해 & 63으로 걸러주는 부분만 다르고 큰 흐름은 동일합니다. 이렇게 단순한 알고리즘만으로도 짧고 충돌 위험이 낮은 식별자를 척척 만들어 낼 수 있다는 점이 NanoID의 매력 아닐까 싶습니다.

마치며

지금까지 작고 빠른 고유 식별자 생성기인 NanoID를 어떻게 사용하고, 어떤 옵션이 있는지에 대해서 살펴보았습니다. NanoID를 다양한 프로젝트에서 잘 활용하셔서 더욱 안전하고 신뢰성 높은 고유 식별자를 생성하실 수 있으셨으면 좋겠습니다.

This work is licensed under CC BY 4.0 CC BY

개발자를 위한 뉴스레터

달레가 정리한 AI 개발 트렌드와 직접 만든 콘텐츠를 전해드립니다.

Discord