Estrategias de almacenamiento de carga de imágenes


Cuando un usuario sube una imagen a mi sitio, la imagen pasa por este proceso;

  • el usuario carga pic
  • almacenar metadatos pic en db, dando a la imagen un id único
  • procesamiento de imágenes asíncronas (creación de miniaturas, recorte, etc.)
  • todas las imágenes se almacenan en la misma carpeta de cargas

Hasta ahora el sitio es bastante pequeño, y solo hay ~200,000 imágenes en el directorio de subidas. Me doy cuenta de que no estoy cerca del límite físico de los archivos dentro de un directorio, pero este enfoque claramente no escalará, así que me preguntaba si alguien tenía algún consejo sobre las estrategias de carga / almacenamiento para manejar grandes volúmenes de carga de imágenes.

EDITAR: Crear subcarpetas de nombre de usuario (o más específicamente, userid) parecería ser una buena solución. Con un poco más de investigación, he encontrado una gran información aquí; Cómo almacenar imágenes en su sistema de archivos
Sin embargo, este enfoque dir de id de usuario escalaría bien si se compra una CDN en el ecuación?

Author: Community, 2010-04-16

7 answers

He respondido a una pregunta similar antes, pero no puedo encontrarla, tal vez la OP eliminó su pregunta...

De todos modos, La solución Adams parece ser la mejor hasta ahora, sin embargo, no es a prueba de balas ya que images/c/cf/ (o cualquier otro par dir/subdir) todavía podría contener hasta 16^30 hashes únicos y al menos 3 veces más archivos si contamos las extensiones de imagen, mucho más de lo que cualquier sistema de archivos normal puede manejar.

AFAIK, SourceForge.net también utiliza este sistema para el proyecto los repositorios, por ejemplo el proyecto "fatfree" se colocarían en projects/f/fa/fatfree/, sin embargo creo que limitan los nombres de los proyectos a 8 caracteres.


Almacenaría el hash de imagen en la base de datos junto con un DATE / DATETIME / TIMESTAMP campo que indica cuándo se cargó / procesó la imagen y luego coloca la imagen en una estructura como esta:

images/
  2010/                                      - Year
    04/                                      - Month
      19/                                    - Day
        231c2ee287d639adda1cdb44c189ae93.png - Image Hash

O:

images/
  2010/                                    - Year
    0419/                                  - Month & Day (12 * 31 = 372)
      231c2ee287d639adda1cdb44c189ae93.png - Image Hash

Además de ser más descriptivo, esta estructura es suficiente para albergar cientos de miles (dependiendo en los límites de su sistema de archivos) de imágenes por día durante varios miles de años, esta es la forma en que Wordpress y otros lo hacen, y creo que lo hicieron bien en este caso.

Las imágenes duplicadas podrían consultarse fácilmente en la base de datos y solo tendría que crear enlaces simbólicos.

Por supuesto, si esto no es suficiente para usted, siempre puede agregar más subdirs (horas, minutos,...).

Personalmente no usaría ID de usuario a menos que no tenga esa información disponible en su base de datos, porque:

  1. Divulgación de nombres de usuario en la URL
  2. Los nombres de usuario son volátiles (es posible que pueda cambiar el nombre de las carpetas, pero aún así...)
  3. Un usuario puede hipotéticamente cargar un gran número de imágenes
  4. No sirve para nada (?)

Con respecto a la CDN no veo ninguna razón por la que este esquema (o cualquier otro) no funcionaría...

 26
Author: Alix Axel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:18

MediaWiki genera la suma MD5 del nombre del archivo cargado, y utiliza las dos primeras letras del MD5 (digamos, "c" y "f" de la suma "cf1e66b77918167a6b6b972c12b1c00d") para crear esta estructura de directorios:

images/c/cf/Whatever_filename.png

También podría usar el ID de imagen para un límite superior predecible en el número de archivos por directorio. Tal vez tome floor(image unique ID / 1000) para determinar el directorio padre, para 1000 imágenes por directorio.

 12
Author: Annika Backstrom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-15 20:22:59

Sí, sí Sé que este es un tema antiguo. Pero el problema para almacenar una gran cantidad de imágenes y cómo se debe organizar la estructura de carpetas subyacente. Así que presento mi manera de manejarlo con la esperanza de que esto pueda ayudar a algunas personas.

La idea de usar hash md5 es la mejor manera de manejar el almacenamiento masivo de imágenes. Teniendo en cuenta que diferentes valores pueden tener el mismo hash, recomiendo encarecidamente agregar también el id de usuario o nicname a la ruta para que sea única. Sí, eso es todo lo que se necesita. Si alguien tiene diferentes usuarios con el mismo id de base de datos - bueno, hay algo mal;) Así que root_path/md5_hash/user_id es todo lo que necesita para hacerlo correctamente.

Usar DATE / DATETIME / TIMESTAMP no es la solución óptima por cierto IMO. Terminas con grandes grupos de carpetas de imágenes en un día normal y casi vacías en las menos frecuentadas. No estoy seguro de que esto lleve a problemas de rendimiento, pero hay algo como la estética de los datos y una distribución de datos consistente siempre es superior.

Así que claramente opta por la solución hash. introduzca la descripción de la imagen aquí

Escribí la siguiente función para facilitar la generación de dichas rutas de almacenamiento basadas en hash. Siéntase libre de usarlo si le gusta.

/**
* Generates directory path using $user_id md5 hash for massive image storing 
* @author Hexodus 
* @param string $user_id numeric user id
* @param string $user_root_raw root directory string
* @return null|string
*/

function getUserImagePath($user_id = null, $user_root_raw = "images/users", $padding_length = 16, 
                            $split_length = 3, $hash_length = 12, $hide_leftover = true)
{
    // our db user_id should be nummeric
    if (!is_numeric($user_id))
        return null;

    // clean trailing slashes  
    $user_root_rtrim = rtrim( $user_root_raw, '/\\' );
    $user_root_ltrim = ltrim( $user_root_rtrim, '/\\' );
    $user_root = $user_root_ltrim;

    $user_id_padded = str_pad($user_id, $padding_length, "0", STR_PAD_LEFT); //pad it with zeros  
    $user_hash = md5($user_id); // build md5 hash

    $user_hash_partial = $hash_length >=1 && $hash_length < 32 
                        ? substr($user_hash, 0, $hash_length) : $user_hash;
    $user_hash_leftover = $user_hash_partial <= 32 ? substr($user_hash, $hash_length, 32) : null;

    $user_hash_splitted = str_split($user_hash_partial, $split_length); //split in chunks
    $user_hash_imploded = implode($user_hash_splitted,"/"); //glue aray chunks with slashes

    if ($hide_leftover || !$user_hash_leftover)
        $user_image_path = "{$user_root}/{$user_hash_imploded}/{$user_id_padded}"; //build final path
    else
        $user_image_path = "{$user_root}/{$user_hash_imploded}/{$user_hash_leftover}/{$user_id_padded}"; //build final path plus leftover

    return $user_image_path;
}

Llamadas de prueba de función:

$user_id = "1394";
$user_root = "images/users"; 
$user_hash = md5($user_id);
$path_sample_basic = getUserImagePath($user_id);
$path_sample_advanced = getUserImagePath($user_id, "images/users", 8, 4, 12, false);

echo "<pre>hash: {$user_hash}</pre>";
echo "<pre>basic:<br>{$path_sample_basic}</pre>";
echo "<pre>customized:<br>{$path_sample_advanced}</pre>";
echo "<br><br>";

La salida resultante-coloreada para su conveniencia ;): introduzca la descripción de la imagen aquí

 3
Author: Hexodus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-15 19:35:31

¿Ha pensado en usar algo como Amazon S3 para almacenar los archivos? Dirijo una empresa de alojamiento de fotos y después de alcanzar rápidamente los límites en nuestro propio servidor, nos cambiamos a AmazonS3. La belleza de S3 es que no hay límites como los inodos y lo que no, solo sigues lanzando archivos a él.

También: Si no te gusta S3, siempre puedes intentar dividirlo en subcarpetas tanto como puedas:

/id de usuario/año/mes/día/photoid.jpg

 2
Author: Mitch Dempsey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-19 02:06:59

Puede convertir un nombre de usuario a md5 y establecer una carpeta de 2-3 primeras letras del nombre de usuario md5 convertido para los avatares y para las imágenes que puede convertir y jugar con tiempo , cadenas aleatorias , id y nombres

8648b8f3ce06a7cc57cf6fb931c91c55-devcline

También una primera letra del nombre de usuario o id para la siguiente carpeta o inverso

Se verá como

Estructura:

stream/img/86/8b8f3ce06a7cc57cf6fb931c91c55.png    //simplest
stream/img/d/2/0bbb630d63262dd66d2fdde8661a410075.png //first letter and id folders
stream/img/864/d/8b8f3ce06a7cc57cf6fb931c91c55.png // with first letter of the nick
stream/img/864/2/8b8f3ce06a7cc57cf6fb931c91c55.png   //with unique id
stream/img/2864/8b8f3ce06a7cc57cf6fb931c91c55.png    //with unique id in 3 letters
stream/img/864/2_8b8f3ce06a7cc57cf6fb931c91c55.png   //with unique id in picture name

Código

$username = substr($username_md5, 1); // to cut first letter from the md5 converted nick
$username_first = $username[0]; // the first letter
$username_md5 = md5($username); // md5 for username
$randomname = uniqid($userid).md5(time());  //for generate a random name based on ID

También puedes probar con base64

 $image_encode = strtr(base64_encode($imagename), '+/=', '-_,');
 $image_decode = base64_decode(strtr($imagename, '-_,', '+/='));

Steam Y dokuwiki usan esta estructura.

 1
Author: devcline,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-20 21:06:49

Podría considerar el código abierto http://danga.com/mogilefs / ya que es perfecto para lo que estás haciendo. Te llevará de pensar en carpetas a espacios de nombres (que podrían ser usuarios) y te permitirá almacenar imágenes por ti. La mejor parte es que no tiene que preocuparse por cómo se almacenan los datos. Lo hace completamente redundante e incluso puede establecer controles sobre cómo son redundantes las miniaturas también.

 0
Author: Keith Adler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-15 20:30:05

Tengo soultion im usando durante mucho tiempo. Es un código bastante antiguo, y se puede optimizar aún más, pero todavía sirve bien como es.

Es una función inmutable que crea una estructura de directorios basada en:

  1. Número que identifica la imagen (ID DE ARCHIVO):

Se recomienda que este número sea único para el directorio base, como la clave primaria para la tabla de la base de datos, pero no es necesario.

  1. El directorio base

  2. El número máximo deseado de archivos y subdirectorios de primer nivel. Esto prometido solo se puede mantener si cada ID de ARCHIVO es único.

Ejemplo de uso:

Usando explícitamente el ID DEL ARCHIVO:

$fileName = 'my_image_05464hdfgf.jpg';
$fileId = 65347;
$baseDir = '/home/my_site/www/images/';
$baseURL = 'http://my_site.com/images/';

$clusteredDir = \DirCluster::getClusterDir( $fileId );
$targetDir = $baseDir . $clusteredDir;
$targetPath = $targetDir . $fileName;
$targetURL = $baseURL . $clusteredDir  . $fileName;

Usando nombre de archivo, number = crc32 (nombre de archivo )

$fileName = 'my_image_05464hdfgf.jpg';
$baseDir = '/home/my_site/www/images/';
$baseURL = 'http://my_site.com/images/';

$clusteredDir = \DirCluster::getClusterDir( $fileName );
$targetDir = $baseDir . $clusteredDir;
$targetURL = $baseURL . $clusteredDir  . $fileName;

Código:

class DirCluster {


/**
* @param mixed $fileId       - numeric FILE ID or file name
* @param int $maxFiles       - max files in one dir
* @param int $maxDirs        - max 1st lvl subdirs in one dir
* @param boolean $createDirs - create dirs?
* @param string $path        - base path used when creatign dirs
* @return boolean|string
*/
public static function getClusterDir($fileId, $maxFiles = 100, $maxDirs = 10,
$createDirs = false, $path = "") {

// Value for return
$rt = '';

// If $fileId is not numerci - lets create crc32
if (!is_numeric($fileId)) {
    $fileId = crc32($fileId);
}

if ($fileId < 0) {
  $fileId = abs($fileId);
}

if ($createDirs) {

    if (!file_exists($path))
    {
        // Check out the rights - 0775 may be not the best for you
        if (!mkdir($path, 0775)) { 
          return false;
        }
        @chmod($path, 0775);
    }
}

if ( $fileId <= 0 || $fileId <= $maxFiles ) { 
  return $rt;
}

// Rest from dividing
$restId = $fileId%$maxFiles;

$formattedFileId = $fileId - $restId;

// How many directories is needed to place file
$howMuchDirs = $formattedFileId / $maxFiles;

while ($howMuchDirs > $maxDirs)
{
    $r = $howMuchDirs%$maxDirs;
    $howMuchDirs -= $r;
    $howMuchDirs = $howMuchDirs/$maxDirs;
    $rt .= $r . '/'; // DIRECTORY_SEPARATOR = /

    if ($createDirs)
    {
        $prt = $path.$rt;
        if (!file_exists($prt))
        {
            mkdir($prt);
            @chmod($prt, 0775);
        }
    }
}

$rt .= $howMuchDirs-1;
if ($createDirs)
{
    $prt = $path.$rt;
    if (!file_exists($prt))
    {
        mkdir($prt);
        @chmod($prt, 0775);
    }
}

$rt .= '/'; // DIRECTORY_SEPARATOR

return $rt;


}

}
 0
Author: Tomasz Zadora,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-14 18:43:52