SQL과 JSON의 유연성이 주는 힘

관계형 데이터베이스의 기능을 그대로 유지하면서 Couchbase의 인메모리 JSON 데이터베이스에서 관계형 데이터베이스의 최상의 기능을 그대로 유지하면서 밀리초 미만의 응답을 얻으세요.

레거시 데이터베이스 때문에 애플리케이션 개발이 지연되고 성능이 저하되고 있나요?
Couchbase를 사용하면 가능합니다:

  • 익숙한 쿼리 액세스를 위해 SQL 사용
  • 데이터를 JSON으로 저장하고 제한적인 관계형 데이터 모델을 피하세요.
  • SQL 및 JSON을 사용하여 새로운 기능을 빌드할 수 있습니다. 다중 모델 다음을 포함한 서비스 전체 텍스트 검색, 분석, 이벤트 및 키 값

코드 확인

Java - Upsert
                                      import com.couchbase.client.core.error.subdoc.PathNotFoundException;
import com.couchbase.client.java.*;
import com.couchbase.client.java.kv.*;
import com.couchbase.client.java.kv.MutationResult;
import com.couchbase.client.java.json.JsonObject;
import com.couchbase.client.java.kv.LookupInResult;
import static com.couchbase.client.java.kv.LookupInSpec.get;
import static com.couchbase.client.java.kv.MutateInSpec.upsert;
import java.util.Collections;

class Program {
  public static void main(String[] args) {
    var cluster = Cluster.connect(
      "couchbase://127.0.0.1", "username", "password"
    );
    var bucket = cluster.bucket("travel-sample");
    var collection = bucket.defaultCollection();

    JsonObject content = JsonObject.create()
      .put("country", "Iceland")
      .put("callsign", "ICEAIR")
      .put("iata", "FI")
      .put("icao", "ICE")
      .put("id", 123)
      .put("name", "Icelandair")
      .put("type", "airline");

    collection.upsert("airline_123", content);

    try {
      LookupInResult lookupResult = collection.lookupIn(
        "airline_123", Collections.singletonList(get("name"))
      );

      var str = lookupResult.contentAs(0, String.class);
      System.out.println("New Document name = " + str);

    } catch (PathNotFoundException ex) {
      System.out.println("Document not found!");
    }

  }
}
NodeJS - Upsert
                                      const couchbase = require('couchbase')

const main = async () => {
  const cluster = await couchbase.connect('couchbase://127.0.0.1', {
    username: 'username', password: 'password'
  })

  const bucket = cluster.bucket('travel-sample')
  const collection = bucket.defaultCollection()

  const airline = {
    country: 'Iceland', callsign: 'ICEAIR',
    iata: 'FI', icao: 'ICE', id: 123,
    name: 'Icelandair', type: 'airline',
  }

  const upsertDocument = async (type, id, doc) => {
    try {
      const upsertResult = await collection.upsert(`${type}_${id}`, doc);
      console.log('Upsert Result: ')
      console.log(upsertResult)
    } catch (err) {
      console.error(err)
    }
  }

  const getSubDocument = async (key, field) => {
    try {
      var result = await collection.lookupIn(key, [
        couchbase.LookupInSpec.get(field),
      ])
      var fieldValue = result.content[0].value

      console.log('LookupIn Result: ')
      console.log(result)

      console.log('Field Value: ')
      console.log(fieldValue)
    } catch (error) {
      console.error(error)
    }
  }

  upsertDocument(airline.type, airline.id, airline)
    .then(
      getSubDocument('airline_123', 'name')
        .then(() => process.exit(0))
    )
}

main()
Python - Upsert
                                      #!/usr/bin/python3
import sys
import couchbase.collection
import couchbase.subdocument as SD
from couchbase.cluster import Cluster, ClusterOptions
from couchbase.auth import PasswordAuthenticator
from couchbase.durability import ServerDurability, Durability
from datetime import timedelta

pa = PasswordAuthenticator('username', 'password')
cluster = Cluster('couchbase://127.0.0.1', ClusterOptions(pa))
bucket = cluster.bucket('travel-sample')
collection = bucket.default_collection()

try:
  document = dict(
    country="Iceland", callsign="ICEAIR", iata="FI", icao="ICE",
    id=123, name="Icelandair", type="airline"
  )
  result = collection.upsert(
    'airline_123',
    document,
    expiry=timedelta(minutes=1)
  )
  print("UPSERT SUCCESS")
  print("cas result:", result.cas)
except:
  print("exception:", sys.exc_info())

try:
  result = collection.lookup_in('airline_123', [SD.get('name')])
  name = result.content_as[str](0) # "United Kingdom"
  print("name:", name)

except:
  print("exception:", sys.exc_info()[0])
.NET - Upsert
                                      using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Couchbase;
using Couchbase.Core.Exceptions.KeyValue;
using Couchbase.KeyValue;

namespace CouchbaseDotNetExample
{
  class Program
  {
    static async Task Main(string[] args)
    {
      var cluster = await Cluster.ConnectAsync(
        "couchbase://127.0.0.1", "username", "password"
      );

      var bucket = await cluster.BucketAsync("travel-sample");
      var scope = await bucket.ScopeAsync("_default");
      var collection = await scope.CollectionAsync("_default");

      var content = new
        {
          Country = "Iceland",
          Callsign = "ICEAIR",
          Iata = "FI",
          Icao = "ICE",
          Id = 123,
          Name = "Icelandair",
          Type = "airline"
        };

      await collection.UpsertAsync("airline_123", content);

      try
      {
        var lookupResult = await collection.LookupInAsync("airline_123",
          new List<LookupInSpec>
            {
              LookupInSpec.Get("name")
            });

        var name = lookupResult.ContentAs<string>(0);
        Console.WriteLine($"New Document name = {name}");
      }
      catch (PathNotFoundException)
      {
        Console.WriteLine("Document not found!");
      }

      await cluster.DisposeAsync();
    }
  }
}
PHP - Upsert
                                      <?php
  $connectionString = "couchbase://127.0.0.1";
  $options = new \Couchbase\ClusterOptions();
  $options->credentials("username", "password");
  $cluster = new \Couchbase\Cluster($connectionString, $options);

  $bucket = $cluster->bucket("travel-sample");
  $collection = $bucket->defaultCollection();

  $content = ["country" => "Iceland",
              "callsign" => "ICEAIR",
              "iata" => "FI",
              "icao" => "ICE",
              "id" => 123,
              "name" => "Icelandair",
              "type" => "airline"];

  $collection->upsert("airline_123", $content);
  try {
    $result = $collection->lookupIn("airline_123", [new \Couchbase\LookupGetSpec("name")]);
    $name = $result->content(0);
    print("New Document name = $name");
  } catch (\Couchbase\PathNotFoundException $pnfe) {
    print("Sub-doc path not found!");
  } catch (\Couchbase\BaseException $ex) {
    print("Exception $ex\n");
  }
?>
Ruby - Upsert
                                      require "couchbase"
include Couchbase

options = Cluster::ClusterOptions.new
options.authenticate("username", "password")
cluster = Cluster.connect("couchbase://127.0.0.1", options)

bucket = cluster.bucket("travel-sample")
collection = bucket.default_collection

begin
  content = {"country" => "Iceland",
             "callsign" => "ICEAIR",
             "iata" => "FI",
             "icao" => "ICE",
             "id" => 123,
             "name" => "Icelandair",
             "type" => "airline"}
  collection.upsert("airline_123", content)
  result = collection.lookup_in("airline_123", [ LookupInSpec.get("name")])
  puts "New Document name: #{result.content(0)}"
rescue Couchbase::Error::PathNotFound => pnfe
  puts "Sub-doc path not found!"
rescue Couchbase::Error::DocumentNotFound => ex
  puts "Document not found!"
end
Scala - Upsert
                                      package com.couchbase

import com.couchbase.client.core.error.{CouchbaseException, DocumentNotFoundException}
import com.couchbase.client.core.error.subdoc.{PathExistsException,PathNotFoundException}
import com.couchbase.client.scala.Cluster
import com.couchbase.client.scala.json.{JsonObject, JsonObjectSafe}
import com.couchbase.client.scala.kv.LookupInSpec._
import com.couchbase.client.scala.kv.{LookupInResult, _}
import scala.util.{Failure, Success, Try}

object Program extends App {
  val cluster = Cluster.connect("127.0.0.1", "username", "password").get
  var bucket = cluster.bucket("travel-sample");
  val collection = bucket.defaultCollection

  val content = JsonObject("country" -> "Iceland",
                "callsign"-> "ICEAIR",
                "iata" -> "FI",
                "icao" -> "ICE",
                "id" -> 123,
                "name" -> "Icelandair",
                "type" -> "airline")

  collection.upsert("airline_123", content) match {
    case Success(result)    =>
    case Failure(exception) => println("Error: " + exception)
  }

  val result = collection.lookupIn("airline_123", Array(get("name")))
  result match {
    case Success(r) =>
      val str: Try[String] = r.contentAs[String](0)
      str match {
        case Success(s)   => println(s"New document name: ${s}")
        case Failure(err) => println(s"Error: ${err}")
      }
    case Failure(err: DocumentNotFoundException) => println("Document not found")
    case Failure(err: PathNotFoundException) => println("Sub-doc path not found!")
    case Failure(err: CouchbaseException) => println("Couchbase error: " + err)
    case Failure(err) => println("Error getting document: " + err)
  }
}
Golang - Upsert
                                      package main

import (
  "fmt"
  "log"
  "time"

  "github.com/couchbase/gocb/v2"
)

func main() {
  cluster, err := gocb.Connect("couchbase://127.0.0.1", gocb.ClusterOptions{
    Authenticator: gocb.PasswordAuthenticator{
      Username: "username",
      Password: "password",
    },
  })
  if err != nil {
    log.Fatal(err)
  }

  bucket := cluster.Bucket("travel-sample")
  err = bucket.WaitUntilReady(5*time.Second, nil)
  if err != nil {
    log.Fatal(err)
  }

  collection := bucket.DefaultCollection()

  type Doc struct {
    Id int `json:"id"`
    Type string `json:"type"`
    Name string `json:"name"`
    Iata string `json:"iata"`
    Icao string `json:"icao"`
    Callsign string `json:"callsign"`
    Country string `json:"country"`
  }

  document := Doc {
    Country: "Iceland",
    Callsign: "ICEAIR",
    Iata: "FI",
    Icao: "ICE",
    Id: 123,
    Name: "Icelandair",
    Type: "airline",
  }

  _, err = collection.Upsert("airline_123", &document, nil)
  if err != nil {
    log.Fatal(err)
  }

  options := []gocb.LookupInSpec{
    gocb.GetSpec("name", &gocb.GetSpecOptions{}),
  }
  results, err := collection.LookupIn("airline_123", options, &gocb.LookupInOptions{})
  if err != nil {
    log.Fatal(err)
  }

  var country string
  err = results.ContentAt(0, &country)
  if err != nil {
    log.Fatal(err)
  }

  fmt.Printf("New document name: %s\n", country)
}
C++ - Upsert
                                      #include <vector>
#include <string>
#include <iostream>

#include <libcouchbase/couchbase.h>

static void
check(lcb_STATUS err, const char* msg)
{
    if (err != LCB_SUCCESS) {
        std::cerr << "[ERROR] " << msg << ": " << lcb_strerror_short(err) << "\n";
        exit(EXIT_FAILURE);
    }
}

struct Result {
    std::string value{};
    lcb_STATUS status{ LCB_SUCCESS };
};

struct SubdocResults {
    lcb_STATUS status{ LCB_SUCCESS };
    std::vector<Result> entries{};
};

static void
sdget_callback(lcb_INSTANCE*, int, const lcb_RESPSUBDOC* resp)
{
    SubdocResults* results = nullptr;
    lcb_respsubdoc_cookie(resp, reinterpret_cast<void**>(&results));
    results->status = lcb_respsubdoc_status(resp);

    if (results->status != LCB_SUCCESS) {
        return;
    }

    std::size_t number_of_results = lcb_respsubdoc_result_size(resp);
    results->entries.resize(number_of_results);
    for (size_t idx = 0; idx < number_of_results; ++idx) {
        results->entries[idx].status = lcb_respsubdoc_result_status(resp, idx);
        const char* buf = nullptr;
        std::size_t buf_len = 0;
        lcb_respsubdoc_result_value(resp, idx, &buf, &buf_len);
        if (buf_len > 0) {
            results->entries[idx].value.assign(buf, buf_len);
        }
    }
}

int
main()
{
    std::string username{ "username" };
    std::string password{ "password" };
    std::string connection_string{ "couchbase://127.0.0.1" };
    std::string bucket_name{ "travel-sample" };

    lcb_CREATEOPTS* create_options = nullptr;
    check(lcb_createopts_create(&create_options, LCB_TYPE_BUCKET), "build options object for lcb_create");
    check(lcb_createopts_credentials(create_options, username.c_str(), username.size(), password.c_str(), password.size()),
        "assign credentials");
    check(lcb_createopts_connstr(create_options, connection_string.c_str(), connection_string.size()), "assign connection string");
    check(lcb_createopts_bucket(create_options, bucket_name.c_str(), bucket_name.size()), "assign bucket name");

    lcb_INSTANCE* instance = nullptr;
    check(lcb_create(&instance, create_options), "create lcb_INSTANCE");
    check(lcb_createopts_destroy(create_options), "destroy options object");
    check(lcb_connect(instance), "schedule connection");
    check(lcb_wait(instance, LCB_WAIT_DEFAULT), "wait for connection");
    check(lcb_get_bootstrap_status(instance), "check bootstrap status");

    std::string key{ "airline_123" };
    {
      std::string value{ R"({"country":"Iceland", "callsign":"ICEAIR", "iata":"FI", "icao":"ICE", "id":123, "name":"Icelandair", "type":"airline"})" };

      lcb_CMDSTORE* cmd = nullptr;
      check(lcb_cmdstore_create(&cmd, LCB_STORE_UPSERT), "create UPSERT command");
      check(lcb_cmdstore_key(cmd, key.c_str(), key.size()), "assign ID for UPSERT command");
      check(lcb_cmdstore_value(cmd, value.c_str(), value.size()), "assign value for UPSERT command");
      check(lcb_store(instance, nullptr, cmd), "schedule UPSERT command");
      check(lcb_cmdstore_destroy(cmd), "destroy UPSERT command");
      lcb_wait(instance, LCB_WAIT_DEFAULT);
    }

    lcb_install_callback(instance, LCB_CALLBACK_SDLOOKUP, reinterpret_cast<lcb_RESPCALLBACK>(sdget_callback));

    {
        SubdocResults results;

        lcb_SUBDOCSPECS* specs = nullptr;
        check(lcb_subdocspecs_create(&specs, 1), "create SUBDOC operations container");

        std::vector<std::string> paths{
            "name",
        };

        check(lcb_subdocspecs_get(specs, 0, 0, paths[0].c_str(), paths[0].size()), "create SUBDOC-GET operation");

        lcb_CMDSUBDOC* cmd = nullptr;
        check(lcb_cmdsubdoc_create(&cmd), "create SUBDOC command");
        check(lcb_cmdsubdoc_key(cmd, key.c_str(), key.size()), "assign ID to SUBDOC command");
        check(lcb_cmdsubdoc_specs(cmd, specs), "assign operations to SUBDOC command");
        check(lcb_subdoc(instance, &results, cmd), "schedule SUBDOC command");
        check(lcb_cmdsubdoc_destroy(cmd), "destroy SUBDOC command");
        check(lcb_subdocspecs_destroy(specs), "destroy SUBDOC operations");

        lcb_wait(instance, LCB_WAIT_DEFAULT);

        check(results.status, "status of SUBDOC operation");
        std::size_t idx = 0;
        for (const auto& entry : results.entries) {
            if (entry.status == LCB_SUCCESS) {
                std::cout << "New Document name: " << (entry.value.empty() ? "(no value)" : entry.value) << "\n";
            } else {
                std::cout << "code=" << lcb_strerror_short(entry.status) << "\n";
            }
            ++idx;
        }
    }

    lcb_destroy(instance);
    return 0;
}
couchbase-playground-drop-shadow

미래의 데이터베이스 과제에 미리 대비하세요

챌린지 1

경직된 데이터 모델로 개발 주기 단축

레거시 데이터베이스

  • 경직된 데이터 모델로 인해 애자일 개발이 어려워집니다.
  • 애플리케이션 개체가 테이블로 분할되어 재조립해야 하는 경우

Couchbase

  • 유연한 데이터 모델을 통해 민첩한 개발을 지원하는 JSON 데이터
  • 애플리케이션 객체를 직접 직렬화하거나 역직렬화할 수 있습니다.

챌린지 2

앱 성능 저하

레거시 데이터베이스

  • 관계형 모델링에는 종종 많은 수의 쓰기 및 읽기가 필요합니다.
  • 기본 제공 관리형 캐시 사용 불가

Couchbase

  • 캐싱은 응답성이 뛰어난 사용자 경험을 제공하고 지연 시간을 단축합니다.
  • 밀리초 미만의 지연 시간을 제공하는 내장형 관리형 캐시

도전 과제 3

새로운 기능에는 새로운 도구가 필요합니다.

레거시 데이터베이스

  • 캐싱, 분석, 이벤트, 모바일 동기화를 처리하기 위해 볼트로 고정된 도구와 시간이 많이 소요되는 통합이 필요합니다.
  • 데이터 액세스는 SQL로만 제한됩니다.
  • 더 많은 볼트 체결은 더 많은 작업과 더 많은 유지보수를 의미합니다.

Couchbase

  • SQL의 친숙함 제공(SQL++ 사용)
  • 제공 다중 모델 기능(전체 텍스트 검색, 쿼리, 키/값, 분석 등, 모두 동일한 데이터 세트에서)
  • 모바일과 엣지 데이터 실시간 자동 동기화

도전 과제 4

HA로 배포를 확장하기가 어렵습니다.

레거시 데이터베이스

  • 단일 서버 배포 및 고비용 수직 확장에 최적화됨
  • 수평적 확장은 비용이 많이 들고 어렵습니다.
  • 업그레이드 및 유지 관리로 인한 다운타임은 주말 오전 2시의 업무 시간과 동일합니다.

Couchbase

  • 핵심 분산 데이터 아키텍처
  • 마스터리스, 공유 없음 자동 수평 스케일링
  • 관리할 샤드 또는 파티션 키가 없습니다.
  • 자동 복제
  • 자동 장애 복구 및 리밸런싱
  • 유지 관리 및 업그레이드 시 다운타임이 필요하지 않습니다.