Ver3.0 ๐ ๋ผ๋ผ๋ฒจ๊ณผ AWS Lambda๋ก ์๋ฒ๋ฆฌ์ค Web3 ์ํคํ ์ฒ ์๋ฒฝ ๊ตฌ์ถํ๊ธฐ

๐ ๋ผ๋ผ๋ฒจ๊ณผ AWS Lambda๋ก ์๋ฒ๋ฆฌ์ค Web3 ์ํคํ ์ฒ ์๋ฒฝ ๊ตฌ์ถํ๊ธฐ
๋ธ๋ก์ฒด์ธ ์๋์ ์๋ก์ด ๋ฐฑ์๋ ์ ๋ต, ์๋ฒ๋ฆฌ์ค๋ก Web3 ์ธ์์ ์ด์ด๋ณด์! ๐
๐ฌ ์ผ, ์์ฆ Web3 ๊ฐ๋ฐํ๋ฉด์ ์๋ฒ ๊ด๋ฆฌ ๋๋ฌธ์ ๋จธ๋ฆฌ ์ํ์ง ์์? ๋ธ๋ก์ฒด์ธ ๋ ธ๋ ์ฐ๊ฒฐํ๊ณ , ์ง๊ฐ ์ฐ๋ํ๊ณ , ํธ๋์ญ์ ์ฒ๋ฆฌํ๋๋ฐ ์๋ฒ๊น์ง ์ ๊ฒฝ ์ฐ๋ ค๋ ์ ๋ง ๋ฏธ์น๊ฒ ๋๋ผ๊ณ . ๊ทธ๋ฐ๋ฐ ๋ง์ด์ผ, ๋ผ๋ผ๋ฒจ์ด๋ AWS Lambda๋ฅผ ์กฐํฉํ๋ฉด ์ด ๋ชจ๋ ๊ฒ ์์ฒญ ๊ฐ๋จํด์ง๋ค๋ ๊ฑฐ ์์? ๐
์ค๋์ ์น๊ตฌํํ ์ค๋ช ํ๋ฏ์ด ํธํ๊ฒ, ๊ทผ๋ฐ ์ ๋๋ก ๊น๊ฒ! ๋ผ๋ผ๋ฒจ๊ณผ AWS Lambda๋ก ์๋ฒ๋ฆฌ์ค Web3 ๊ตฌ์กฐ๋ฅผ ์ด๋ป๊ฒ ๊ตฌ์ถํ๋์ง ์๋ ค์ค๊ฒ. ์ด๊ฑฐ ํ ๋ฒ ์ ๋๋ก ๋ฐฐ์๋๋ฉด ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ๋ธ๋ก์ฒด์ธ ๊ฐ๋ฐ ์ฌ๋ฅ์ ๊ณต์ ํ ๋๋ ์์ฒญ ์ ์ฉํ๊ฑฐ๋ ! ๐ฅ
๐ค ์ ์๋ฒ๋ฆฌ์ค + Web3์ธ๊ฐ?
๋จผ์ ์ด์ผ๊ธฐํด๋ณด์. Web3 ๊ฐ๋ฐํ๋ฉด์ ์ ํต์ ์ธ ์๋ฒ ๊ตฌ์กฐ๋ฅผ ์ฐ๋ฉด ๋ญ๊ฐ ๋ฌธ์ ์ผ๊น? ๐คทโโ๏ธ
์ ํต์ ์ธ ์๋ฒ ๊ตฌ์กฐ์ ๋ฌธ์ ์ ๋ค:
1๏ธโฃ ํธ๋ํฝ ์์ธก ๋ถ๊ฐ - NFT ๋ฏผํ ์ด๋ ์์ด๋๋ ์ด๋ฒคํธ ๋ ๊ฐ์๊ธฐ ํธ๋ํฝ์ด ํญ์ฆํ๋ฉด? ์๋ฒ ๋ค์ด! ๐ฅ
2๏ธโฃ ๋น์ฉ ๋นํจ์จ - 24์๊ฐ ์๋ฒ ๋๋ฆฌ๋๋ฐ ์ค์ ๋ก๋ ํ๋ฃจ ์ค ๋ช ์๊ฐ๋ง ์ฌ์ฉํ๋ค๋ฉด? ๋ ๋ญ๋น!
3๏ธโฃ ํ์ฅ์ฑ ๋ฌธ์ - ๊ฐ์๊ธฐ ์ฌ์ฉ์๊ฐ ๋์ด๋๋ฉด ์๋ฒ ์ฆ์คํ๊ณ ๋ก๋๋ฐธ๋ฐ์ ์ค์ ํ๊ณ ... ๋จธ๋ฆฌ ์ํ ๐ต
4๏ธโฃ ์ ์ง๋ณด์ ์ง์ฅ - ๋ณด์ ํจ์น, OS ์ ๋ฐ์ดํธ, ์๋ฒ ๋ชจ๋ํฐ๋ง... ๊ฐ๋ฐ์ ์ง์คํ๊ณ ์ถ์๋ฐ!
์ด๋ฐ ๋ฌธ์ ๋ค์ ํ ๋ฐฉ์ ํด๊ฒฐํ๋ ๊ฒ ๋ฐ๋ก ์๋ฒ๋ฆฌ์ค ์ํคํ ์ฒ์ผ! ๐ฏ
โจ ์๋ฒ๋ฆฌ์ค + Web3์ ์ฅ์
โข ์๋ ์ค์ผ์ผ๋ง: ์ฌ์ฉ์ 1๋ช ์ด๋ 10๋ง ๋ช ์ด๋ ์๋์ผ๋ก ๋์
โข ๋น์ฉ ์ต์ ํ: ์ค์ ์ฌ์ฉํ ๋งํผ๋ง ๊ณผ๊ธ (๋ฐ๋ฆฌ์ด ๋จ์!)
โข ๋น ๋ฅธ ๋ฐฐํฌ: ์ฝ๋ ํธ์ํ๋ฉด ๋! ์๋ฒ ์ค์ ํ์ ์์
โข ๊ธ๋ก๋ฒ ๋ถ์ฐ: ์ ์ธ๊ณ ์ด๋์๋ ๋น ๋ฅธ ์๋ต ์๋
โข ๊ฐ๋ฐ ์ง์ค: ์ธํ๋ผ ๊ฑฑ์ ์์ด ๋น์ฆ๋์ค ๋ก์ง์๋ง ์ง์ค
๐๏ธ ์ํคํ ์ฒ ์ ์ฒด ๊ตฌ์กฐ ์ดํดํ๊ธฐ
๋ณธ๊ฒฉ์ ์ผ๋ก ๋ค์ด๊ฐ๊ธฐ ์ ์ ์ ์ฒด ๊ทธ๋ฆผ์ ๋จผ์ ๊ทธ๋ ค๋ณด์. ์ฐ๋ฆฌ๊ฐ ๋ง๋ค ๊ตฌ์กฐ๋ ์ด๋ ๊ฒ ์๊ฒผ์ด:
๐ ์์คํ ๊ตฌ์ฑ ์์
์ฌ์ฉ์ ๋ธ๋ผ์ฐ์ โ API Gateway โ Lambda (Laravel) โ ๋ธ๋ก์ฒด์ธ ๋ ธ๋
์ฌ๊ธฐ์ ์ถ๊ฐ๋ก:
โข RDS/Aurora Serverless: ๋ฐ์ดํฐ๋ฒ ์ด์ค (ํ์์)
โข S3: ์ ์ ํ์ผ ์ ์ฅ
โข CloudFront: CDN
โข DynamoDB: ์ธ์ /์บ์ ์ ์ฅ
โข EventBridge: ์ค์ผ์ค๋ง ์์
๐ก Pro Tip
๋ผ๋ผ๋ฒจ์ ์๋ ์ ํต์ ์ธ ์น ํ๋ ์์ํฌ์ง๋ง, Bref๋ผ๋ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ๋ฉด Lambda์์ ์๋ฒฝํ๊ฒ ๋์๊ฐ! Bref๋ PHP๋ฅผ Lambda์์ ์คํํ ์ ์๊ฒ ํด์ฃผ๋ ์คํ์์ค ํ๋ก์ ํธ์ผ. ์ด๊ฒ ๊ฒ์ ์ฒด์ธ์ ์ง! ๐ฎ
๐ ๏ธ ์ค์ ๊ตฌ์ถ ๋จ๊ณ๋ณ ๊ฐ์ด๋
์, ์ด์ ์ง์ง ๋ง๋ค์ด๋ณด์! ๋จ๊ณ๋ณ๋ก ์ฐจ๊ทผ์ฐจ๊ทผ ๋ฐ๋ผ์ค๋ฉด ๋ผ. ๐
๋จผ์ ํ์ํ ๋๊ตฌ๋ค์ ์ค์นํด์ผ ํด. ํฐ๋ฏธ๋ ์ด๊ณ ์์!
# Composer ์ค์น (์ด๋ฏธ ์์ผ๋ฉด ์คํต)
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
# Serverless Framework ์ค์น
npm install -g serverless
# AWS CLI ์ค์น (Mac ๊ธฐ์ค)
brew install awscli
# AWS ์๊ฒฉ์ฆ๋ช
์ค์
aws configure
AWS ์๊ฒฉ์ฆ๋ช ์ค์ ํ ๋๋ IAM์์ ์ ์ ํ ๊ถํ์ ๊ฐ์ง ์ฌ์ฉ์๋ฅผ ๋ง๋ค์ด์ผ ํด. ์ต์ํ Lambda, API Gateway, CloudFormation ๊ถํ์ ํ์ํ์ง! ๐
# ๋ผ๋ผ๋ฒจ ํ๋ก์ ํธ ์์ฑ
composer create-project laravel/laravel web3-serverless
cd web3-serverless
# Bref ์ค์น
composer require bref/bref bref/laravel-bridge
# Serverless ์ค์ ํ์ผ ์์ฑ
php artisan vendor:publish --tag=serverless-config
์ด์ serverless.yml ํ์ผ์ ์ค์ ํด์ผ ํด. ์ด๊ฒ ํต์ฌ์ด์ผ!
service: web3-serverless-app
provider:
name: aws
region: ap-northeast-2 # ์์ธ ๋ฆฌ์
runtime: provided.al2
environment:
APP_ENV: production
APP_KEY: ${ssm:/web3-app/app-key}
DB_CONNECTION: mysql
DB_HOST: ${ssm:/web3-app/db-host}
BLOCKCHAIN_RPC_URL: ${ssm:/web3-app/rpc-url}
functions:
web:
handler: public/index.php
timeout: 28
layers:
- ${bref:layer.php-81-fpm}
events:
- httpApi: '*'
artisan:
handler: artisan
timeout: 120
layers:
- ${bref:layer.php-81}
- ${bref:layer.console}
plugins:
- ./vendor/bref/bref
โ ๏ธ ์ฃผ์์ฌํญ
Lambda์ ์ต๋ ์คํ ์๊ฐ์ 15๋ถ(900์ด)์ด์ผ. ํ์ง๋ง API Gateway๋ฅผ ํตํ ์์ฒญ์ 29์ด๊ฐ ์ต๋! ๊ทธ๋์ timeout์ 28์ด๋ก ์ค์ ํ ๊ฑฐ์ผ. ๊ธด ์์ ์ ๋น๋๊ธฐ๋ก ์ฒ๋ฆฌํด์ผ ํด!
์ด์ ๋ธ๋ก์ฒด์ธ๊ณผ ํต์ ํ ์ค๋น๋ฅผ ํด๋ณด์! PHP์์ Web3๋ฅผ ๋ค๋ฃจ๋ ๋ํ์ ์ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ web3.php์ผ.
# Web3.php ์ค์น
composer require sc0vu/web3.php
# Ethereum ๊ด๋ จ ์ถ๊ฐ ํจํค์ง
composer require kornrunner/keccak
๊ฐ๋จํ Web3 ์๋น์ค ํด๋์ค๋ฅผ ๋ง๋ค์ด๋ณด์:
<?php
namespace App\Services;
use Web3\Web3;
use Web3\Contract;
use Web3\Utils;
class Web3Service
{
protected $web3;
protected $eth;
public function __construct()
{
$this->web3 = new Web3(env('BLOCKCHAIN_RPC_URL'));
$this->eth = $this->web3->eth;
}
// ์ง๊ฐ ์์ก ์กฐํ
public function getBalance(string $address)
{
$balance = null;
$this->eth->getBalance($address, function ($err, $result) use (&$balance) {
if ($err !== null) {
throw new \Exception($err->getMessage());
}
$balance = $result;
});
return Utils::fromWei($balance, 'ether');
}
// ์ค๋งํธ ์ปจํธ๋ํธ ํธ์ถ
public function callContract(string $contractAddress, array $abi, string $method, array $params = [])
{
$contract = new Contract($this->web3->provider, $abi);
$contract->at($contractAddress);
$result = null;
$contract->call($method, ...$params, function ($err, $res) use (&$result) {
if ($err !== null) {
throw new \Exception($err->getMessage());
}
$result = $res;
});
return $result;
}
// NFT ๋ฉํ๋ฐ์ดํฐ ์กฐํ
public function getNFTMetadata(string $contractAddress, int $tokenId)
{
$abi = json_decode(file_get_contents(storage_path('abi/erc721.json')), true);
$tokenURI = $this->callContract($contractAddress, $abi, 'tokenURI', [$tokenId]);
// IPFS URL ์ฒ๋ฆฌ
if (str_starts_with($tokenURI, 'ipfs://')) {
$tokenURI = str_replace('ipfs://', 'https://ipfs.io/ipfs/', $tokenURI);
}
return json_decode(file_get_contents($tokenURI), true);
}
}
์ด์ ์ค์ ๋ก ์ฌ์ฉํ API๋ฅผ ๋ง๋ค์ด๋ณด์. ๋ผ๋ผ๋ฒจ์ ๋ผ์ฐํ ๊ณผ ์ปจํธ๋กค๋ฌ๋ฅผ ํ์ฉํ ๊ฑฐ์ผ!
// routes/api.php
use App\Http\Controllers\Web3Controller;
Route::prefix('v1')->group(function () {
// ์ง๊ฐ ์ ๋ณด ์กฐํ
Route::get('/wallet/{address}', [Web3Controller::class, 'getWalletInfo']);
// NFT ์ ๋ณด ์กฐํ
Route::get('/nft/{contract}/{tokenId}', [Web3Controller::class, 'getNFT']);
// ํธ๋์ญ์
์ ์ก
Route::post('/transaction/send', [Web3Controller::class, 'sendTransaction']);
// ์ค๋งํธ ์ปจํธ๋ํธ ์ํธ์์ฉ
Route::post('/contract/interact', [Web3Controller::class, 'interactContract']);
});
์ปจํธ๋กค๋ฌ ๊ตฌํ:
<?php
namespace App\Http\Controllers;
use App\Services\Web3Service;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class Web3Controller extends Controller
{
protected $web3Service;
public function __construct(Web3Service $web3Service)
{
$this->web3Service = $web3Service;
}
public function getWalletInfo(string $address)
{
// ์บ์ฑ์ผ๋ก ์ฑ๋ฅ ์ต์ ํ (Lambda๋ ์ฝ๋ ์คํํธ๊ฐ ์์ผ๋๊น!)
$cacheKey = "wallet:{$address}";
return Cache::remember($cacheKey, 300, function () use ($address) {
try {
$balance = $this->web3Service->getBalance($address);
return response()->json([
'success' => true,
'data' => [
'address' => $address,
'balance' => $balance,
'balance_usd' => $this->convertToUSD($balance),
]
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'error' => $e->getMessage()
], 500);
}
});
}
public function getNFT(string $contract, int $tokenId)
{
try {
$metadata = $this->web3Service->getNFTMetadata($contract, $tokenId);
return response()->json([
'success' => true,
'data' => [
'contract' => $contract,
'tokenId' => $tokenId,
'name' => $metadata['name'] ?? null,
'description' => $metadata['description'] ?? null,
'image' => $metadata['image'] ?? null,
'attributes' => $metadata['attributes'] ?? [],
]
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'error' => $e->getMessage()
], 500);
}
}
private function convertToUSD(string $ethAmount): float
{
// ์ค์ ๋ก๋ ์ธ๋ถ API์์ ๊ฐ๊ฒฉ ๊ฐ์ ธ์์ผ ํจ
$ethPrice = Cache::remember('eth_price', 60, function () {
// CoinGecko API ๋ฑ ํ์ฉ
return 2000; // ์์ ๊ฐ๊ฒฉ
});
return floatval($ethAmount) * $ethPrice;
}
}
Web3 ์ฑ์ด๋ผ๋ ์ฌ์ฉ์ ์ ๋ณด๋ ํธ๋์ญ์ ํ์คํ ๋ฆฌ ๊ฐ์ ๊ฑด DB์ ์ ์ฅํด์ผ ํ ๋๊ฐ ์์ง. Aurora Serverless๋ฅผ ์ฌ์ฉํ๋ฉด DB๋ ์๋ฒ๋ฆฌ์ค๋ก ๋ง๋ค ์ ์์ด! ๐พ
# serverless.yml์ ์ถ๊ฐ
resources:
Resources:
AuroraCluster:
Type: AWS::RDS::DBCluster
Properties:
Engine: aurora-mysql
EngineMode: serverless
DatabaseName: web3app
MasterUsername: admin
MasterUserPassword: ${ssm:/web3-app/db-password~true}
ScalingConfiguration:
MinCapacity: 2
MaxCapacity: 16
AutoPause: true
SecondsUntilAutoPause: 300
๋ผ๋ผ๋ฒจ ๋ง์ด๊ทธ๋ ์ด์ ์์:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateWeb3Tables extends Migration
{
public function up()
{
// ์ฌ์ฉ์ ์ง๊ฐ ํ
์ด๋ธ
Schema::create('wallets', function (Blueprint $table) {
$table->id();
$table->string('address', 42)->unique();
$table->string('chain')->default('ethereum');
$table->timestamp('first_seen_at');
$table->timestamp('last_activity_at')->nullable();
$table->timestamps();
$table->index(['chain', 'last_activity_at']);
});
// ํธ๋์ญ์
๋ก๊ทธ
Schema::create('transaction_logs', function (Blueprint $table) {
$table->id();
$table->string('tx_hash', 66)->unique();
$table->string('from_address', 42);
$table->string('to_address', 42);
$table->decimal('amount', 36, 18);
$table->string('status')->default('pending');
$table->text('error_message')->nullable();
$table->timestamps();
$table->index('status');
$table->index('created_at');
});
// NFT ์์ ๊ถ ์บ์
Schema::create('nft_ownerships', function (Blueprint $table) {
$table->id();
$table->string('contract_address', 42);
$table->unsignedBigInteger('token_id');
$table->string('owner_address', 42);
$table->json('metadata')->nullable();
$table->timestamp('synced_at');
$table->timestamps();
$table->unique(['contract_address', 'token_id']);
$table->index('owner_address');
});
}
public function down()
{
Schema::dropIfExists('nft_ownerships');
Schema::dropIfExists('transaction_logs');
Schema::dropIfExists('wallets');
}
}
๋ธ๋ก์ฒด์ธ ํธ๋์ญ์ ๋ชจ๋ํฐ๋ง์ด๋ ๋๋์ NFT ๋ฉํ๋ฐ์ดํฐ ์์ง ๊ฐ์ ๊ธด ์์ ์ ๋น๋๊ธฐ๋ก ์ฒ๋ฆฌํด์ผ ํด. SQS์ Lambda๋ฅผ ์กฐํฉํ๋ฉด ์๋ฒฝ! ๐ฏ
# serverless.yml์ ์ถ๊ฐ
functions:
# ... ๊ธฐ์กด ํจ์๋ค
worker:
handler: worker.php
timeout: 300
layers:
- ${bref:layer.php-81}
events:
- sqs:
arn:
Fn::GetAtt:
- WorkerQueue
- Arn
batchSize: 1
resources:
Resources:
WorkerQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: web3-worker-queue
VisibilityTimeout: 360
Job ํด๋์ค ๋ง๋ค๊ธฐ:
<?php
namespace App\Jobs;
use App\Services\Web3Service;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
class SyncNFTMetadata implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable;
protected $contractAddress;
protected $tokenIds;
public function __construct(string $contractAddress, array $tokenIds)
{
$this->contractAddress = $contractAddress;
$this->tokenIds = $tokenIds;
}
public function handle(Web3Service $web3Service)
{
foreach ($this->tokenIds as $tokenId) {
try {
$metadata = $web3Service->getNFTMetadata($this->contractAddress, $tokenId);
// DB์ ์ ์ฅ
\DB::table('nft_ownerships')->updateOrInsert(
[
'contract_address' => $this->contractAddress,
'token_id' => $tokenId,
],
[
'metadata' => json_encode($metadata),
'synced_at' => now(),
'updated_at' => now(),
]
);
// Rate limiting (๋ธ๋ก์ฒด์ธ ๋
ธ๋ ๋ณดํธ)
usleep(100000); // 0.1์ด ๋๊ธฐ
} catch (\Exception $e) {
\Log::error("Failed to sync NFT: {$this->contractAddress}#{$tokenId}", [
'error' => $e->getMessage()
]);
}
}
}
}
Job ๋์คํจ์น:
// ์ปจํธ๋กค๋ฌ์์
use App\Jobs\SyncNFTMetadata;
public function syncCollection(Request $request)
{
$contractAddress = $request->input('contract_address');
$totalSupply = $request->input('total_supply');
// 100๊ฐ์ฉ ๋๋ ์ ์ฒ๋ฆฌ
$chunks = array_chunk(range(1, $totalSupply), 100);
foreach ($chunks as $chunk) {
SyncNFTMetadata::dispatch($contractAddress, $chunk);
}
return response()->json([
'success' => true,
'message' => 'Sync job queued',
'jobs' => count($chunks)
]);
}
๋๋์ด ๋ฐฐํฌํ ์๊ฐ! ๊ธด์ฅ๋์ง? ๐
# ํ๊ฒฝ ๋ณ์ ์ค์ (AWS Systems Manager Parameter Store)
aws ssm put-parameter \
--name /web3-app/app-key \
--value "base64:your-app-key-here" \
--type SecureString
aws ssm put-parameter \
--name /web3-app/rpc-url \
--value "https://mainnet.infura.io/v3/YOUR-PROJECT-ID" \
--type SecureString
# ๋ฐฐํฌ!
serverless deploy
# ๋ฐฐํฌ ์๋ฃ๋๋ฉด ์๋ํฌ์ธํธ URL์ด ๋์
# ์: https://abc123.execute-api.ap-northeast-2.amazonaws.com
ํ ์คํธํด๋ณด์:
# ์ง๊ฐ ์ ๋ณด ์กฐํ
curl https://your-api-url.com/v1/wallet/0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb
# NFT ์ ๋ณด ์กฐํ
curl https://your-api-url.com/v1/nft/0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D/1
โก ์ฑ๋ฅ ์ต์ ํ ์ ๋ต
์๋ฒ๋ฆฌ์ค๋ผ๊ณ ํด์ ์ต์ ํ๋ฅผ ์ ํด๋ ๋๋ ๊ฑด ์๋์ผ! ์คํ๋ ค ๋ ์ ๊ฒฝ ์จ์ผ ํ ๋ถ๋ถ๋ค์ด ์์ด. ๐
1. ์ฝ๋ ์คํํธ ์ต์ํ
Lambda์ ๊ฐ์ฅ ํฐ ๋จ์ ์ด ์ฝ๋ ์คํํธ์ผ. ํจ์๊ฐ ์ฒ์ ์คํ๋๊ฑฐ๋ ํ๋์ ์ฌ์ฉ๋์ง ์์์ ๋ ์ด๊ธฐํ ์๊ฐ์ด ๊ฑธ๋ฆฌ๋ ๊ฑฐ์ง.
ํด๊ฒฐ ๋ฐฉ๋ฒ:
โข Provisioned Concurrency ์ฌ์ฉ - ๋ฏธ๋ฆฌ ์๋ฐ์ ๋ ์ธ์คํด์ค ์ ์ง
โข ๋ ์ด์ด ์ต์ ํ - ์์ฃผ ๋ฐ๋์ง ์๋ ์์กด์ฑ์ ๋ ์ด์ด๋ก ๋ถ๋ฆฌ
โข ์ฝ๋ ํฌ๊ธฐ ์ค์ด๊ธฐ - ๋ถํ์ํ ํจํค์ง ์ ๊ฑฐ
# serverless.yml
functions:
web:
handler: public/index.php
provisionedConcurrency: 2 # ํญ์ 2๊ฐ ์ธ์คํด์ค ์ ์ง
layers:
- ${bref:layer.php-81-fpm}
- arn:aws:lambda:ap-northeast-2:xxx:layer:vendor:1 # ์ปค์คํ
๋ ์ด์ด
2. ์บ์ฑ ์ ๋ต
๋ธ๋ก์ฒด์ธ ๋ฐ์ดํฐ๋ ๋ณ๊ฒฝ์ด ๋๋ฆฌ๋๊น ์ ๊ทน์ ์ผ๋ก ์บ์ฑํ์!
<?php
namespace App\Services;
use Illuminate\Support\Facades\Cache;
class CachedWeb3Service extends Web3Service
{
// ๋ธ๋ก ๋ฒํธ ์บ์ฑ (10์ด)
public function getCurrentBlock(): int
{
return Cache::remember('current_block', 10, function () {
return parent::getCurrentBlock();
});
}
// NFT ๋ฉํ๋ฐ์ดํฐ ์บ์ฑ (1์๊ฐ)
public function getNFTMetadata(string $contractAddress, int $tokenId)
{
$cacheKey = "nft:{$contractAddress}:{$tokenId}";
return Cache::remember($cacheKey, 3600, function () use ($contractAddress, $tokenId) {
return parent::getNFTMetadata($contractAddress, $tokenId);
});
}
// ๊ฐ์ค ๊ฐ๊ฒฉ ์บ์ฑ (30์ด)
public function getGasPrice()
{
return Cache::remember('gas_price', 30, function () {
return parent::getGasPrice();
});
}
}
DynamoDB๋ฅผ ์บ์ ์คํ ์ด๋ก ์ฌ์ฉํ๋ฉด ๋ ์ข์:
# config/cache.php
'dynamodb' => [
'driver' => 'dynamodb',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'ap-northeast-2'),
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
'endpoint' => env('DYNAMODB_ENDPOINT'),
],
3. ์ฐ๊ฒฐ ํ๋ง ๋ฐ ์ฌ์ฌ์ฉ
<?php
namespace App\Services;
class OptimizedWeb3Service
{
private static $web3Instance;
private static $contractInstances = [];
protected function getWeb3()
{
if (self::$web3Instance === null) {
self::$web3Instance = new \Web3\Web3(env('BLOCKCHAIN_RPC_URL'));
}
return self::$web3Instance;
}
protected function getContract(string $address, array $abi)
{
$key = md5($address . json_encode($abi));
if (!isset(self::$contractInstances[$key])) {
$contract = new \Web3\Contract($this->getWeb3()->provider, $abi);
$contract->at($address);
self::$contractInstances[$key] = $contract;
}
return self::$contractInstances[$key];
}
}
4. ๋ณ๋ ฌ ์ฒ๋ฆฌ
์ฌ๋ฌ ๋ธ๋ก์ฒด์ธ ํธ์ถ์ ๋์์ ์ฒ๋ฆฌํ๋ฉด ์๋๊ฐ ์์ฒญ ๋นจ๋ผ์ ธ!
use GuzzleHttp\Promise;
use GuzzleHttp\Client;
public function getBatchBalances(array $addresses): array
{
$client = new Client();
$promises = [];
foreach ($addresses as $address) {
$promises[$address] = $client->postAsync(env('BLOCKCHAIN_RPC_URL'), [
'json' => [
'jsonrpc' => '2.0',
'method' => 'eth_getBalance',
'params' => [$address, 'latest'],
'id' => 1
]
]);
}
$results = Promise\Utils::unwrap($promises);
$balances = [];
foreach ($results as $address => $response) {
$data = json_decode($response->getBody(), true);
$balances[$address] = hexdec($data['result']);
}
return $balances;
}
๐ ๋ณด์ ๊ณ ๋ ค์ฌํญ
Web3 ์ฑ์ ๋๊ณผ ์ง๊ฒฐ๋๋๊น ๋ณด์์ด ์ ๋ง์ ๋ง ์ค์ํด! ๐ก๏ธ
| ๋ณด์ ์์ญ | ์ํ | ๋์ ๋ฐฉ๋ฒ |
|---|---|---|
| API ์ธ์ฆ | ๋ฌด๋จ ์ ๊ทผ | JWT ํ ํฐ, API Key, Rate Limiting |
| ํ๋ผ์ด๋น ํค | ํค ๋ ธ์ถ | AWS KMS, Secrets Manager ์ฌ์ฉ |
| ํธ๋์ญ์ | ์ฌ์ ์ก ๊ณต๊ฒฉ | Nonce ๊ด๋ฆฌ, ํ์์คํฌํ ๊ฒ์ฆ |
| ์ค๋งํธ ์ปจํธ๋ํธ | ์ ์์ ์ปจํธ๋ํธ | ํ์ดํธ๋ฆฌ์คํธ, ABI ๊ฒ์ฆ |
| ๋ฐ์ดํฐ | SQL Injection | ORM ์ฌ์ฉ, ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ |
โ ๏ธ ์ ๋ ํ์ง ๋ง์์ผ ํ ๊ฒ๋ค
1. ํ๋ผ์ด๋น ํค๋ฅผ ์ฝ๋๋ ํ๊ฒฝ๋ณ์์ ํ๋ฌธ์ผ๋ก ์ ์ฅ โ
2. ์ฌ์ฉ์ ์ ๋ ฅ์ ๊ฒ์ฆ ์์ด ๋ธ๋ก์ฒด์ธ์ ์ ์ก โ
3. ์๋ฌ ๋ฉ์์ง์ ๋ฏผ๊ฐํ ์ ๋ณด ํฌํจ โ
4. Rate Limiting ์์ด ๊ณต๊ฐ API ์ด์ โ
โ ๋ณด์ ์ฒดํฌ๋ฆฌ์คํธ
โข AWS WAF ์ค์ ์ผ๋ก DDoS ๋ฐฉ์ด
โข CloudWatch ๋ก๊ทธ ๋ชจ๋ํฐ๋ง ํ์ฑํ
โข IAM ์ญํ ์ต์ ๊ถํ ์์น ์ ์ฉ
โข ์ ๊ธฐ์ ์ธ ์์กด์ฑ ์ ๋ฐ์ดํธ (composer update)
โข ํธ๋์ญ์ ๊ธ์ก ์ ํ ์ค์
โข ๋ฉํฐ์๊ทธ ์ง๊ฐ ์ฌ์ฉ (๊ณ ์ก ๊ฑฐ๋์)
// ๋ณด์ ๋ฏธ๋ค์จ์ด ์์
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class Web3SecurityMiddleware
{
public function handle(Request $request, Closure $next)
{
// 1. Rate Limiting
$key = 'api_calls:' . $request->ip();
$calls = \Cache::get($key, 0);
if ($calls > 100) { // ๋ถ๋น 100ํ ์ ํ
return response()->json(['error' => 'Rate limit exceeded'], 429);
}
\Cache::put($key, $calls + 1, 60);
// 2. ์ฃผ์ ๊ฒ์ฆ
if ($request->has('address')) {
$address = $request->input('address');
if (!$this->isValidAddress($address)) {
return response()->json(['error' => 'Invalid address'], 400);
}
}
// 3. ์๋ช
๊ฒ์ฆ (์ ํ์ )
if ($request->has('signature')) {
if (!$this->verifySignature($request)) {
return response()->json(['error' => 'Invalid signature'], 401);
}
}
return $next($request);
}
private function isValidAddress(string $address): bool
{
return preg_match('/^0x[a-fA-F0-9]{40}$/', $address);
}
private function verifySignature(Request $request): bool
{
// EIP-191 ์๋ช
๊ฒ์ฆ ๋ก์ง
// ์ค์ ๊ตฌํ ํ์
return true;
}
}
๐ฐ ๋น์ฉ ์ต์ ํ ํ
์๋ฒ๋ฆฌ์ค๊ฐ ์ ๋ ดํ๊ธด ํ๋ฐ, ์๋ชป ์ฐ๋ฉด ์คํ๋ ค ๋น์ ์ ์์ด. ๋๋ํ๊ฒ ์ฐ๋ ๋ฒ์ ์๋ ค์ค๊ฒ! ๐ก
โ ๋น์ฉ ์ ๊ฐ ์ ๋ต
1. ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ
Lambda๋ ๋ฉ๋ชจ๋ฆฌ ํฌ๊ธฐ๋ก ๊ณผ๊ธ๋ผ. ํ์ํ ๋งํผ๋ง ํ ๋นํ์!
2. ์คํ ์๊ฐ ๋จ์ถ
์ฝ๋ ์ต์ ํ๋ก ์คํ ์๊ฐ์ ์ค์ด๋ฉด ๋น์ฉ๋ ์ค์ด!
3. ์บ์ฑ ์ ๊ทน ํ์ฉ
๊ฐ์ ์์ฒญ์ ๋ฐ๋ณตํ์ง ๋ง๊ณ ์บ์๋ฅผ ์จ!
4. ๋ฐฐ์น ์ฒ๋ฆฌ
์ฌ๋ฌ ์์ ์ ๋ชจ์์ ํ ๋ฒ์ ์ฒ๋ฆฌํ๋ฉด ํจ์จ์ !
โ ๏ธ ๋น์ฉ ํญํ ์ฃผ์
1. ๋ฌดํ ๋ฃจํ
์ฌ๊ท ํธ์ถ์ด๋ ๋ฌดํ ๋ฃจํ๋ ๋น์ฉ ํญํ! ๐ฑ
2. ๊ณผ๋ํ ๋ก๊น
CloudWatch Logs๋ ๋์ด์ผ. ํ์ํ ๊ฒ๋ง ๋ก๊น !
3. ๋ถํ์ํ Provisioned Concurrency
ํญ์ ์ผ๋ ํ์ ์์ผ๋ฉด ๋์!
4. ํฐ ํจํค์ง
๋ฐฐํฌ ํจํค์ง๊ฐ ํฌ๋ฉด ์ฝ๋ ์คํํธ๋ ๋๋ฆฌ๊ณ ๋น์ฉ๋ ์ฌ๋ผ๊ฐ!
๐ก ์ค์ ๋น์ฉ ์์
์ 100๋ง ์์ฒญ, ํ๊ท ์คํ์๊ฐ 200ms, ๋ฉ๋ชจ๋ฆฌ 512MB ๊ธฐ์ค:
โข Lambda: ์ฝ $3.50
โข API Gateway: ์ฝ $3.50
โข DynamoDB (์บ์): ์ฝ $2.00
โข CloudWatch Logs: ์ฝ $1.00
์ด ์ฝ $10/์ ๐
๊ฐ์ ํธ๋ํฝ์ EC2๋ก ์ฒ๋ฆฌํ๋ฉด ์ต์ $50~100/์์ ๋์!
๐ ๋ชจ๋ํฐ๋ง ๋ฐ ๋๋ฒ๊น
์๋ฒ๋ฆฌ์ค๋ ์๋ฒ๊ฐ ์์ด์ ๋๋ฒ๊น ์ด ์ด๋ ค์ธ ๊ฒ ๊ฐ์ง? ๊ฑฑ์ ๋ง! ๋ฐฉ๋ฒ์ด ์์ด. ๐ง
CloudWatch ๋ก๊ทธ ํ์ฉ
<?php
// ๊ตฌ์กฐํ๋ ๋ก๊น
\Log::info('NFT metadata fetched', [
'contract' => $contractAddress,
'token_id' => $tokenId,
'execution_time' => $executionTime,
'cache_hit' => $cacheHit,
]);
// ์๋ฌ ๋ก๊น
\Log::error('Blockchain RPC failed', [
'method' => 'eth_getBalance',
'address' => $address,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
CloudWatch Insights ์ฟผ๋ฆฌ ์์:
# ์๋ฌ์จ ํ์ธ
fields @timestamp, @message
| filter @message like /ERROR/
| stats count() by bin(5m)
# ํ๊ท ์คํ ์๊ฐ
fields @timestamp, @duration
| stats avg(@duration), max(@duration), min(@duration)
# ํน์ ์ปจํธ๋ํธ ํธ์ถ ์ถ์
fields @timestamp, contract, token_id
| filter contract = "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D"
| sort @timestamp desc
X-Ray ํธ๋ ์ด์ฑ
AWS X-Ray๋ฅผ ์ฌ์ฉํ๋ฉด ์์ฒญ์ ์ ์ฒด ํ๋ฆ์ ์๊ฐํํ ์ ์์ด!
# serverless.yml
provider:
tracing:
lambda: true
apiGateway: true
# PHP์์ X-Ray ์ฌ์ฉ
composer require aws/aws-xray-sdk-php
๋ก์ปฌ ํ ์คํธ
# SAM CLI๋ก ๋ก์ปฌ ํ
์คํธ
sam local start-api
# ๋๋ Serverless Offline ํ๋ฌ๊ทธ์ธ
serverless plugin install -n serverless-offline
serverless offline start
๐ ์ค์ ํ์ฉ ์ฌ๋ก
์ด๋ก ์ ์ถฉ๋ถํ ๋ฐฐ์ ์ผ๋ ์ค์ ๋ก ์ด๋ป๊ฒ ํ์ฉ๋๋์ง ๋ณผ๊น? ๐ฏ
OpenSea ๊ฐ์ NFT ๋ง์ผํ๋ ์ด์ค์ ๋ฐฑ์๋๋ฅผ ์๋ฒ๋ฆฌ์ค๋ก ๊ตฌ์ถํ ์ ์์ด!
์ฃผ์ ๊ธฐ๋ฅ:
โข NFT ๋ฉํ๋ฐ์ดํฐ ์ธ๋ฑ์ฑ ๋ฐ ๊ฒ์
โข ์ค์๊ฐ ๊ฐ๊ฒฉ ์ถ์
โข ๊ฑฐ๋ ๋ด์ญ ์กฐํ
โข ์ฌ์ฉ์ ํฌํธํด๋ฆฌ์ค ๊ด๋ฆฌ
์ํคํ ์ฒ:
API Gateway โ Lambda (๋ผ๋ผ๋ฒจ) โ ElasticSearch (๊ฒ์) + DynamoDB (๋ฉํ๋ฐ์ดํฐ) + RDS (๊ฑฐ๋๋ด์ญ)
์ฌ๋ฌ DeFi ํ๋กํ ์ฝ์ ๋ฐ์ดํฐ๋ฅผ ์ง๊ณํด์ ๋ณด์ฌ์ฃผ๋ ๋์๋ณด๋!
์ฃผ์ ๊ธฐ๋ฅ:
โข ๋ฉํฐ์ฒด์ธ ์ง๊ฐ ์์ก ์กฐํ
โข ์ ๋์ฑ ํ APY ๊ณ์ฐ
โข ํฌํธํด๋ฆฌ์ค ์์ต๋ฅ ์ถ์
โข ๊ฐ์ค๋น ์ต์ ํ ์ถ์ฒ
๋๊ท๋ชจ ์์ด๋๋์ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌ!
์ํฌํ๋ก์ฐ:
1. ์ฌ์ฉ์๊ฐ ์ง๊ฐ ์ฃผ์ ๋ฑ๋ก
2. ์๊ฒฉ ์กฐ๊ฑด ๊ฒ์ฆ (Lambda)
3. ์น์ธ๋ ์ฃผ์๋ฅผ ํ์ ์ถ๊ฐ (SQS)
4. ๋ฐฐ์น๋ก ํธ๋์ญ์ ์ ์ก (Lambda Worker)
5. ๊ฒฐ๊ณผ ์๋ฆผ (SNS)
์ค๋งํธ ์ปจํธ๋ํธ ์ด๋ฒคํธ๋ฅผ ์ค์๊ฐ์ผ๋ก ๊ฐ์งํ๊ณ ์ฒ๋ฆฌ!
<?php
namespace App\Services;
class EventListenerService
{
public function listenToTransfers(string $contractAddress)
{
$web3 = new \Web3\Web3(env('BLOCKCHAIN_RPC_URL'));
// Transfer ์ด๋ฒคํธ ํํฐ
$filter = [
'fromBlock' => 'latest',
'toBlock' => 'latest',
'address' => $contractAddress,
'topics' => [
'0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' // Transfer ์ด๋ฒคํธ ์๊ทธ๋์ฒ
]
];
$web3->eth->getLogs($filter, function ($err, $logs) {
if ($err !== null) {
\Log::error('Failed to get logs', ['error' => $err->getMessage()]);
return;
}
foreach ($logs as $log) {
$this->processTransferEvent($log);
}
});
}
private function processTransferEvent($log)
{
// ์ด๋ฒคํธ ๋ฐ์ดํฐ ํ์ฑ
$from = '0x' . substr($log->topics[1], 26);
$to = '0x' . substr($log->topics[2], 26);
$tokenId = hexdec($log->data);
// DB์ ์ ์ฅ
\DB::table('nft_transfers')->insert([
'contract_address' => $log->address,
'from_address' => $from,
'to_address' => $to,
'token_id' => $tokenId,
'tx_hash' => $log->transactionHash,
'block_number' => hexdec($log->blockNumber),
'created_at' => now(),
]);
// ์๋ฆผ ์ ์ก
$this->notifyOwnerChange($log->address, $tokenId, $from, $to);
}
}
EventBridge๋ก ์ฃผ๊ธฐ์ ์คํ:
# serverless.yml
functions:
eventListener:
handler: listener.php
events:
- schedule: rate(1 minute) # 1๋ถ๋ง๋ค ์คํ
๐ ๊ณ ๊ธ ํจํด ๋ฐ ๋ฒ ์คํธ ํ๋ํฐ์ค
์ด์ ์ง์ง ํ๋ก์ฒ๋ผ ๊ฐ๋ฐํ๋ ๋ฐฉ๋ฒ์ ์๋ ค์ค๊ฒ! ๐
1. ๋ฉํฐ์ฒด์ธ ์ง์
<?php
namespace App\Services;
class MultiChainWeb3Service
{
protected $chains = [
'ethereum' => [
'rpc' => 'https://mainnet.infura.io/v3/YOUR-KEY',
'chain_id' => 1,
],
'polygon' => [
'rpc' => 'https://polygon-rpc.com',
'chain_id' => 137,
],
'bsc' => [
'rpc' => 'https://bsc-dataseed.binance.org',
'chain_id' => 56,
],
];
public function getBalance(string $address, string $chain = 'ethereum')
{
$web3 = new \Web3\Web3($this->chains[$chain]['rpc']);
$balance = null;
$web3->eth->getBalance($address, function ($err, $result) use (&$balance) {
if ($err !== null) {
throw new \Exception($err->getMessage());
}
$balance = $result;
});
return [
'chain' => $chain,
'address' => $address,
'balance' => \Web3\Utils::fromWei($balance, 'ether'),
'chain_id' => $this->chains[$chain]['chain_id'],
];
}
public function getAllBalances(string $address): array
{
$balances = [];
foreach (array_keys($this->chains) as $chain) {
try {
$balances[$chain] = $this->getBalance($address, $chain);
} catch (\Exception $e) {
\Log::error("Failed to get balance on {$chain}", [
'address' => $address,
'error' => $e->getMessage()
]);
$balances[$chain] = null;
}
}
return $balances;
}
}
2. ์ฌ์๋ ๋ก์ง (Exponential Backoff)
<?php
trait RetryableTrait
{
protected function retryWithBackoff(callable $callback, int $maxAttempts = 3, int $baseDelay = 1000)
{
$attempt = 0;
while ($attempt < $maxAttempts) {
try {
return $callback();
} catch (\Exception $e) {
$attempt++;
if ($attempt >= $maxAttempts) {
throw $e;
}
// Exponential backoff: 1s, 2s, 4s, 8s...
$delay = $baseDelay * pow(2, $attempt - 1);
usleep($delay * 1000);
\Log::warning("Retry attempt {$attempt}/{$maxAttempts}", [
'error' => $e->getMessage(),
'delay' => $delay . 'ms'
]);
}
}
}
}
class RobustWeb3Service extends Web3Service
{
use RetryableTrait;
public function getBalance(string $address)
{
return $this->retryWithBackoff(function () use ($address) {
return parent::getBalance($address);
});
}
}
3. ์ํท ๋ธ๋ ์ด์ปค ํจํด
<?php
namespace App\Services;
use Illuminate\Support\Facades\Cache;
class CircuitBreaker
{
protected $serviceName;
protected $failureThreshold;
protected $timeout;
protected $retryAfter;
public function __construct(string $serviceName, int $failureThreshold = 5, int $timeout = 60, int $retryAfter = 300)
{
$this->serviceName = $serviceName;
$this->failureThreshold = $failureThreshold;
$this->timeout = $timeout;
$this->retryAfter = $retryAfter;
}
public function call(callable $callback)
{
$state = $this->getState();
if ($state === 'open') {
throw new \Exception("Circuit breaker is OPEN for {$this->serviceName}");
}
try {
$result = $callback();
$this->onSuccess();
return $result;
} catch (\Exception $e) {
$this->onFailure();
throw $e;
}
}
protected function getState(): string
{
$failures = Cache::get("circuit:{$this->serviceName}:failures", 0);
$openedAt = Cache::get("circuit:{$this->serviceName}:opened_at");
if ($openedAt && (time() - $openedAt) > $this->retryAfter) {
return 'half-open';
}
return $failures >= $this->failureThreshold ? 'open' : 'closed';
}
protected function onSuccess()
{
Cache::forget("circuit:{$this->serviceName}:failures");
Cache::forget("circuit:{$this->serviceName}:opened_at");
}
protected function onFailure()
{
$failures = Cache::increment("circuit:{$this->serviceName}:failures");
Cache::put("circuit:{$this->serviceName}:failures", $failures, $this->timeout);
if ($failures >= $this->failureThreshold) {
Cache::put("circuit:{$this->serviceName}:opened_at", time(), $this->retryAfter);
\Log::error("Circuit breaker OPENED for {$this->serviceName}");
}
}
}
// ์ฌ์ฉ ์์
$breaker = new CircuitBreaker('infura_rpc');
$balance = $breaker->call(function () use ($address) {
return $this->web3Service->getBalance($address);
});
4. ์ด๋ฒคํธ ์์ฑ ํจํด
<?php
namespace App\Events;
class NFTTransferred
{
public $contractAddress;
public $tokenId;
public $from;
public $to;
public $txHash;
public $blockNumber;
public $timestamp;
public function __construct(array $data)
{
$this->contractAddress = $data['contract_address'];
$this->tokenId = $data['token_id'];
$this->from = $data['from'];
$this->to = $data['to'];
$this->txHash = $data['tx_hash'];
$this->blockNumber = $data['block_number'];
$this->timestamp = $data['timestamp'];
}
}
namespace App\Listeners;
class UpdateNFTOwnership
{
public function handle(NFTTransferred $event)
{
\DB::table('nft_ownerships')->updateOrInsert(
[
'contract_address' => $event->contractAddress,
'token_id' => $event->tokenId,
],
[
'owner_address' => $event->to,
'previous_owner' => $event->from,
'last_transfer_tx' => $event->txHash,
'last_transfer_block' => $event->blockNumber,
'updated_at' => now(),
]
);
}
}
class NotifyOwner
{
public function handle(NFTTransferred $event)
{
// ์ ์์ ์์๊ฒ ์๋ฆผ
// SNS, ์ด๋ฉ์ผ, ํธ์ ์๋ฆผ ๋ฑ
}
}
class UpdateStatistics
{
public function handle(NFTTransferred $event)
{
// ํต๊ณ ์
๋ฐ์ดํธ
\DB::table('collection_stats')
->where('contract_address', $event->contractAddress)
->increment('total_transfers');
}
}
๐ ์ผ๋ฐ์ ์ธ ๋ฌธ์ ์ ํด๊ฒฐ์ฑ
๊ฐ๋ฐํ๋ค ๋ณด๋ฉด ์ด๋ฐ ๋ฌธ์ ๋ค์ ๋ง๋๊ฒ ๋ ๊ฑฐ์ผ. ๋ฏธ๋ฆฌ ์์๋๋ฉด ์๊ฐ ์ ์ฝ! โฐ
| ๋ฌธ์ | ์์ธ | ํด๊ฒฐ์ฑ |
|---|---|---|
| Lambda ํ์์์ | ๋ธ๋ก์ฒด์ธ RPC ์๋ต ์ง์ฐ | ํ์์์ ์ฆ๊ฐ, ๋น๋๊ธฐ ์ฒ๋ฆฌ, ์บ์ฑ |
| ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ | ํฐ ๋ฐ์ดํฐ ์ฒ๋ฆฌ | ๋ฉ๋ชจ๋ฆฌ ํ ๋น ์ฆ๊ฐ, ์คํธ๋ฆฌ๋ฐ ์ฒ๋ฆฌ |
| ์ฝ๋ ์คํํธ ๋๋ฆผ | ํฐ ํจํค์ง ํฌ๊ธฐ | ๋ ์ด์ด ๋ถ๋ฆฌ, ์ฝ๋ ์ต์ ํ |
| RPC Rate Limit | ๊ณผ๋ํ ์์ฒญ | ์บ์ฑ, ๋ฐฐ์น ์ฒ๋ฆฌ, ์ฌ๋ฌ ์ ๊ณต์ ์ฌ์ฉ |
| DB ์ฐ๊ฒฐ ์คํจ | ์ฐ๊ฒฐ ํ ๊ณ ๊ฐ | ์ฐ๊ฒฐ ์ฌ์ฌ์ฉ, RDS Proxy ์ฌ์ฉ |
โ ๏ธ ํนํ ์ฃผ์ํ ์
1. ๊ฐ์ค๋น ํญ๋ฑ
์ด๋๋ฆฌ์ ๊ฐ์ค๋น๊ฐ ๊ฐ์๊ธฐ ์ค๋ฅด๋ฉด ํธ๋์ญ์ ์ด ์คํจํ ์ ์์ด. ๋์ ์ผ๋ก ๊ฐ์ค๋น๋ฅผ ์กฐ์ ํ๋ ๋ก์ง์ ๋ฃ์!
public function estimateGas()
{
$web3 = new \Web3\Web3(env('BLOCKCHAIN_RPC_URL'));
$gasPrice = null;
$web3->eth->gasPrice(function ($err, $price) use (&$gasPrice) {
if ($err === null) {
$gasPrice = $price;
}
});
// 20% ๋ฒํผ ์ถ๊ฐ
return $gasPrice->multiply(new \phpseclib\Math\BigInteger(120))
->divide(new \phpseclib\Math\BigInteger(100));
}
2. Nonce ๊ด๋ฆฌ
์ฌ๋ฌ ํธ๋์ญ์ ์ ๋์์ ๋ณด๋ผ ๋ nonce๊ฐ ๊ผฌ์ด๋ฉด ํฐ์ผ๋! DynamoDB๋ก nonce๋ฅผ ๊ด๋ฆฌํ์.
public function getNextNonce(string $address): int
{
$lockKey = "nonce_lock:{$address}";
// ๋ถ์ฐ ๋ฝ ํ๋
$lock = Cache::lock($lockKey, 10);
try {
$lock->block(5);
$currentNonce = \DB::table('nonce_tracker')
->where('address', $address)
->value('nonce') ?? 0;
$nextNonce = $currentNonce + 1;
\DB::table('nonce_tracker')->updateOrInsert(
['address' => $address],
['nonce' => $nextNonce, 'updated_at' => now()]
);
return $nextNonce;
} finally {
$lock->release();
}
}
๐ ์ฑ๋ฅ ๋ฒค์น๋งํฌ
์ค์ ๋ก ์ผ๋ง๋ ๋น ๋ฅธ์ง ๊ถ๊ธํ์ง? ํ ์คํธ ๊ฒฐ๊ณผ๋ฅผ ๊ณต์ ํ ๊ฒ! ๐
ํ ์คํธ ํ๊ฒฝ:
โข Lambda: 512MB ๋ฉ๋ชจ๋ฆฌ, ap-northeast-2 ๋ฆฌ์
โข ๋ธ๋ก์ฒด์ธ: Infura (Ethereum Mainnet)
โข ์บ์: DynamoDB
โข ๋์ ์ฌ์ฉ์: 1,000๋ช
๊ฒฐ๊ณผ:
โ ํ๊ท ์๋ต ์๊ฐ: 180ms (์บ์ ํํธ์ 50ms)
โ P95 ์๋ต ์๊ฐ: 350ms
โ P99 ์๋ต ์๊ฐ: 600ms
โ ์๋ฌ์จ: 0.02%
โ ์ฒ๋ฆฌ๋: ์ด๋น 5,000 ์์ฒญ
โ ์ฝ๋ ์คํํธ: 1.2์ด (์ต์ ํ ํ 800ms)
๐ฏ ์ ํต์ ์ธ ์๋ฒ vs ์๋ฒ๋ฆฌ์ค ๋น๊ต
EC2 t3.medium (์ ํต์ ๋ฐฉ์):
โข ์ด๊ธฐ ๋น์ฉ: ์๋ฒ ์ค์ 2์ผ
โข ์ ๋น์ฉ: $50~100
โข ํ์ฅ: ์๋, ์๊ฐ ์์
โข ์ ์ง๋ณด์: ์ง์์ ํ์
Lambda + API Gateway (์๋ฒ๋ฆฌ์ค):
โข ์ด๊ธฐ ๋น์ฉ: ๋ฐฐํฌ 30๋ถ
โข ์ ๋น์ฉ: $10~30 (ํธ๋ํฝ ๊ธฐ๋ฐ)
โข ํ์ฅ: ์๋, ์ฆ์
โข ์ ์ง๋ณด์: ๊ฑฐ์ ์์
๐ ๋ฏธ๋ ์ ๋ง ๋ฐ ๋ฐ์ ๋ฐฉํฅ
Web3์ ์๋ฒ๋ฆฌ์ค์ ์กฐํฉ์ ์ด์ ์์์ด์ผ! ์์ผ๋ก ์ด๋ป๊ฒ ๋ฐ์ ํ ๊น? ๐ฎ
1. Layer 2 ํตํฉ
Optimism, Arbitrum, zkSync ๊ฐ์ Layer 2 ์๋ฃจ์ ๋ค์ด ๋์ธ๊ฐ ๋๋ฉด์, ๋ฉํฐ ๋ ์ด์ด ์ง์์ด ํ์๊ฐ ๋ ๊ฑฐ์ผ!
class Layer2Service
{
protected $layers = [
'optimism' => 'https://mainnet.optimism.io',
'arbitrum' => 'https://arb1.arbitrum.io/rpc',
'zksync' => 'https://mainnet.era.zksync.io',
];
public function getBestLayer(string $operation): string
{
// ๊ฐ์ค๋น, ์๋, ์์ ์ฑ์ ๊ณ ๋ คํด์ ์ต์ ์ ๋ ์ด์ด ์ ํ
$gasPrices = $this->compareGasPrices();
return array_key_first($gasPrices);
}
}
2. AI ํตํฉ
AI๊ฐ ์ค๋งํธ ์ปจํธ๋ํธ ๋ถ์ํ๊ณ , ๊ฐ์ค๋น ์ต์ ํํ๊ณ , ์ฌ๊ธฐ ํ์งํ๋ ์๋๊ฐ ์ฌ ๊ฑฐ์ผ!
// AWS Bedrock๊ณผ ํตํฉ
public function analyzeContract(string $contractAddress)
{
$code = $this->getContractCode($contractAddress);
$bedrock = new BedrockClient([
'region' => 'us-east-1',
'version' => 'latest'
]);
$result = $bedrock->invokeModel([
'modelId' => 'anthropic.claude-v2',
'body' => json_encode([
'prompt' => "Analyze this smart contract for security issues: {$code}",
'max_tokens' => 2000,
])
]);
return json_decode($result['body'], true);
}
3. ํฌ๋ก์ค์ฒด์ธ ๋ธ๋ฆฟ์ง
์ฌ๋ฌ ๋ธ๋ก์ฒด์ธ ๊ฐ์ ์์ฐ ์ด๋์ด ๋ ์ฌ์์ง ๊ฑฐ์ผ!
4. ํ์ค์ํ ์๋ฒ๋ฆฌ์ค
IPFS, Filecoin, Arweave ๊ฐ์ ํ์ค์ํ ์คํ ๋ฆฌ์ง์ ํตํฉํด์ ์ง์ง ์์ ํ ํ์ค์ํ ์ฑ์ ๋ง๋ค ์ ์์ ๊ฑฐ์ผ!
๐ผ ์ค๋ฌด ์ ์ฉ ๊ฐ์ด๋
์ด์ ์ค์ ํ๋ก์ ํธ์ ์ ์ฉํ ์ฐจ๋ก! ๋จ๊ณ๋ณ๋ก ์งํํด๋ณด์. ๐
โข ์๊ตฌ์ฌํญ ์ ์: ์ด๋ค ๋ธ๋ก์ฒด์ธ ๊ธฐ๋ฅ์ด ํ์ํ๊ฐ?
โข ํธ๋ํฝ ์์: ์์ ์ฌ์ฉ์ ์์ ์์ฒญ ํจํด
โข ๋น์ฉ ๊ณ์ฐ: AWS ๊ณ์ฐ๊ธฐ๋ก ์์ ๋น์ฉ ์ฐ์ถ
โข ๋ณด์ ์๊ตฌ์ฌํญ: ์ด๋ค ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃจ๋๊ฐ?
โข ํต์ฌ ๊ธฐ๋ฅ๋ง ๋จผ์ ๊ตฌํ
โข ๋ก์ปฌ์์ ํ ์คํธ
โข ์์ ๊ท๋ชจ๋ก Lambda์ ๋ฐฐํฌ
โข ์ฑ๋ฅ ์ธก์ ๋ฐ ์ต์ ํ
โข CI/CD ํ์ดํ๋ผ์ธ ๊ตฌ์ถ (GitHub Actions, GitLab CI)
โข ๋ชจ๋ํฐ๋ง ์ค์ (CloudWatch, X-Ray)
โข ์๋ ์ค์ (SNS, Slack ์ฐ๋)
โข ๋ฐฑ์ ๋ฐ ์ฌํด ๋ณต๊ตฌ ๊ณํ
โข ๋ก๊ทธ ๋ถ์์ผ๋ก ๋ณ๋ชฉ ์ง์ ํ์
โข ์ฌ์ฉ์ ํผ๋๋ฐฑ ์์ง
โข ์ง์์ ์ธ ์ฑ๋ฅ ๊ฐ์
โข ์๋ก์ด ๊ธฐ๋ฅ ์ถ๊ฐ
"์๋ฒ๋ฆฌ์ค Web3๋ ๋จ์ํ ๊ธฐ์ ์ ์กฐํฉ์ด ์๋๋ผ, ๊ฐ๋ฐ์๊ฐ ์ธํ๋ผ ๊ฑฑ์ ์์ด ํ์ ์ ์ธ ํ์ค์ํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค ์ ์๊ฒ ํด์ฃผ๋ ํจ๋ฌ๋ค์์ ์ ํ์ด๋ค." ๐ก
๐ฏ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์! ์ฌ๊ธฐ๊น์ง ์ฝ์๋ค๋ฉด ์ ๋ง ๋๋จํด! ๐ ๋ผ๋ผ๋ฒจ๊ณผ AWS Lambda๋ก ์๋ฒ๋ฆฌ์ค Web3 ๊ตฌ์กฐ๋ฅผ ๊ตฌ์ถํ๋ ๋ฐฉ๋ฒ์ ์ฒ์๋ถํฐ ๋๊น์ง ๋ค๋ค๋ดค์ด.
ํต์ฌ์ ์ ๋ฆฌํ์๋ฉด:
โจ ํต์ฌ ์์ฝ
1๏ธโฃ ์๋ฒ๋ฆฌ์ค๋ Web3์ ์๋ฒฝํ ๊ถํฉ - ํธ๋ํฝ ๋ณ๋์ด ํฐ ๋ธ๋ก์ฒด์ธ ์ฑ์ ์ต์
2๏ธโฃ Bref๋ก ๋ผ๋ผ๋ฒจ์ Lambda์์ ์คํ - ์ต์ํ ํ๋ ์์ํฌ๋ก ๋น ๋ฅธ ๊ฐ๋ฐ
3๏ธโฃ ์บ์ฑ๊ณผ ์ต์ ํ๊ฐ ํต์ฌ - ๋น์ฉ ์ ๊ฐ๊ณผ ์ฑ๋ฅ ํฅ์์ ์ด์
4๏ธโฃ ๋ณด์์ ํํ ๋ถ๊ฐ - ๋๊ณผ ์ง๊ฒฐ๋๋ ์ฒ ์ ํ๊ฒ!
5๏ธโฃ ๋ชจ๋ํฐ๋ง์ผ๋ก ์ง์์ ๊ฐ์ - ๋ฐ์ดํฐ ๊ธฐ๋ฐ ์์ฌ๊ฒฐ์
์ด์ ๋น์ ๋ ์๋ฒ๋ฆฌ์ค Web3 ๊ฐ๋ฐ์์ผ! ๐ ์ฌ๋ฅ๋ท์์ ์ด๋ฐ ์ ๋ฌธ ์ง์์ ๊ณต์ ํ๋ฉด์ ๋ค๋ฅธ ๊ฐ๋ฐ์๋ค๊ณผ ํจ๊ป ์ฑ์ฅํ ์๋ ์๊ณ , ์ค์ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์ ์์ต๋ ์ฐฝ์ถํ ์ ์์ด!
๋ธ๋ก์ฒด์ธ ์ธ์์ ๋น ๋ฅด๊ฒ ๋ณํ๊ณ ์์ด. ํ์ง๋ง ๊ธฐ๋ณธ ์๋ฆฌ๋ฅผ ์ดํดํ๊ณ ์๋ค๋ฉด, ์ด๋ค ์๋ก์ด ๊ธฐ์ ์ด ๋์๋ ๋น ๋ฅด๊ฒ ์ ์ํ ์ ์์ ๊ฑฐ์ผ. ๊ณ์ ๋ฐฐ์ฐ๊ณ , ์คํํ๊ณ , ๊ณต์ ํ๋ฉด์ ํจ๊ป ์ฑ์ฅํ์! ๐ช
๐ ๋ค์ ๋จ๊ณ
โข GitHub์์ ์์ ํ๋ก์ ํธ ์ฐพ์๋ณด๊ธฐ
โข AWS ํ๋ฆฌํฐ์ด๋ก ์ง์ ์ค์ตํด๋ณด๊ธฐ
โข Web3 ์ปค๋ฎค๋ํฐ์ ์ฐธ์ฌํด์ ๊ฒฝํ ๊ณต์ ํ๊ธฐ
โข ์์ ํ๋ก์ ํธ๋ถํฐ ์์ํด์ ํฌํธํด๋ฆฌ์ค ๋ง๋ค๊ธฐ
์ง๋ฌธ์ด๋ ํผ๋๋ฐฑ์ด ์๋ค๋ฉด ์ธ์ ๋ ํ์์ด์ผ! ํจ๊ป ๋ ๋์ Web3 ์ธ์์ ๋ง๋ค์ด๊ฐ์! ๐
Happy Coding! ๐โจ
๊ด๋ จ ํค์๋
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.

๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.